You are on page 1of 46

1

Lekcija 4 – Preklapanje operatora


Pregled
1.1 Pojam preklapanje operatora
1.2 Operatorske funkcije
1.3 Bočni efekti i veze
1.4 Preklapanje ++ i --
1.5 Preklapanje ()
1.6 Preklapanje []
1.7 Preklapanje ->
1.8 Preklapanje new i delete
1.9 Preklapanje =
1.10 U/I tokovi
1.11 Preklapanje >> i <<
1.12 Inicijalizacija i dodjela
1.13 Konverzije tipova
2

Ciljevi lekcije
• U ovoj lekciji:
– Upoznavanje sa osnovim pojmovima vezanim za preklapanje
operatora
– Definisanje operatorskih funkcija
– Predefinisanje operatora ++, --, (), [], ->, new i delete
– Predefinisanje operatora dodjele i razlike u odnosu na
inicijalizaciju
– Osnovne ulazno/izlaznih tokova i operatori >> i <<
– Konverzije tipova
3

1.1 Pojam preklapanja operatora


• Značenje operatora ugrađenih u jezik moguće je
definisati za korisničke tipove (klase)
– Preklapanje operatora (engl. operator overloading)
– Sličan princip kao kod preklapanja funkcija
• Ako je za operator definisano novo značenje onda
se kaže da je on preklopljen
• Povećavanje čitljivosti koda
– Primjer: Klasa koja predstavlja kompleksne brojeve i
operacija sabiranja
• Bilo bi pogodno da možemo koristiti operator + nad objektima
ove klase
4

1.1 Pojam preklapanja operatora


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i){};
Complex add(Complex c1)
{
Complex rez;
rez.re = this->re + c1.re;
rez.im = this->im + c1.im;
return rez;
}
};

int main()
{
Complex c1(1.0,2.0), c2(4.5,5.6), c3;
c3 = c1.add(c2);
return 0;
}
5

1.2 Operatorske funkcije


• Značenje operatora za korisnički tip definiše se putem
operatorskih funkcija
• Imaju posebna imena operator@
– Simbol @ označava neki operator ugrađen u jezik
• Npr. operator+
• Ove funkcije preklapaju standardne operatore (+,-, , /, *
itd.)
• Osim eksplicitnog poziva preko imena mogu se pozivati
koristeći notaciju koja se koristi za operatore nad
ugrađenim tipovima
– Izraz t1@t2 se tumači kao:
• operator@(t1,t2) // za operatorsku prijateljsku
funkciju klase
• t1.operator@(t2) // za operatorsku metodu klase
6

1.2 Operatorske funkcije


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i){};
friend Complex operator+(Complex c1, Complex c2);
void Print(){cout << "Re=" << re <<",Im=" << im << endl;}
};

Complex operator+(Complex c1, Complex c2)


{
Complex rez;
rez.re = c1.re + c2.re;
rez.im = c1.im + c2.im;
return rez;
}

int main()
{
Complex c1(1.0,2.0), c2(4.5,5.6), c3;
c3 = c1 + c2; //ekv. sa c3 = operator+(c1,c2);
c3.Print();
return 0;
}
7

1.2 Operatorske funkcije


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i){};
Complex operator-(Complex c1);
Complex operator+(float f){return Complex(this->re + f, this->im);}
void Print(){cout << "Re=" << re <<",Im=" << im << endl;}
};

Complex Complex::operator-(Complex c1)


{
return Complex(this->re - c1.re, this->im - c1.im);
}

int main()
{
Complex c1(1.0,2.0), c2(4.5,5.6), c3;
c3 = c2 - c1; //ekv. sa c3 = c2.operator-(c1);
c3.Print();
c3 = c3 + 1.0;
c3.Print();
return 0;
}
8

1.2 Operatorske funkcije


