You are on page 1of 43

15.

Objektno orijentirano programiranje

Objektno temeljeno programiranje je metoda programiranja kojoj je temeljni princip da se klasa definira kao samostalna programska cjelina. Pri tome je poeljno koristiti princip enkapsulacije zdruivanje funkcija i podataka s definiranim javnim sueljem i implementacijom koja je skrivena od korisnika, kompoziciju objekata lanovi klase mogu se deklarirati pomou postojeih klasa generiko definiranje klasa Ove tehnike smo do sada koristili. Pod objektno orijentiranim programiranjem (OOP) podrazumijeva se metoda programiranja kojom se definiranje neke klase vri koritenjem svojstava postojeih klasa. Objekti koji se iniciraju pomou takovih klasa iskazuju dva svojstva: nasljeivanje i polimorfizam. Postupno emo upoznati ova svojstva OOP i tehniku programiranja kojom se realizira OOP.
15. Objektno orijentirano programiranje

Nasljeivanje

Nasljeivanje je tehnika kojom se definiranje neke klase vri koritenjem definicije postojee klase koja se naziva temeljna. klasa. Tako dobivena klasa se naziva izvedena klasa. Kada se pomou izvedene klase deklarira neki objekt, njegove lanske funkcije i varijable postaju i lanovi temeljne klase. Sintaksa deklaracije izvedene klase najee se koristi u obliku:
class ime_izvedene_klase : public ime_temeljne_klase { // sadri lanove koji su definirani u temeljnoj klasi // definiranje dodatnih lanova klase }

15. Objektno orijentirano programiranje

Primjerice, neka postoji klasa imena Temelj i pomou nje deklarirajmo klasu Izveden
class Temelj { public; Temelj() { elem0=0; } int elem0; } class Izveden : public Temelj { public: Izvedena() {elem1 = 0} int elem1; }

Ako pomou nje deklariramo klasu Izveden tada objekti ove klase imaju dva lana elem0 i elem1. Promjerice, objektu x, deklariranom s:
Izveden x;

lanskim varijablama pristupamo s:


x.elem0 = 5; x.elem1 = 7;

Kaemo da je klasa Izveden naslijedila lan klase Temelj, jer je elem0 deklariran u klasi Temelj.

15. Objektno orijentirano programiranje

Ukoliko se ne koristi nasljeivanje, klasu Izveden se moe zapisati u funkcionalno ekvivalentnom obliku:
class Izveden // bez nasljeivanja { public: Izvedena() {elem0 = 0; elem1=0;} int elem0; int elem1; }

U verziji s nasljeivanjem konstruktor je zaduen za inicijalizaciju samo onih varijabli koje su deklarirane u pojedinoj klasi. U C++ jeziku vrijedi pravilo : Konstruktor i destruktor se ne naslijeuje. Pri inicijalizaciji objekta izvravaju se svi konstruktori iz hijerarhije nasljeivanja najprije konstruktor temeljne klase, a zatim konstruktor izvedenih klasa. Isto vrijedi i za destruktor, jedino se poziv destruktora vri obrnutim redoslijedom najprije se izvrava destruktor izvedene klase, a potom destruktor temeljne klase. Napomena: esto se temeljna klasa naziva superklasa, a izvedene klase se nazivaju subklase. Takoer se za temeljnu klasu koristi naziv roditelj (parent class), a izvedene klase se nazivaju djeca (child classes)
15. Objektno orijentirano programiranje

15.1.2 Kada koristimo nasljeivanje

Nasljeivanje je korisno u sluaju kada se pomou temeljne klase moe izvesti vie klasa. Primjerice klasa Poligon moe biti temeljna klasa za definiciju klasa Pravokutnik i Trokut.

Poligon

Pravokut nik

Trokut

U klasi Poligon moemo deklarirati lanove koji su zajedniki za klasu Trokut i Pravokutnik. To su irina i visina poligona. U klasama Pravokutnik i Trokut, koje su izvedene klase, definirat emo funkciju kojom se rauna povrina poligona Povrsina().

15. Objektno orijentirano programiranje

class Poligon { protected: int sirina, visina; public: void init (int a, int b) { sirina=a; visina=b;} int Sirina() {return sirina;} int Visina() {return visina;} }; class Pravokutnik: public Poligon { public: int Povrsina (void) { return (sirina * visina); } }; class Trokut: public Poligon { public: int Povrsina (void) { return (sirina * visina / 2); } }; int main () { Pravokutnik pr; Trokut tr; pr.init (4,5); tr.init (4,5); cout << pr.Povrsina() << endl << tr.Povrsina() << endl; return 0; } Rezultat izvrenja je: 20 10

15. Objektno orijentirano programiranje

15.1.3 Public, private i protected specifikatori

Specifikatori public, private i protected odreuju pristup lanovima klase: Pristup lanovima public iz temeljne klase iz prijatelja klase iz izvedene klase izvan klase da da da da protecte privat d e da da da da da ne ne ne

Razlika specifikatora private i protected je u tome to se protected lanovi mogu koristiti u izvedenim klasama, a private lanovi se mogu koristiti samo u klasi u kojoj su deklarirani. Kljune rijei public, private i protected se koriste i kao specifikatori tipa nasljeivanja. U primjeru, klase Pravokutnik i Trokut su deklarirane s nasljeivanjem tipa public, tj.
class Pravokutnik: public Poligon; class Trokut: public Poligon;

Rije public oznaava da specifikatori pristupa varijablama iz temeljne klase vrijede i u izvedenim klasama.

15. Objektno orijentirano programiranje

Pravo pristupa prema tipu nasljeivanja prikazano je u slijedeoj tablici:

Tip nasljeivanja public protected private

Pravo pristupa u temeljnoj klasi public protected private public protected private protected protetected private private private private

Ako se nasljeivanje specificira s protected ili private, tada se ne moe javno pristupiti lanovima koji su temeljnoj klasi deklarirani kao public.Ukoliko se ne oznai specifikator nasljeivanja, podrazumjeva se da izvedena klasa ima private specifikator nasljeivanja.

15. Objektno orijentirano programiranje

to se sve nasljeuje iz temeljne klase

U pravilu, iz temeljne se klase nasljeuju sve lanske funkcije i varijable osim: konstruktora i destruktora operatora = prijateljskih funkcija i klasa Uvijek se prije izvrenja konstruktora izvedene klase izvrava predodreeni konstruktor temeljne klase. Ako elimo da se izvri neki drugi konstruktor, tada to treba eksplicitno zadati u izvedenim klasama u obliku
ime_izvedene_klase(parameteri) : ime_temeljne_klase(parameteri) { ... }

15. Objektno orijentirano programiranje

Primjerice:
class Otac { public: Otac() { cout << "otac: konstruktor bez argumenta\n"; } Otac(int a) { cout << "otac: konstruktor s argumentom\n"; } }; class Kcer : public Otac { public: Kcer(int a) {cout << "kcer: konstruktor s };

argumentom\n\n";}

class Sin : public Otac { public: Sin(int a):Otac(a) { cout << "sin: konstruktor s argumentom\n\n";} }; int main () { Kcer ana(1); Sin anton(1); return 0; } // // // // otac: konstruktor bez argumenta kcer: konstruktor s argumentom otac: konstruktor s argumentom sin: konstruktor s argumentom

15. Objektno orijentirano programiranje

10

Nadreenje lanskih funkcija

U izvedenoj se klasi moe definirati lanska funkcija istog imena kao i u temeljnoj klasi. Potrebno je razlikovati dva sluaja:
1. 2.

kada obje funkcije imaju iste parametre tada nastaje nadreenje funkcije (overriding) kada funkcije imaju razliite parametre tada nastaje preoptereenje funkcije (overloading).

Efekte preoptereenja funkcije smo ve upoznali i vidjeli da preoptereene funkcije kompajler tretira kao razliite funkcije. Razmotrimo primjer nadreenih funkcija. Uzet emo banalni primjer, da se definira klasa Romb pomou klase Pravokutnik. Poto za povrinu pravokutnika i romba vrijedi ista zakonitost, klasa Romb je definirana ve samim nasljeivanjem
class Romb: public Pravokutnik { };

15. Objektno orijentirano programiranje

11

Ako bi izvrili program:


int main () { Pravokutnik pr; Romb romb; pr.init (4,5); romb.init (4,5); cout << pr.Povrsina() << endl; cout << romb.Povrsina() << endl; return 0; }

dobili bi isti rezultat za povrinu romba i pravokutnika. Da bi pokazali efekt nadreenja funkcija u klasi Romb emo sada definirati funkciju kojom se rauna povrina koristei funkciju za povrinu iz temeljne klase:
class Romb: public Pravokutnik { public: int Povrsina (void) { return Pravokutnik::Povrsina(); } };

Uoimo da se poziv funkcije iz temeljne klase oznaava imenom temeljne klase i rezolucijskim operatorom ::.

15. Objektno orijentirano programiranje

12

U slijedeem programu testira se klasa Romb i ujedno pokazuje da se moe pristupiti nadreenim javnim funkcijama,
int main () { Romb romb; romb.init (4,5); cout << romb.Povrsina() << endl; cout << romb.Pravokutnik::Povrsina() << endl; return 0; }

Nasljeivanje generikih klasa

Nasljeivanje se moe koristiti i kod definicije generikih klasa. Opi oblik deklaracije nasljeivanja kod generikih klasa je
template <class T> class izvedena_klasa: public temeljna_klasa<T> { // sadri lanove koji su u temeljnoj klasi // definirani pomou generikog tipa T // definiranje dodatnih lanova klase }

15. Objektno orijentirano programiranje

13

Primjer: Klasu List, definiranu u prethofnom poglavlju koristit emo kao temeljnu klasu u izvoenju klase List1. U Izvedenoj klasi emo definirati lansku funkciju concat() slui za spajanje dviju lista
#include "list.h" template <class Type> class List1: public List<Type> { public: void concat(List<Type> & L); };

template <class Type> void List1<Type>::concat(List<Type> &otherList) { ListElem *pElem = otherList.First; while (pElem != NULL) { push_back(pElem->Elem); pElem = pElem->Next; } }

15. Objektno orijentirano programiranje

14

Viestruko nasljeivanje

U C++ je dozvoljeno da se nasljeivanje moe realizirati iz vie temeljnih klasa. Takovo nasljeivanje se naziva viestruko nasljeivanje. Sintaksa viestrukog nasljeivanja je:
class ime_izvedene_klase : lista_temeljnih_klasa { // sadri lanove koji su definirani u temeljnim klasama // definiranje dodatnih lanova klase }

Kao primjer uzmimo da u programu koji se odvija u grafikoj okolini treba ispisati string u toki kojoj su koordinate x,y.
class Point { protected: int m_x; int m_y; public: Point() : m_x(0), m_y(0) {} void SetPosition(int x, int y) {m_x=xy; m_y=y;} }

15. Objektno orijentirano programiranje

15

Moemo definirati klasu PositionedString koja naslijeuje svojstva od klase string i klase Point.

Point

String

PositionedStri ng class PositionedString: public string, public Point { //-----------public: Draw(); }

Objekt klase pomicati

PositionedString se ponaa kao objekt klase Point, primjerice moemo ga

PositionedString pstr; pstr = "Hello"; pstr.SetPosition(2, 10); pstr.Draw(); ...

// koristimo kao obicni string // pomiemo string

15. Objektno orijentirano programiranje

16

PROBLEM VIESTRUKOG NASLJEIVANJA Iako viestruko nasljeivanje moe biti vrlo korisno, mnogi programeri ga ne koriste, a u novijim programski jezicima (Java ,C#) nije ni implementirano. Razloga su dva: Prvi je razlog da se njime uspostavlja dosta komplicirana hijerarhija u nasljeivanju, a drugi razlog je da esto moe nastati konflikt koji jer prikazan slijedeom slikom: A B1 : A D: B1,B2 Klasa D nasljeuje klase B1 i B1, koje imaju istu temeljnu klasu A. Poto se konstruktori ne nasljeuju vidimo da bi se konstruktor A trebao inicirati dva puta, prvi put unutar konstruktora B1 a drugi put unutar konstruktora B2 (redoslijed poziva konstruktora odreen je redoslijedom u specifikaciji nasljeivanja). Ovaj se problem moe rijeiti koritenjem tzv. virtualnih temeljnih konstruktora, ali to ovdje nee biti objanjeno.
15. Objektno orijentirano programiranje

B2 : A

17

Savjet: Viestruko nasljeivanje mnogi programeri zovu goto of the 90s. Ne preporuuje se njegova upotreba.

15. Objektno orijentirano programiranje

18

Polimorfizam Polimorfizam je svojstvo promjenljivosti oblika. Kae se da je polimorfan onaj program koji je napisan tako da se programski algoritami ili funkcije mogu primijeniti na objekte razliitih oblika. U tu svrhu, u C++ jeziku je implementiran mehanizam virtualnih funkcija. Najprije emo pokazati kako se prilagoenje objektu moe dijelom izvriti ve prilikom kompajliranja programa (tzv- statiko povezivanje s objektom static binding), a zatim emo pokazati kao se pomou pokazivaa i virtualnih funkcija povezivanje s objektom vri tijekom izvrenja programa (tzv. izvrno povezivanje run-time binding).
15.2.1. Is a odnos objekata

U praksi se skoro iskljuivo koristi public nasljeivanje. Razlog tome je to se njime omoguuje tzv. Is a odnos objekata iz hijerarhije nasljeivanja. Moemo kazati Klasa Trokut je od vrste klase Poligon, i (eng. kind-of class relationship)

Objekt klase Trokut je objekt klase Poligon (eng. is-a object relationship) jer se lanovima objekta Trokut moe pristupati kao da se radi o objektu klase Poligon. Ovo is-a svojstvo ima programsku implikaciju da se pokazivai i reference, koji mogu biti deklarirani i kao argumenti funkcija, a koji se deklariraju pomou temeljne klase, mogu koristiti i za manipuliranje s objektima izvedenih klasa. To ilustrira primjer:
15. Objektno orijentirano programiranje

19

//.. definicija klasa Poligon, Pravokutnik i Trtokut void IspisDimenzija(Poligon & p) { cout << "sirina = " << p.Sirina() << endl; cout << "visina = " << p.Visina() << endl; } int main () { Pravokutnik pr; Trokut tr; pr.init (4,5); tr.init (4,5); cout << "Pravokutnik:" << endl; IspisDimenzija(pr); cout << "povrsina =" << pr.Povrsina() << endl; cout << "Trokut:" << endl; IspisDimenzija(pr); cout << "povrsina =" << tr.Povrsina() << endl; return 0; } Rezultat izvrenja je: Pravokutnik: sirina = 4 visina = 5 povrsina =20 Trokut:
15. Objektno orijentirano programiranje

20

sirina = 4 visina = 5 povrsina =10

Funkcija IspisDimenzija() je definirana s parametrom koji oznaava referencu objekta temeljne klase Poligon. Pri pozivu ove funkcije stvarni argument funkcije su objekti izvedenih klasa. etiri standardne pretvorbe su mogue izmeu objekate izvedene i temeljne javne klase: 1. Objekt izvedene klase moe se implicitno pretvoriti u objekt javne temeljne klase. 2. Referenca na objekt izvedene klase moe se implicitno pretvoriti u referencu objekta javne temeljne klase. 3. Pokaziva na objekt izvedene klase moe se implicitno pretvoriti u pokaziva objekta javne temeljne klase. 4. Pokaziva na lana objekta izvedene klase moe se implicitno pretvoriti u pokaziva lana objekta javne temeljne klase.

15. Objektno orijentirano programiranje

21

U sljedeem primjeru pokazano je kako se pomou pokazivaa na temeljnu klasu pristupa objektima izvedenih klasa.
//inherit5.cpp //.. primjena pokazivaa za povezivanje s objektima int main () { Pravokutnik pravokutnik; Trokut trokut; Poligon * pPol1 = &pravokutnik; Poligon * pPol2 = &trokut; pPol1->init (4,5); pPol2->init (4,5); cout << pravokutnik.Povrsina() << endl; cout << trokut.Povrsina() << endl; return 0; } Rezultat: 20 10

15. Objektno orijentirano programiranje

22

15.2.2. Virtualne lanske funkcije

U oba prethodna primjera samo je djelomino iskazan princip polimorfizma, naime koriten je samo za pristup lanskoj funkciji init, koja je definirana u temeljnoj i izvedenim klasama. Da bi princip polimorfizma mogli potpuno koristiti potreban je mehanizam kojim bi omoguio i poziv fukcije Povrsina. To nije bilo mogue ostvariti jer ta funkcija nije definirana u temeljnoj klasi Poligon. Ipak postoji , mehanizam da se ta funkcija moe, makar virtualno, definirati u temeljnoj klasi . To se postie tako da se u temeljnoj klasi deklarira funkcija Povrsina s prefiksom virtual.
// inherit6.cpp #include <iostream> using namespace std; class Poligon { protected: int sirina, visina; public: void init (int a, int b) { sirina=a; visina=b; } int Sirina() {return sirina;} int Visina() {return visina;} virtual int Povrsina (void) { return (0); } };

15. Objektno orijentirano programiranje

23

class Pravokutnik: public Poligon { public: int Povrsina (void) { return (sirina * visina); } }; class Trokut: public Poligon { public: int Povrsina (void) { return (sirina * visina / 2); } }; int main () { Pravokutnik pravokutnik; Trokut trokut; Poligon * pPol1 = &pravokutnik; Poligon * pPol2 = &trokut; pPol1->init (4,5); pPol2->init (4,5); cout << pPol1->Povrsina() << endl; cout << pPol2->Povrsina() << endl; return 0; }

Vidimo da je sada pomou pokazivaa mogue pristupiti svim funkcijama neke klase.

15. Objektno orijentirano programiranje

24

Postavlja se pitanje: na koji nain je prepoznato koja funkcija treba biti pozvana. Da bi to shvatili treba znati slijedee: kada se u temeljnoj klasi neka funkcija oznai kao virtualna tada se i sve funkcije istog imena u izvedenim klasama tretiraju kao virtualne funkcije. u izvedenim se klasama ispred imena virtualne funkcije moe napisati specifikator virtual, iako to nije nuno. poziv virtualnih funkcija vri se drukije nego poziv regularnih lanskih funkcija. Za svaki objekt koji ima virtualne funkcije kompajler generira posebnu tablicu (V-tablicu) u koju upisuje adresu virtualnih funkcija, takoer uz lanove klase zapisuje i pokaziva na ovu tablicu (vptr). To prikazuje slijedea slika:

15. Objektno orijentirano programiranje

25

objekt pravokutik
vptr width height

V-tablica za pravokutnik

Pravokutnik::Povrsina

objekt trokut
vptr width height

V-tablica za trokut

Trokut::Povrsina

.......

Objasnimo nain na koji se prepoznaje i vri poziv virtualne funkcije: 1. pretpostavimo da je adresa nekog objekta pridijeljena nekom pokazivau (ili je referenca) 2. kada se treba izvriti poziv virtualne funkcije najprije se dobavlja adresa tablice pokazivaa virtualnih funkcija koja je zapisana u pokazivau vptr. 3. zatim se iz tablice dobavlja adresa te funkcije i konano indirekcijom tog pokazivaa se vri poziv funkcije. Proizlazi da je izvrenje programa s virtualnim funkcijama sporije nego izvrenje programa s regularnim funkcijama, jer se gubi vrijeme za dobavu adrese virtualne funkcije. Bez obzira na ovu injenicu rad s virtualnim funkcijama je od velike koristi jer se pomou njih postie potpuni polimorfizam, a to je najvaniji element objektno orijentiranog programiranja.
15. Objektno orijentirano programiranje

26

15.2.3. Apstraktne temeljne klase

Apstraktne temeljne klase su klase u kojima je definirana bar jedna ista virtualna funkcija. ista virtualna funkcija se oznaava tako da se iza deklaracije funkcije napie = 0, tj Sintaksa iste virtualne funkcije: virtual deklaracija_funkcije = 0;

Klase koje sadre iste virtualne funkcije ne mogu se koristiti za deklaraciju objekata, ali se pomou njih moe deklarirati pokaziva na objekte ili argument funkcije koji je referenca objekta. Primjerice, klasa Poligon se moe tretirati kao apstraktna temeljna klasa :
// abstract class Poligon class Poligon { protected: int sirina, visina; public: void init (int a, int b) { sirina=a; visina=b; } virtual int Povrsina (void) =0; };
15. Objektno orijentirano programiranje

27

jer nije predvieno da se njome deklarira statike objekte. Napomena: Kompajler ne generira izvrni kod za iste virtualne funkcije, stoga u svim klasama koje se izvode iz apstraktne temeljne klase mora biti implementirana ta funkcija. Pregledom prethodnih programa vidi se da se moe pristupiti svim objektima iz hijerarhije nasljeivanja pomou pokazivaa na temeljnu klasu (Poligon *). To znai da ako se u temeljnoj klasi definira funkcije koje koriste virtualne funkcije, tada te funkcije mogu vrijediti za sve objekte iz hijerarhije nasljeivanja. Primjerice, u klasi Poligon emo definirati funkciju PrintPovrsina() kojom se ispisuje vrijednost povrine. To je pokazano u programu inherit7.cpp.
class Poligon { protected: int sirina, visina; public: void init (int a, int b) { sirina=a; visina=b; } int Sirina() {return sirina;} int Visina() {return visina;} virtual int Povrsina (void) = 0; void PrintPovrsina (void) { cout << this->Povrsina() << endl; } }; class Pravokutnik: public Poligon { public: int Povrsina (void) { return (sirina * visina); }
15. Objektno orijentirano programiranje

28

}; class Trokut: public Poligon { public: int Povrsina (void) { return (sirina * visina / 2); } }; int main () { Pravokutnik pravokutnik; Trokut trokut; Poligon * pPol1 = &pravokutnik; Poligon * pPol2 = &trokut; pPol1->init (4,5); pPol2->init (4,5); pPol1->PrintPovrsina(); pPol2->PrintPovrsina(); return 0; }

U funkciji
void PrintPovrsina (void) { cout << this->Povrsina() << endl; }

pristup funkciji Povrsina je izvren pomou this pokazivaa. To osigurava da e biti pozvana funkcija Povrsina koja pripada aktivnom objektu, jer this pokaziva uvijek pokazuje na aktivni objekt.
15. Objektno orijentirano programiranje

29

15.2.4. Virtualni destruktor

Prethodni program smo mogli napisati i u sljedeem obliku:


int main () { Poligon * pPol = new Pravokutnik; pPol->init(4,5); pPol->PrintPovrsina(); delete pPol; // dealociraj memoriju Pravokutnika pPol = new Trokut; pPol->init(4,5); pPol->PrintPovrsina(); delete pPol; return 0; } // ponovo koristi pokaziva

U prvoj liniji se alocira memorija za objekt tipa pravokutnik. Tom se objektu dalje pristupa pomou pokazivaa na temeljni objekt tipa Poligon. Kada se obave radnje s ovim objektom oslobaa se zauzeta memorija. Zatim se isti pokaziva koristi za rad s objektom tipa Trokut. Na prvi pogled sve izgleda uredu, i veina kompajlera e izvriti ovaj program bez greke. Ipak, ako bi ovaj program uzeli kao obrazac za za rad s dinamikim objektima, onda se u njemu krije jedna ozbiljna greka, a to je da se nee se izvriti poziv destruktora. Zato? Da bi odgovorili na ovo pitanje prisjetimo se to se dogaa kad se pozove operator delete.
15. Objektno orijentirano programiranje

30

Tada se najprije poziva destruktor objekta i destruktori svih klasa koje on nasljeuje, a zatim se vri dealociranje memorije. U ovom sluaju pokaziva pPol je deklariran kao pokaziva na temeljni objekt tipa Poligon, pa se nee izvriti poziv destruktora Pravokutnika i Trokuta (u ovom programu to nema neke posljedice, jer u klasama Pravokutnik i Trokut destruktor nema nikakovi uinak). Da bi se omoguilo da se moe pozvati destruktor izvedenih klasa pomou pokazivaa na temeljnu klasu potrebno je da se destruktor temeljne klase deklarira kao virtualna funkcija. Redoslijed poziva konstruktora i destruktora se moe analizirati pomou programa inherit8.cpp.
#include <iostream> using namespace std; class Superclass { public: Superclass (){cout << "Konstruktor temeljne klase\n"; } virtual ~Superclass(){cout << "Destruktor temeljne klase\n";} }; class Subclass : public Superclass { public: Subclass () {cout << "Konstruktor izvedene klase\n"; } ~Subclass() {cout << "Destruktor izvedene klase\n";} };
15. Objektno orijentirano programiranje

31

int main () { Superclass * p = new Subclass; delete p; return 0; }

Dobije se ispis:
Konstruktor temeljne klase Konstruktor izvedene klase Destruktor izvedene klase Destruktor temeljne klase

Zadatak: Provjerite, ako se u temeljnoj klase destruktor deklarira bez prefiksa virtual, dobije se ispis:
Konstruktor temeljne klase Konstruktor izvedene klase Destruktor temeljne klase

Dakle, u ovom se sluaju ne poziva destruktor izvedene klase.

15. Objektno orijentirano programiranje

32

Zapamtite: Uvijek je korisno deklarirati funkcije temeljne klase kao virtualne funkcije. Time se omoguuje polimorfizam klasa. Destruktor temeljne klase treba deklarirati kao virtualnu funkcija. Kada se kasnije u radu pokae da neku lansku funkciju nije potrebno koristiti polimorfno, tada se funkcija moe deklarirati kao nevirtualna lanska funkcija, jer se time dobija neto bre izvrenje poziva funkcija.

15. Objektno orijentirano programiranje

33

15.2.5. Polimorfizam na djelu izvrenje aritmetikih izraza

Pokazat emo neto kompliciraniji primjer: program kojim se raunaju aritmetiki izrazi. Analizirajmo najprije strukturu aritmetikih izraza. Oni se sastoje od vie lanova i faktora koji mogu biti grupirani unutar zagrada. lanovi i faktori sadre operande i operatore, a izraz u zagradama se tretira kao jedinstveni operand. Izrazi oblika -3 ili +7 se nazivaju unarni izrazi, a izrazi oblika 3 + 7 ili 6.7 / 2.0 se nazivaju binarni izrazi. Oni se jednostavno raunaju tako da se na operande primijeni raunska operacija definirana zadanim operatorom. U sluaju kompleksnijih izraza, primjerice -5 * (3+4) potrebno je prethodno izraz na adekvatan nain zapisati u memoriji, s tono oznaenim redoslijedom izvrenja operacija, kako bi se pravilno primijenilo pravilo djelovanja asocijativnosti i prioriteta djelovanja operatora. Kod modernih kompajlera i intepretera za internu prezentaciju izraza koristi se zapisi u obliku razgranate strukture koje se naziva apstraktno sintaktiko stablo. Primjerice, gornji izraz se moe apstraktno predstaviti u obliku:

15. Objektno orijentirano programiranje

34

* 5 3 + 4

Slika 15.5. Apstraktno sintaktiko stablo izraza: -5 * (3+4) Stablo ima vie vorova i grana. U vorovima su zapisani sintaktiki entiteti: operatori i operandi. vor iz kojeg poinje grananje stabla naziva se korijen stabla. vorovi koji nemaju grane nazivaju se lie stabla. Svi ostali vorovi se nazivaju unutarnji vorovi. Unutarnji vorovi i lie su podstabla stabla koje je definirano korijenom stabla. Apstraktno sintaktiko stablo aritmetikih izraza se moe izgraditi na slijedei nain: U korijen stabla se zapisuje operator najveeg prioriteta. Zatim se crtaju dvije grane koje povezuju vor lijevog i desnog operanda. Ako su ti operandi ponovo neki izrazi, proces izgradnje se nastavlja tako da se u te vorove ponovo upisuje operator najveeg prioriteta u podizrazima. Ako su operandi brojevi, u vor se upisuje vrijednost broja. Proces izgradnje zavrava tako da se u liu nalaze brojevi, a u viim vorovima se nalaze operatori.
15. Objektno orijentirano programiranje

35

U programu se sintaktiko stablo moe zapisati pomou struktura koje sadre informaciju vora i pokazivae na vor
*

Slika 15.6. Realizacija apstraktnog sintaktikog stabla izraza: -5 * (3+4) Vidimo da svi vorovi ne sadre istu informaciju: u unutarnjim vorovima je sadrana informacija o operatorima, a vorovi lia sadre operande. vorovi koji sadre binarne operatore trebaju imati dva pokazivaa za vezu s lijevim i desnim operandom, vorovi koji sadre unarni operator trebaju samo jedan pokaziva, a vorovi lia ne trebaju ni jedan pokaziva. Oito je da vorove treba tretirati polimorfno. Mogua su razliita rjeenja.

15. Objektno orijentirano programiranje

36

Pokazat emo rjeenje u kojem se za sve tipove vorova koristi temeljna virtualna klasa ExprNode, a pomou nje emo definirati klase BinaryExprNode, UnaryExprNode i NumNode.
class ExprNode // temeljna klasa za sve tipove izraza { friend ostream& operator<< (ostream&, const ExpNode *); public: ExpNode() {} virtual ~ExpNode() { } virtual void print (ostream&) const = 0; virtual double execute() const = 0; }; ostream& operator<<(ostream& out, const ExpNode *p) { p->print(out); // virtualni poziv, vrijedi za sve return out; }

subklase

U temeljnoj klasi ExprNode definirane su dvije iste virtualne funkcije print() i execute(). Funkcija print() sluit e za ispis sadraja stabla, a funkcija execute() e izvriti aritmetiki izraz koji je definiran podstablom. Operator << omoguuje da se ispie sadraj stabla iz poznatog pokazivaa na stablo (ili podstablo).

15. Objektno orijentirano programiranje

37

NumNode Klasa NumNode se izvodi iz ExprNode. Ona sadri numerike operande tipa double. Funkcija print() ispisuje numeriku vrijednost, a funkcija execute() vraa tu vrijednost, jer je vrijednost aritmetikog izraza koji sadri samo jedan broj upravo vrijednost tog broja.
class NumNode: public ExprNode { double n; public: NumNode (double x): n (x) { } ~NumNode() { }; void print (ostream& out) const { out << n; } double execute() const {return n;} };

UnaryExprNode Klasa UnaryExprNode se izvodi iz ExprNode. Ona sadri unarni operator + ili -, te pokazivaa na vor koji je operand.
class UnaryExprNode: public ExprNode { const int op; // tip operatora ('+' ili '-') ExprNode * oprnd; // pokazivac na operanda public: UnaryExprNode (const int a, ExprNode * b): op (a), oprnd (b) { } ~UnaryExprNode() {delete oprnd;} double execute() const
15. Objektno orijentirano programiranje

38

{ return (op=='-')? -oprnd->execute() : oprnd->execute();} void print (ostream& out) const { out << "(" << (char)op << oprnd << ")";} };

BinaryExprNode Klasa BinaryExprNode se takoer izvodi iz ExprNode. Ona sadri binarni operator ( +, -, * ili /) te pokazivae na vorove koji je predstavljaju lijevi i desni operand.
class BinaryExprNode: public ExprNode { private: const int op; // tip operatora ('+', '-', '*' ili '/') ExprNode * left; // pokazivac na lijevi operand ExprNode * right; // pokazivac na desni operand public: BinaryExprNode (const int a, ExprNode *b, ExprNode *c): op (a), left (b), right (c) { } ~BinaryExprNode() {delete left; delete right;} double execute() const; void print (ostream& out) const { out << "(" << left << (char)op << right << ")"; } }; double BinaryExprNode:: execute() const{ switch(op) { case '+': return left->execute() + right->execute(); case '-': return left->execute() - right->execute ();
15. Objektno orijentirano programiranje

39

case '*': return left->execute() * right->execute(); case '/': // provjeri dijeljenje s nulom { double val = right->execute(); if(val != 0) return left-> execute() / val; else return 0; } default: return 0; } }

U ovom sluaju funkcija print() ispisuje, unutar zagrada, lijevi operand, operator i desni operand. Funkcija execute() vraa vrijednost koja se dobije primjenom operatora na lijevi i desni operand. Posebno je analiziran sluaj operacije dijeljenja koko bi se izbjeglo dijeljenje s nulom. Konstruktor formira vor tako da prima argumente: operator, i pokaziva na vorove desnog i lijevog operanda. Destruktor dealocira memoriju koju zauzimaju operandi.

15. Objektno orijentirano programiranje

40

Testiranje ovog programa provodimo sljedeom main() funkcijom:


int main() { // formiraj stablo za izraz -5 * (3+4) ExprNode* t= new BinaryExprNode('*', new UnaryExprNode ('-', new NumNode(5)), new BinaryExprNode('+', new NumNode(3), new NumNode(4))); // ispii i izvri izraz cout << t << "=" << t-> execute() << "\n"; // dealociraj memoriju delete t; return 0; }

Uoimo da se svi vorovi alociraju dinamiki. Poinje se od korijena i zatim se dodaju podstabla. Kada se ovaj program izvri, dobije se ispis:
((-5)*(3+4))=-35

Ovaj primjer pokazuje da se koritenjem temeljne virtualne klase moe pomou pokazivaa temeljne klase manipulirati sa svim objektima iz izvedenih klasa. To je, bez sumnje, najvaniji mehanizam objektno orijentiranog programiranja.
15. Objektno orijentirano programiranje

41

Zadatak: Prethodni primjer realizirajte tako da lanska funkcija print() ispisuje izraz u obrnutoj poljskoj notaciji (postfiks oblik). Pomo: Dovoljno je da se u u klasi BinaryExprNode lanska funkcija
void print (ostream& out) const { out << "(" << left << (char)op << right << ")"; }

napie u obliku:
void print (ostream& out) const { out << left << " "<< right << " "<< (char)op << ""; }

Tada e se dobiti ispis:


(-5) 3 4 + * =-35

Ovime smo pokazali da se prethodno sintaktiko stablo lako moemo koristiti ne samo kao intepreter, ve i kao kompajler. U ovom sluaju se infiks izrazi kompajliraju u postfiks izraze.

15. Objektno orijentirano programiranje

42

- .Ovdje su prikazani elementi objektno orijentiranog programiranja na ekstremno pojednostavljenim primjerima. U praksi se objektno orijentirano programiranje koristi u veoma sloenim softverskim projektima. Kasnije emo pokuati dati odgovor na slijedee pitanja: Kako pristupiti analizi i izradi sloenih programa? Postoje li obrasci po kojima se moe rijeiti neke tipine probleme? Koji softverski alati su pogodni za razvoj OOP programa

15. Objektno orijentirano programiranje

43

You might also like