15. Objektno orijentirano programiranje - marjan.fesb.hrmarjan.fesb.hr/~mateljan/cpp/slides15-OOP-slides.pdf · 15. Objektno orijentirano programiranje 2 Nasljeđivanje Nasljeđivanje

Embed Size (px)

Citation preview

  • 15. Objektno orijentirano programiranje 1

    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 tehniku programiranja kojom se realizira OOP.

  • 15. Objektno orijentirano programiranje 2

    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.

    lanovi temeljne klase postaju i lanovi i izvedene 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 3

    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 4

    Ukoliko se ne koristi nasljeivanje, klasu Izveden se moe zapisati u funkcionalno

    ekvivalentnom obliku:

    class Izveden // bez naslijeivanja

    {

    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 naslijeivanja 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 5

    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.

    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().

    Poligon

    Pravokut

    nik

    Trokut

  • 15. Objektno orijentirano programiranje 6

    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

  • 15. Objektno orijentirano programiranje 7

    15.1.3 Public, private i protected specifikatori

    Specifikatori public, private i protected odreuju pristup lanovima klase:

    Pristup lanovima public protecte

    d

    privat

    e

    iz temeljne klase da da da

    iz prijatelja klase da da da

    iz izvedene klase da da ne

    izvan klase da 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

    naslijeivanja.

    U naem primjeru, klase Pravokutnik i Trokut su deklarirane s oznakom naslijeivanja

    tipa public, tj.

    class Pravokutnik: public Poligon;

    class Trokut: public Poligon;

  • 15. Objektno orijentirano programiranje 8

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

    izvedenim klasama. Pravo pristupa prema tipu naslijeivanja prikazano je u slijedeoj

    tablici:

    Tip

    nasljeivanja

    Pravo pristupa u temeljnoj klasi

    public protected private

    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

    naslijeivanja, podrazumjeva se da izvedena klasa ima private specifikator naslijeivanja.

  • 15. Objektno orijentirano programiranje 9

    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 10

    Primjerice naslijeivanja:

    class Otac {

    public:

    Otac() { cout

  • 15. Objektno orijentirano programiranje 11

    Nadreenje lanskih funkcija

    U izvedenoj se klasi moe definirati lanska funkcija istog imena kao i u temeljnoj klasi.

    Potrebno je razlikovati dva sluaja:

    1. kada obje funkcije imaju iste parametre tada nastaje nadreenje funkcije

    (overriding)

    2. 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 12

    Ako bi izvrili program:

    int main () {

    Pravokutnik pr;

    Romb romb;

    pr.init (4,5);

    romb.init (4,5);

    cout

  • 15. Objektno orijentirano programiranje 13

    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

  • 15. Objektno orijentirano programiranje 14

    Nasljeivanje generikih klasa

    Nasljeivanje se moe koristiti i kod definicije generikih klasa. Opi oblik deklaracije

    naslijeivanja kod generikih klasa je

    template

    class izvedena_klasa: public temeljna_klasa

    {

    // sadri lanove koji su u temeljnoj klasi

    // definirani pomou generikog tipa T

    // definiranje dodatnih lanova klase

    }

  • 15. Objektno orijentirano programiranje 15

    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 List1: public List

    {

    public:

    void concat(List & L);

    };

    template void List1::concat(List &otherList)

    {

    ListElem *pElem = otherList.First;

    while (pElem != NULL)

    {

    push_back(pElem->Elem);

    pElem = pElem->Next;

    }

    }

  • 15. Objektno orijentirano programiranje 16

    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 17

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

    Point.

    class PositionedString: public string, public Point {

    //------------

    public:

    Draw();

    }

    Objekt klase PositionedString se ponaa kao objekt klase Point, primjerice moemo ga

    pomicati

    PositionedString pstr;

    pstr = "Hello"; // koristimo kao obicni string

    pstr.SetPosition(2, 10); // pomiemo string

    pstr.Draw();

    ...

    Point String

    PositionedStri

    ng

  • 15. Objektno orijentirano programiranje 18

    PROBLEM VIESTRUKOG NASLIJEIVANJA

    Iako viestruko nasljeivanje moe biti vrlo korisno, u novijim programski jezicima (Java,

    C#) nije ni implementirano. Razloga su dva:

    Prvi je razlog da se njime uspostavlja dosta komplicirana hijerarhija u naslijeivanju, a

    drugi razlog je da esto moe nastati konflikt koji jer prikazan slijedeom slikom:

    Klasa D naslijeuje klase B1 i B1, koje imaju istu temeljnu klasu A.

    Poto se konstruktori ne naslijeuju 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 naslijeivanja).

    Ovaj se problem moe rijeiti koritenjem tzv. virtualnih temeljnih konstruktora, ali to

    ovdje nee biti objanjeno.

    Savjet: Viestruko nasljeivanje mnogi programeri zovu goto of the 90s.

    Ne preporuuje se njegova upotreba.

    A

    B1 : A B2 : A

    D :

    B1,B2

  • 15. Objektno orijentirano programiranje 19

    Polimorfizam

    Polimorfizam je svojstvo promjenjljivosti 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 svrhum, u C++ jeziku je implementiran mehanizam virtuelnih

    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 virtuelnih funkcija povezivanje s objektom vri

    tijekom izvrenja programa (tzv. izvrno povezivanje run-time binding).

  • 15. Objektno orijentirano programiranje 20

    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 naslijeivanja. 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:

    //.. koristimodefinicije klasa Poligon, Pravokutnik i Trokut

    void IspisDimenzija(Poligon & p)

    {

    cout

  • 15. Objektno orijentirano programiranje 21

    int main ()

    {

    Pravokutnik pr;

    Trokut tr;

    pr.init (4,5);

    tr.init (4,5);

    cout

  • 15. Objektno orijentirano programiranje 22

    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.

    U sljedeem primjeru pokazano je kako se pomou pokazivaa na temeljnu klasu pristupa

    objektima izvedenih klasa.

    int main ()

    {

    Pravokutnik pravokutnik;

    Trokut trokut;

    Poligon * pPol1 = &pravokutnik;

    Poligon * pPol2 = &trokut;

    pPol1->init (4,5);

    pPol2->init (4,5);

    cout

  • 15. Objektno orijentirano programiranje 23

    15.2.2. Virtuelne 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 virtuelno, definirati u temeljnoj

    klasi . To se postie tako da se u temeljnoj klasi deklarira funkcija Povrsina s prefiksom

    virtual.

    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 24

    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 Povrsina()

  • 15. Objektno orijentirano programiranje 25

    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 virtuelana tada se i sve funkcije istog imena u izvedenim klasama tretiraju kao virtuelne funkcije.

    u izvedenim se klasama ispred imena virtuelne funkcije moe napisati specifikator virtual, iako to nije nuno.

    poziv virtuelnih funkcija vri se drukije nego poziv regularnih lanskih funkcija.

    Za svaki objekt koji ima virtuelne funkcije kompajler generira posebnu tablicu (V-tablicu)

    u koju upisuje adresu virtuelnih funkcija, takoer uz lanove klase zapisuje i pokaziva na

    ovu tablicu (vptr). To prikazuje slijedea slika:

    objekt pravokutik V-tablica za pravokutnik

    objekt trokut V-tablica za trokut

    vptr

    width

    height

    Pravokutnik::Povrs

    ina

    vptr

    width

    height

    Trokut::Povrsina

    .......

  • 15. Objektno orijentirano programiranje 26

    Objasnimo nain na koji se prepoznaje i vri poziv virtuelne funkcije:

    1. pretpostavimo da je adresa nekog objekta pridjeljena nekom pokazivau (ili je

    referenca)

    2. kada se treba izvriti poziv virtuelne funkcije najprije se dobavlja adresa tablice

    pokazivaa virtuelnih 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.

    Iz ovog objanjenja proizilazi da je izvrenje programa s virtuelnim funkcijama sporije

    nego izvrenje programa s regularnim funkcijama, jer se gubi vrijeme za dobavu adrese

    virtuelne funkcije. Bez obzira na ovu injenicu rad s virtuelnim funkcijama je od velike

    koristi jer se pomou njih postie potpuni polimorfizam, a to je najvaniji element objektno

    orjentiranog programiranja.

    Zapamti: virtuelne funkcije nam slue za potpunu primjenu polimorfizma. To

    omoguuje da se programska rjeenja lako prilagouju razliitim objektima iz

    hijerarhije naslijeivanja.

  • 15. Objektno orijentirano programiranje 27

    15.2.3. Apstraktne temeljne klase

    Apstraktne temeljne klase su klase u kojima je definirana bar jedna ista virtuelna

    funkcija.

    ista virtuelna funkcija se oznaava tako da se iza deklaracije funkcije napie = 0, tj

    Sintaksa iste virtuelne funkcije:

    virtual deklaracija_funkcije = 0;

    Klase koje sadre iste virtuelne 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;

    };

    jer nije predvieno da se njome deklarira statike objekte.

  • 15. Objektno orijentirano programiranje 28

    Napomena: Kompajler ne generira izvrni kod za iste virtuelne 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 virtuelne 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 Povrsina()

  • 15. Objektno orijentirano programiranje 29

    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);

    pPol1->PrintPovrsina();

    pPol2->PrintPovrsina();

    return 0;

    }

    Uoite da je u funkciji

    void PrintPovrsina (void)

    { cout Povrsina()

  • 15. Objektno orijentirano programiranje 30

    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; // ponovo koristi pokaziva

    pPol->init(4,5);

    pPol->PrintPovrsina();

    delete pPol;

    return 0;

    }

    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 31

    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 virtuelna funkcija.

    Redoslijed poziva konstruktora i destruktora se moe analizirati pomou programa

    inherit8.cpp.

    #include

    using namespace std;

    class Superclass {

    public:

    Superclass (){cout

  • 15. Objektno orijentirano programiranje 32

    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.

    Zapamtite: Uvijek je korisno deklarirati funkcije temeljne klase kao virtuelne

    funkcije. Time se omoguuje polimorfizam klasa. Destruktor temeljne klase treba

    deklarirati kao virtuelnu funkcija.

    Kada se kasnije u radu pokae da neku lansku funkciju nije potrebno koristiti

    polimorfno, tada se funkcija moe deklarirati kao nevirtuelna 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

    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 zapoima 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.

    *

    - +

    3 4 5

  • 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.

    *

    +

    3 4

    -

    5

  • 15. Objektno orijentirano programiranje 36

    Pokazat emo rjeenje u kojem se za sve tipove vorova koristi temeljna virtuelna klasa

    ExprNode, a pomou nje emo definirati klase BinaryExprNode, UnaryExprNode i

    NumNode.

    class ExprNode // temeljna klasa za sve tipove izraza

    {

    friend ostream& operator

  • 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

  • 15. Objektno orijentirano programiranje 38

    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

    { return (op=='-')? -oprnd->execute() : oprnd->execute();}

    void print (ostream& out) const

    { out

  • 15. Objektno orijentirano programiranje 39

    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 execute() * right->execute();

    case '/': // provjeri dijeljenje s nulom

    { double val = right->execute();

    if(val != 0)

    return left-> execute() / val;

    else

    return 0;

  • 15. Objektno orijentirano programiranje 40

    }

    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.

    Testiranje 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

  • 15. Objektno orijentirano programiranje 41

    Uoimo da se svi vorovi alociraju dinamiki. Poima 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 virtuelne 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 42

    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

  • 15. Objektno orijentirano programiranje 43

    Ovdje su prikazani elementi objektno orjentiranog programiranja na jednostanim

    primjerima. U praksi se objektno orjentirano 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 programiranjeNasljeivanje15.1.2 Kada koristimo nasljeivanje15.1.3 Public, private i protected specifikatorito se sve nasljeuje iz temeljne klaseNadreenje lanskih funkcijaNasljeivanje generikih klasaViestruko nasljeivanje

    Polimorfizam15.2.1. Is a odnos objekata15.2.2. Virtuelne lanske funkcije15.2.3. Apstraktne temeljne klase15.2.4. Virtualni destruktor15.2.5. Polimorfizam na djelu izvrenje aritmetikih izraza

    - .-