• Nije moguće
– Promijeniti prioritet, asocijativnost i broj operanda operatora
u odnosu na svojstva koja su ugrađena u jezik
• Npr. nije moguće definisati da prekolopljeni operator + ima
veći prioritet u odnosu na preklopljeni operator *
– Predefinisati operatore za ugrađene tipove
• Npr. nije dozvoljeno Complex operator+(float,
float)
– Uvoditi nove operatore u jezik
– Preklopiti operatore ., .*, ::, ?:, sizeof i throw dok
svi ostali mogu
• Neki operatori imaju posebna ograničenja za
preklapanje: new, delete, i++, i--, =, (),
[], ->, (tip)
9

1.2 Operatorske funkcije


• Operatorska funkcija može biti
– Funkcija članica klase
• Neka je @ binarni opererator, tada operatorska funkcije koja je
funkcija članica treba da se realizuje kao T operator@(Y)
• Prvi operand mora biti objekat klase kojoj operatorska funkcija
pripada
• Drugi operator mora biti tipa argumenta
• Npr. ako u klasi Complex postoji definisana operatroska
funkcija Complex operator+(float f), onda prvi
argument mora biti objekat klase Complex. Drugi argument
mora biti tipa float.
– Globalna funkcija (najčešće prijateljska)
• Neka je @ binarni opererator, tada operatorska funkcije koja je
globalna treba da se realizuje kao T operator@(X,Y)
10

1.2 Operatorske funkcije


• Unarni operator ima samo jedan operand, pa se
može realizovati:
– kao metoda bez argumenata:
tip operator@ ()
– kao globalna funkcija sa jednim argumentom:
tip operator@ (X x)
• Binarni operator ima dva argumenta, pa se može
realizovati
– kao metoda sa jednim argumentom:
tip operator@ (X xdesni)
– kao globalna funkcija sa dva argumenta:
tip operator@ (X xlevi, X xdesni)
11

1.2 Operatorske funkcije


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i){};
void Print(){cout << "Re=" << re <<",Im=" << im << endl;}
Complex operator-(); //unarni minus
};

Complex Complex ::operator-()


{
return Complex( -re, -im);
}

int main()
{
Complex c1(1.0,2.0);
c1 = -c1;
c1.Print();
return 0;
}
12

1.3 Sporedni efekti i veze


• Bočni efekti koji postoje kod operatora za
primitivne tipove ne podrazumijevaju se za
preklopljene operatore
– Bočni efekti postoje kod:
• Operatora ++ i –- (prefiksnih i postfiksnih)
• Svih operatora dodjele (=, +=, -=, *=, ...)
• Veze koje postoje između operatora za primitivne
tipove ne podrazumijevaju se za preklopljene
operatore
– Npr., ako je definisan operator+, a+=b ne znači
automatski a=a+b
– Ako je potreban, operator += mora posebno da se preklopi
13

1.3 Sporedni efekti i veze


• Preporuke:
– Preklopljeni operatori treba da imaju očekivano značenje
(zbog čitljivosti)
• Npr., ako su definisani i operator+= i operator+,
dobro je da a+=b ima isti efekat kao i a=a+b
• Operatori dodjele treba da mijenjaju stanje lijevog operanda
– Kada se definišu operatori za klasu, treba težiti da njihov
skup bude kompletan
• Npr., ako su definisani operator= i operator+, treba
definisati i operator+=
• Za definisan operator== treba definisati i operator!=
14

1.4 Preklapanje ++ i --
• Komplikovaniji je postoji i prefiksna i postfiksna varijanta
• Preklapanje prefiksnog operatora ++ vrši se putem
uobičajenih operatorskih funkcija
– Kao funkcija članica - T operator++()
– Kao globalna funkcija - T operator++(T)
– Analogno važi i za --
• Preklapanje postfiksnog operatora ++ vrši se
– Kao funkcija članica - T operator++(int)
– Kao globalna funkcija - T operator++(T, int)
– Ako se drugi stvarni argument ne navede biće 0
• Ne može se navesti kada se funkcija poziva kao operator
• Može se navesti kada se funkcija poziva direktno t.operator++
(k)
– Analogno važi i za --
15

1.4 Preklapanje ++ i --
class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i){};
void Print(){cout << "Re=" << re <<",Im=" << im << endl;}
Complex operator++(); //prefiksna
Complex operator++(int); //postfiksna
};

Complex Complex ::operator++()


{
//prvo se uvecaju re i im,
//pa se kreira lokalni objekat
return Complex( ++re, ++im);
}

Complex Complex ::operator++(int k)


{
//prvo se kreira lokalni objekat,
//pa uvecaju re i im
return Complex( re++, im++);
}
16

1.4 Preklapanje ++ i --
int main()
{
Complex c1(1.0,2.0), c2;
cout << "c1="; c1.Print();
c2 = c1++;
cout << "c2="; c2.Print();
cout << "c1="; c1.Print();

c2 = ++c1;
cout << "c2="; c2.Print();
cout << "c1="; c1.Print();
return 0;
}
17

1.5 Preklapanje ()
• Odgovara mu funkcija operator()()
– Mora da bude nestatička funkcija
– Ne može da bude globalna funkcija
– Ima proizvoljan broj argumenata
– Svaki argument može da bude proizvoljnog tipa
• Poziva se f(a1,...,aN)
– To je ekvivalent f.operator()(a1,...,aN)
• Rezultat je lvrijednost ako i samo ako funkcija
vraća referencu
– Ovo važi za sve operatorske funkcije
– Termin lvrijednost označava izraz koji se odnosi na neki
objekat ili funkciju
• Najčeće, u pitanju je izraz koji se može naći sa lijeve strane
znaka jedankosti
18

1.5 Preklapanje ()
class Matrix
{
private:
int** mat;
int r;
int c;
public:
Matrix(int r, int c);
~Matrix();
int& operator()(int i, int j);
};

Matrix::Matrix(int r, int c) :r(r), c(c)


{
mat = new int*[r];
for(int i = 0; i < r; i++) {mat[i] = new int[c];}
}

Matrix::~Matrix()
{
for(int i = 0; i < r; i++) {delete new int[c];}
delete mat;
}

int& Matrix::operator()(int i, int j)


{
return mat[i][j];
}
19

1.5 Preklapanje ()
int main()
{
Matrix m(3,3);
m(0,0) = 1;
//lvrijednost, moze se naci sa
//lijeve strane znaka jednakosti
cout << m(0,0);
return 0;
}
20

1.6 Preklapanje []
• Odgovara mu funkcija operator[]()
• Mora da bude nestatička funkcija
• Ne može da bude globalna funkcija
• Ima tačno jedan argument
– Može da bude proizvoljnog tipa
• Poziva se niz[ind]
– To je ekvivalent niz.operator[](ind)
• Najčešća upotreba
– Kod objekata koji predstavljaju neke kolekcije
21

1.6 Preklapanje []
class Array
{
private:
int* a;
int num;
public:
Array(int num);
~Array();
int& operator[](int i);
};

Array::Array(int num) : num(num)


{ a = new int[num];}

Array::~Array()
{delete a;}

int& Array::operator[](int i)
{ return a[i];}

int main()
{
Array a(3);
a[0] = 1;
cout << a[0];
return 0;
}
22

1.7 Preklapanje ->


• Unarni operator kojem odgovara funkcija
operator->()
• Mora da bude nestatička funkcija
• Ne može da bude globalna funkcija
• Mora da bude bez argumenata
– Može da bude proizvoljnog tipa
• Poziva se o->clan
– To je ekvivalent (o.operator->()(ind))->clan
• Najčešća upotreba
– Pristup članovima preko pametnih pokazivača
• Broji pristupe
23

1.7 Preklapanje ->


class X
{ public: int data;};

class Y
{
private:
X* xPtr;
int num;
public:
Y(X* xPtr) : xPtr(xPtr), num(0){};
X* operator->(){num++; return xPtr;};
int getNum(){return num;}
};

int main()
{
X x;
Y y(&x);
y->data = 5; //(poziva se y->operator->)->data
cout << "data="<< x.data << ",num=" << y.getNum();
return 0;
}
24

1.8 Preklapanje new i delete


• Predefinisanjem operatora new vrši se kontrola
nad alokacijom memorije
– Za male objekte može se vršiti precizno alokacija i
optimizovati taj postupak
• Operatorske funkcije new i delete su uvijek
statičke
– Kada su pozvane objekat još uvijek ne postoji
• Unutar tijela nije potrebno pozivati konstruktor, tj.
destruktor
– Oni su implicitno pozvani
• Operator new treba da vrati pokazivač na alocirani
prostor
25

1.8 Preklapanje new i delete


• Operatorska funkcija new
– void* operator new (size_t sz)
– Argument sz je cjelobrojni i prestavlja broj bajta koje treba
alocirati
– Vraća generički pokazivač void*, tj. pokazivač na zauzetu
memoriju
• Operatorska funkcija new definiše se
– void operator delete (void* ptr)
– Ima jedan argument ptr koji je pokazivač na memoriju
koju treba osloboditi
26

1.8 Preklapanje new i delete


class X
{
private:
static int num;
public:
void* operator new (size_t sz);
void operator delete (void* ptr);
};
int X::num = 0;

void* X::operator new(size_t sz)


{
if (num == 10) {cout << "max 10"<<endl; return 0;}
num++;
return ::operator new(sz);
}

void X::operator delete (void* ptr)


{
if (ptr != 0){num--; ::operator delete(ptr);}
}

int main()
{
X* x[50];
for(int i = 0; i < 11; i++)
{ x[i] = new X;}
return 0;
}
27

1.9 Preklapanje =
• Binarni operator kojem odgovara funkcija
operator=
• Mora da bude nestatička funkcija
• Ne može da bude globalna funkcija
• Mora da ima jedan argument
– Može da bude proizvoljnog tipa
• Poziva se o=izraz
– To je ekvivalent o.operator=(izraz)
• Uvijek je treba definisati kada prosto dodjeljivanje
član po član nije logično
– Sličan problem kod konstruktora kopije
28

1.9 Preklapanje =
class Array
{
private:
int* a;
int num;
public:
Array(int num): num(num){a = new int[num];};
~Array(){delete a;};
int& operator[](int i) {return a[i];};
};

int main()
{
Array a1(3), a2(2);
a1[0] = 1;
a2 = a1;
a2[0] = 5;
cout << "a1[0]=" << a1[0];
cout << ",a2[0]=" << a2[0];
}
29

1.9 Preklapanje =
30

1.9 Preklapanje =
class Array
{
//isto ka raniije
Array& operator=(Array& arr);
};

Array& Array::operator=(Array& arr)


{
//izbjegavanje a=a problema
if (this == &arr){return *this;}

delete a;
num = arr.num;
a = new int[num];
for(int i = 0; i < num; i++)
a[i] = arr.a[i];

return *this;
}

int main()
{
Array a1(3), a2(2);
a1[0] = 1;
a2 = a1;
a2[0] = 5;
cout << "a1[0]=" << a1[0];
cout << ",a2[0]=" << a2[0];
}
31

1.9 Preklapanje =
32

1.10 U/I tokovi


• U C++ ne postoje naredbe ugrađene u jezik koje
su namijenjene ulazno/izlaznim operacijama
– U tu svrhu se koriste standardne biblioteke
– Isto važi i za programski jezik C
• Stadardne biblioteke za U/I realizovane su u duhu
OOP-a
– Deklaracije biblioteka nalaze u zaglavlju <iostream.h>
– Definisani su pojmovi ulaznog i izlaznog toka
• Logički pojmovi koji se odnose na ulazi i izlaz niza znaka na
uređaj ili datoteku
• Biblioteka iostream sadrži
– Klasu istream koja realizuje ulazni tok
– Klasu ostream koja realizuje izlazni tok
33

1.10 U/I tokovi


• Iz navedenih klasa izvedene su klase ifstream i
ofstream
– Jednom objektu klase ifstream/ofstream može da se
pridruži jedna datoteka ili uređaj za U/I
• U biblioteci iostream definisana su i dva globalna
statička objekta:
– Objekat cin klase istream koji je pridružen standardnom
ulaznom uređaju (obično tastatura)
– Objekat cout klase ostream koji je pridružen standardnom
izlaznom uređaju (obično ekran)
• Klasa istream sadrži po jednu operatorsku funkciju
operator>> za svaki ugrađeni tip podatka
– istream& operator>>(istream &is, Tip &t);
• Gdje Tip označava neki ugrađeni tip
34

1.10 U/I tokovi


• Klasa ostream sadrži po jednu operatorsku
funkciju operator<< za svaki ugrađeni tip
podatka
– ostream& operator<<(ostream &os, T& x);
• Gdje Tip označava neki ugrađeni tip
• Lijevi operand je tok, dok je desni se drugi piše ili
čita u ili iz toka
– Funkcije vraćaju reference na isti tok nad kojim je izvršena
U/I operacija
• Mogu se vršiti višestruke U/I operacije
• Operatore >> i << treba koristiti za jednostavne
U/I operacije
35

1.11 Preklapanje >> i <<


• Moguće je da korisnik predefiniše operatore << i
>> za svoje klase
– Definišu se globalne operatorske funkcije koje su prijatelji
klase
• Ne mogu biti metode klase jer prvi operand mora biti
ostream ili istream
– Prvi operand je tipa ostream ili istream
– Drugi operand je objekat tipa klase za koju predefinišemo
operator
– Poželjno je funkcije vraća referencu na ostream, tj. na
istream kako bi omogućili pozivanje u nizu
36

1.11 Preklapanje >> i <<


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i)
{};
friend ostream& operator<<(ostream& os, Complex& c);
};

ostream& operator<<(ostream& os, Complex& c)


{
os << "(Im=" << c.re << ",Re=" << c.im << ")";
return os;
}
int main()
{
Complex c(2.3, 1.0);
cout << c;
}
37

1.12 Inicijalizacija i dodjela


• Postoji razlika između pojmova inicijalizacije i
dodjele
– Inicijalizacija se vrši nad objektom u “fazi kreriranja”
• Vrši se samo jednom
• Definiše početno stanje objekta
– Dodjela se vrši nad postojećim objektom
• Može se vršiti proizvoljan broj puta
• Mijenja postojeće stanje objekta
• Kada se objekat inicijalizuje objektom iste klase
poziva se konstruktor, a ne operator dodjele
– Konstruktor se poziva čak iako je notacija za inicijalizaciju
simbol =
– Ako je izraz sa desne strane = istog tipa kao i objekat koji se
kreira, poziva se konstruktor kopije
38

1.12 Inicijalizacija i dodjela


• Operacija dodjele vrši se u okviru naredbe dodjele
(operator =)
– Operator dodjele se može preklopiti pisanjem operatorske
funkcije operator=
• Podrazumijevano ponašanje je kopiranje objekta
član po član
– Pri kopiranju članova klase pozivaju se operatori = za
članove klase
– Problem:
• Ako je član klase pokazič izvršiće se tzv. plitko dodjeljivanje,
tj. kopiranje pokazivača, ali ne i sadržaja
• Praktično pravilo
– Ako klasa ima konstruktor kopije, operator dodjelje ili
destruktor, onda najvjerovatnije treba da ima sva tri
39

1.12 Inicijalizacija i dodjela


class Array
{
private:
int* a;
int num;
public:
Array(int num);
Array(Array& arr);
~Array(){delete a;};
int& operator[](int i) {return a[i];};
Array& operator=(Array& arr);
friend bool operator==(Array& arr1, Array& arr2);
friend ostream& operator<<(ostream &os, Array& arr);
};

Array::Array(int num): num(num)


{
cout << "Konstr." << endl;
a = new int[num];
};
40

1.12 Inicijalizacija i dodjela


Array::Array(Array& arr): num(arr.num)
{
cout << "Konstr. kop." << endl;
a = new int[num];

for(int i = 0; i < num; i++)


a[i] = arr.a[i];
};

Array& Array::operator=(Array& arr)


{
cout << "Op. =" << endl;

if (this == &arr){return *this;}

delete a;
num = arr.num;
a = new int[num];
for(int i = 0; i < num; i++)
a[i] = arr.a[i];

return *this;
}
41

1.12 Inicijalizacija i dodjela


bool operator==(Array& arr1, Array& arr2)
{
cout << "Op. ==" << endl;
if (arr1.num != arr2.num) return 0;
for(int i = 0; i < arr1.num; i++)
if (arr1[i] != arr2[i])
return 0;
return 1;
}

ostream& operator<<(ostream &os, Array& arr)


{
for(int i = 0; i < arr.num; i++) cout << arr[
i ] << " ";
cout << endl;
return os;
}
42

1.12 Inicijalizacija i dodjela


int main()
{
Array a1(2);
a1[0] = 1; a1[1] = 2;
cout << "a1=" << a1;
Array a2 = a1;
a2[0] = 5;
cout << "a2=" << a2;

if (a1 == a2)
cout << "a1==a2" << endl;
else
cout << "a1!=a2" << endl;

a2 = a1;

cout << "a2=" << a2;

if (a1 == a2)
cout << "a1==a2" << endl;
else
cout << "a1!=a2" << endl;

return 0;
}
43

class Point
1.12 Inicijalizacija i dodjela
{
private:
float x;
float y;
public:
Point(float x = 0.0, float y = 0.0) : x(x), y(y) {cout << "Konstr. : Point"
<< endl;};
Point(Point& p) : x(p.x), y(p.y) {cout << "Konstr. kop : Point" << endl;};
Point& operator=(Point &p){cout << "Point Op =" << endl; x = p.x; y = p.y;
return *this;}
};

class Circle
{
private:
Point p;
float r;
public:
Circle(float x = 0.0, float y = 0.0, float r = 0.0) : p(x,y), r(r)
{cout << "Konstr : Circle" << endl;}
Circle(Circle& c) : p(c.p), r(c.r)
{cout << "Konstr kop. : Circle" << endl;}
};
int main()
{
Circle c1(1.0,2.0,3.0);
Circle c2 = c1;
c2 = c1;
}
44

1.13 Konverzije tipova


• Konverzija tipova može se vršiti putem konstr.
class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) : re(r), im(i)
{cout << "Konstr " << endl;};
~Complex(){cout << "Dest" << endl;}
friend ostream& operator<<(ostream &os, Complex& c1);
};

ostream& operator<<(ostream& os, Complex& c)


{
os << "(Im=" << c.re << ",Re=" << c.im << ")" << endl;
return os;
}

int main()
{
Complex c1(1.2, 2.3);
cout << c1;
c1 = 5.0;
cout << c1;
return 0;
}
45

1.13 Konverzije tipova


• Drugi način jeste definisanje operatorske funkcije
za konverziju
• Operatorska funkcija članica klase X koja ima
oblik operator T() definiše konverziju iz tipa X
u tip T
• Operatorska funkcija operator T() ne može da
bude statička metoda, niti može da bude globalna
prijateljska funkcija
• Nema argumente
– U pitanju je unarni operator
• Tip povratnog rezultata se ne navodi u deklaraciji
– Podrazumijeva se na osnovu imena funkcije
46

1.13 Konverzije tipova


class Complex
{
private:
float re;
float im;
public:
Complex(float r = 0.0, float i = 0.0) :
re(r), im(i){};
operator float(){return re;}
};

int main()
{
Complex c1(1.2, 2.3);
float f;
f = c1;
cout << f << endl;
return 0;
}

You might also like