Transcript
Page 1: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

364

Primer. Jedna funkcija se prenosi u drugu funkciju kao argument pomoću pointera. float abs(float x) { return((x<0) ? -x : x); } float fun(float x) { return(1./(x*x+1.)) } float koren(float (*pf)(float), float arg) { float q=1.0, e=.001, x; x=(*pf)(arg); while(abs(q*q-x)>=e) q=(x/q+q)/2; return(q) } void main() {static float x[]={1.1,2.2,3.3,4.4}; int i; printf("\n"); for(i=0; i<4; ++1) printf("Kvadratni koren fun(%.3f)=%.3f je %.3f\n", x[i], fun(x[i]), koren(fun,x[i])); }

Primer. Konstruisati funkciju F(h) = Q(x(k) +h.s(k)) za zadatu funkciju Q(x), zadati vektor xk = x(k), zadati pravac s = s(k) i parametar koraka h. Konstrukcija ovakve funkcije predstavlja poznat problem optimization along a line. float fun1(float *x) { int i; float f=0.0; for(i=0; i<3; i++)f+=(x[i]-1.0)*(x[i]-1.0); return(f); } float LineFun(float(*fun)(),float *xk,float *s,int n,float h) { int j; float xt[50],f; for(j=0; j<n; j++)xt[j]=xk[j]+h*s[j]; f=(*fun)(xt); return(f); } void main() { float r, x[50], s[50]; int i,n; scanf("%d", &n); for(i=0;i<n;i++)scanf("%f",&x[i]); for(i=0;i<n;i++)scanf("%f",&s[i]); scanf("%f", &h); r=LineFun(fun1,x,s,n,h); printf("%f\n", r); }

9. Objektno-orijentisano programiraje 9.1. Osnovni pojmovi

OOP uvodi drugačiji način razmišljanja u programiranju! U OOP-u, mnogo više vremena troši se na projektovanje, a mnogo manje na samu implementaciju (kodovanje). U OOP-u se razmišlja o delovima sistema (objektima) koji nešto rade, a ne o tome kako se nešto radi (algoritmi). Drugim

Page 2: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

365

rečima, OOP prvenstveno koristi objektnu dekompoziciju umesto isključivo algoritamske dekompozicije.

U osnovi objektno-orijentisanog programiranja leži ideja da se u jednoj strukturi podataka objedine i podaci i akcije koje se sprovode nad tim podacima (to su metodi u terminologiji OOP). Ključni pojmovi u objektno-orijentisanim programskim jezicima jesu:

- klase, - objekti, i - poruke.

Klasama se opisuje struktura i ponašanje pojedinačnih primeraka klase. Pojedinačni primerci (instance) klase, nazivaju se objekti. Svi objekti koji pripadaju jednoj klasi imaju strukturu koja je definisana klasom kojoj pripadaju, i nad njima se mogu izvršavati samo operacije koje su definisane tom klasom. Klase se mogu posmatrati kao šabloni za kreiranje novih objekata. U tom smislu je pojam klase sličan pojmu tipa. Pripadnost klasi je svojstvo objekata, dok je pripadnost tipu svojstvo promenljivih i izraza. Svaka klasa se može posmatrati kao celina u kojoj su pored polja u kojima su opisani podaci (svojstva) opisana i polja koja predstavljaju funkcije i/ili procedure. Ovakve funkcije, odnosno procedure, definisane unutar klasa, nazivaju se metodi klase.

Svojstva objekata menjaju se pozivom odgovarajućih metoda. Pozivi metoda nazivaju se poruke. Objekat odgovara na poruku tako što izvršava odgovarajući metod. Sintaksa poruka zavisi od konkretnog programskog jezika, ali je generalno slična pozivu procedura i funkcija u proceduralnim programskim jezicima.

Program u proceduralnim programskim jezicima se može posmatrati kao skup potprograma (procedura i funkcija) kojima se obrađuju podaci, dok program u objektno-orijentisanim programskim jezicima predstavlja skup objekata koji deluju jedni na druge slanjem poruka.

Objektno-orijentisani programski jezici se mogu implementirati statički i dinamički. Dinamičku implementaciju poseduju netipizirani jezici (kakav je na primer SMALLTALK). U ovakvim programskim jezicima ne koristi se informacija o tipovima podataka, već samo informacija o pripadnosti objekta određenoj klasi. Informacija o pripadnosti objekta određenoj klasi koristi se tek u fazi izvršavanja programa. Većina objektno-orijentisanih jezika je implementirana statički. Kod ovakvih programskih jezika, informacija o pripadnosti objekta određenoj klasi koristi se u fazi kompilovanja programa, na sličan način kao što se kod proceduralnih programskih jezika koristi informacija o tipu promenljive.

Za razumevanje klasa i metoda veoma su važni sledeći pojmovi: - apstraksija i enkapsulacija, - nasleđivanje, i - polimorfizam.

Tip koji modeluje neki pojam iz problema koji se rešava pomoću (neodvojive veze) podataka i pridruženih operacija naziva se apstraktni tip podataka (abstract data type). Sakrivanje interne reprezentacije tipa, tj. detalja njegove implementacije od korisnika tog tipa naziva se enkapsulacija (encapsulation). Apstrakcija i enkapsulacija se mogu realizovati pomoću klase.

Nasleđivanje je mogućnost korišćenja već definisanih klasa za građenje hijerarhije klasa. Svaki “naslednik” nasleđuje opise podataka “praroditelja” kao i metode njihove obrade.

Polimorfizam predstavlja mogućnost definisanja jedinstvenog po imenu metoda (procedure ili funkcije), koji je primenljiv istovremeno na svim klasama hijerarhije nasleđivanja, pri čemu svaka klasa hijerarhije može naznačiti specifičnosti realizacije kada se metod primenjuje nad “njom samom”.

Klase i članovi Klasa je kolekcija promenljivih, često različitih tipova, koji su kombinovani sa skupom povezanih

funkcija.

Jedan način da se razmišlja o automobilu je kao o točkovima, vratima, sedištima, prozorima i tako dalje. Drugi način je da razmišljate o tome šta automobil može da uradi: da se pomera, ubrza, uspori,

Page 3: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

366

stane, parkira i tako dalje. Klasa omogućava da enkapsulišete (zamotate) ove različite delove i različite funkcije u kolekciju, koja se naziva objekat.

Enkapsuliranje svega što znate o automobilu u jednu klasu ima nekoliko prednosti za programera. Sve se nalazi na jednom mestu, što olakšava manipulisanje podacima. Isto tako, klijenti neke klase, to jest, delovi programa koji koriste tu klasu, mogu koristiti objekat klase a da se, pri tom, ne brinu šta se nalazi u njemu, ili kako on radi.

Promenljive članice, takođe poznate kao podaci članovi, jesu članovi u klasi koje upućuju na promenljive u klasi. Klasa Auto bi mogla imati promenljive članice koje predstavljaju sedista, tip radija, gume i tako dalje.

Funkcije članice, takođe poznate kao metodi, jesu funkcije u klasi. Funkcije članice su deo klase isto koliko i promenljive članice. One određuju šta objekti klase mogu uraditi. Metodi klase Auto bi mogle uključiti Start() i Brake() (engl. start = kreni, brake = zakoči).

9.1.1. Nasleđivanje – osnovni pojmovi

Nove klase mogu biti definisane tako da naslede sva svojstva određene klase (roditeljska klasa), ali i da se tim svojstvima pridodaju nova.

Neka je definisana klasa C pomoću strukture koja je definisana promenljivom x i metodom m1. Neka su C1 i C2 dva objekta klase C. Iz klase C izvedena je nasleđivanjem klasa N. Neka su N1 i N2 objekti klase N. Objekti N1 i N2 imaju sve elemente koji su neohodni za definiciju klase C. U ovom slučaju to znači da objekti N1 i N2 jesu strukture koje imaju promenljivu x i metod m1. Međutim, u definiciji klase N mogu se definisati još neki elementi. Na primer, neka u definiciji klase N učestvuju i promenljiva y kao i metod m2. To znači da objekti N1 i N2, kao primerci klase N, poseduju strukturu koja je definisana pomoću dve promenljive x i y kao i dva metoda m1 i m2.

Prednosti upotrebe nasleđivanja Podklase obezbeđuju specijalizovana ponašanja na osnovu zajedničkih elemenata sa

superklasom. Upotrebom tehnike nasleđivanja, programeri mogu da koriste kôd iz superklasa mnogo puta.

Programeri mogu da implementiraju superklase koje se nazivaju apstraktne klase (abstract classes) koje definišu generička ponašanja ("generic" behaviors). Apstraktne superklase definišu i mogu parcijalno da implementiraju ponašanje podklasa. Drugi programeri postavljaju detalje u specijalizovane klase.

9.1.2. Polimorfizam Objekti međsobno utiču jedni na druge pomoću poruka. Međutim, normalno je da različiti objekti

ne reaguju na isti način na dobijene poruke. Ovakva sposobnost različitih objekata da na različit način odgovore na istu poruku naziva se polimorfizam.

Neka su dati objekti a, b, c različitih tipova. Neka se za prikazivanje ovih objekata koristi se metod display, koji se poziva pomoću izraza

a.display, b.display, c.display.

S obzirom da su objekti a, b i c različitih tipova, moramo raspolagati mehanizmom koji će omogućiti da se metod display u svakom slučaju prilagodi strukturi objekta koji se prikazuje. U tom slučaju, objekti a, b i c će različio reagovati na istu poruku koja je dobijena pozivom procedure display. Objektno-orijentisani programski jezici raspolažu takvim mehanizmom. Jasno je da ovakav mehanizam podrazumeva da se informacija o pripadnosti objekta određenom tipu koristi tek u fazi izvršavanja programa, tj. pravi polimorfizam zahteva dinamičku implementaciju jezika. Međutim, statička implementacija je češća, i koristi se kod svih jezika sa jakom tipizacijom. Zbog toga se polimorfizam u statički implementiranim programskim jezicima implementira pomoću virtuelnih metoda za koje važi dinamička implementacija. Takav je slučaj sa programskim jezicima C++ i DELPHI. Napomenimo da koncept dinamičke implementacije programskih jezika obično slabi

Page 4: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

367

njihovu pouzdanost, posebno kada se zahteva rad u realnom vremenu (ne zna se unapred vreme izvršavanja metoda).

9.2. C++ kao objektna nadgradnja jezika C C++ je trenutno najpopularniji objektno orijentisani programski jezik. Jezik C++ nije čisti objektno

orijentisani programski jezik (engl. Objektno orijentisano programiranje Language, OOPL) koji bi korisnika naterao da ga koristi isključivo na objektno orijentisani način. C++ može da se koristi i kao malo bolji C, ali se time ništa ne dobija. C++ treba koristiti kao sredstvo za OOP i kao smernicu za razmišljanje. C++ ne sprečava pisanje loših programa, već samo omogućava pisanje mnogo boljih programa.

9.2.1. Klase u jeziku C++

U jeziku C++ klasa predstavlja tip podataka. Deklarisanjem klase definišete novi tip. Klasa predstavlja strukturu u koju su grupisani podaci i funkcije. Definicija klase se sastoji u navođenju svih članova klase.

Namena. Definiše korisnički tip ili klasu koja može sadržati i promenljive i funkcije.

Sintaksa. class ime [: deklaracija_osnovne_klase] { deklaracija

... public: deklaracija ... protected: deklaracija ... private: deklaracija ...

} [definicije_objekata];

Uglaste zagrade pokazuju da su deklaracije osnovne klase i definicije objekata opcioni. Objekti jesu pojedinačne pojave (instance) klase. Deklaracija osnovne klase se koristi u slučaju kada se definiše klasa nasleđivanjem podataka i metoda te klase. Efekat ključne reči class je isti kao učinak rezervisane reči struct, izuzev što je pristup članovima klase podrazumevano privatan. Unutar neke od deklaracija mogu biti deklaracije promenljivih kao i/ili deklaracije funkcija. Podaci koji su deo klase nazivaju se podaci članovi klase (data members). Funkcije koje su deo klase nazivaju se funkcije članice klase (member functions) ili metodi klase.

Primeri koji se koriste u ovom odeljku nisu kompletni. Iz realizacije primera izbačeno je sve što bi smanjivalo preglednost osnovnih ideja.

Deklarisanje klase Da biste deklarisali klasu, upotrebite ključnu reč class, praćenu imenom klase i otvorenom velikom

zagradom. Između velikih zagrada se navodi lista podataka članova i metoda klase. Završite deklaraciju zatvarajućom zagradom i znakom tačka-zarez. Evo deklaracije klase koja je nazvana Cat:

class Cat { unsigned int starost;

unsigned int tezina; void Miau();

};

Deklaracija ove klase ne alocira memoriju za klasu Cat. Ono samo saopštava kompajleru strukturu klase Cat: podatke koje sadrži (starost i tezina) i metode klase (funkcija Miau()). Ova deklaracija

Page 5: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

368

takođe saopštava kompajleru koliko memorijskog prostora kompajler mora da rezerviše za svaki objekat klase Cat koji se kreira. U ovom primeru, ako celobrojna vrednost zahteva dva bajta, klasa Cat zauzima samo četiri bajta: polje starost zauzima dva bajta, dok polje tezina zahteva druga dva bajta. Funkcija Miau() ne zauzima prostor, jer se za funkcije članice (metode) ne rezerviše memorijski prostor.

Primer. Definicija klase kojom se opisuje tačka u dvodimenzionalnom prostoru. enum Boolean {False, True};

class Point { float X,Y; Boolean Vidljivost; // podaci public: float GetX() {return X;} float GetY() {return Y;} // metodi }

Pored podataka X,Y i Vidljivost koji su karakteristični za strukture, članovi klase Point jesu i dve funkcije GetX() i GetY(), kojima se može pristupati objektima klase Point. Funkcije GetX() i GetY() predstavljaju metode klase Point. Podacima X i Y kod objekata koji pripadaju klasi Point može se pristupiti pomoću metoda GetX() i GetY().

U ovom slučaju, funkcije članice klase su definisane u okviru deklaracije klase. Takve funkcije su umetnute (implementirane inline).

Primer. Program koji odgovara inline implementaciji klase Point: #include<iostream.h> enum Boolean{False, True}; class Point { float X,Y; Boolean Vidljivost; public: float GetX() {return X; } float GetY() {return Y; } Boolean GetVidljivost() {return Vidljivost; } void SetX(float a) {X=a; } void SetY(float b) {Y=b; } void SetVidljivost(Boolean T) {Vidljivost = T; } }; void main() { Point Centar; float a,b; Boolean T; cin>>a>>b; Centar.SetX(a); Centar.SetY(b); cout<<"Centar.X= "<<Centar.GetX()<<" Centar.Y= " <<Centar.GetY()<<endl; T=True; Centar.SetVidljivost(T); if(Centar.GetVidljivost()==True) cout<<"Tacka je vidljiva\n"; else cout<<"Tacka nije vidljiva\n"; }

Tela pridruženih funkcija (metoda) mogu da budu definisana i izvan definicije klase. Tada se u takvoj definiciji klase navode samo zaglavlja metoda. U tom slučaju se prilikom definisanja metoda klase mora izvršiti eksplicitno pridruživanje određene funkcije klasi. Ovakvo pridruživanje je

Page 6: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

369

neophodno i u slučaju kada se različitim klasama pridružuju istoimeni metodi sa jednakim brojem istoimenih parametara istog tipa.

Funkcija se pridružuje određenoj klasi prema sledećoj sintaksi: tip ime_klase :: ime_pridružene_funkcije (lista argumenata) telo_pridružene_funkcije

Klasa Point je mogla da bude definisana i na sledeći način:

class Point { float X,Y; Boolean Vidljivost; public: float GetX(); float GetY(); };

Tada se pridružene funkcije definišu odvojeno:

float Point :: GetX() {return X;} float Point :: GetY() {return Y;}

Ovako definisana klasa se koristi slično definisanim tipovima. Objekti tipa Point se mogu definisati kao i promenljive strukturnog tipa:

Point Centar; // Jedan objekat klase Point Point Vrsta[80] // Niz tacaka

Primer. Program koji odgovara ovakvoj implementaciji klase Point: #include<iostream.h> enum Boolean{False, True}; class Point { float X,Y; Boolean Vidljivost; public: float GetX(); float GetY(); Boolean GetVidljivost(); void SetX(float a); void SetY(float b); void SetVidljivost(Boolean T); }; float Point::GetX() {return X; } float Point::GetY() {return Y; } Boolean Point::GetVidljivost() {return Vidljivost; } void Point::SetX(float a) {X=a; } void Point::SetY(float b) {Y=b; } void Point::SetVidljivost(Boolean T) {Vidljivost = T; } void main() { Point Centar; float a,b; Boolean T; cin>>a>>b; Centar.SetX(a);

Page 7: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

370

Centar.SetY(b); cout<<"Centar.X= "<<Centar.GetX()<< " Centar.Y= "<<Centar.GetY()<<endl; T=True; Centar.SetVidljivost(T); if(Centar.GetVidljivost()==True) cout<<"Tacka je vidljiva\n"; else cout<<"Tacka nije vidljiva\n"; }

Primer. Deklaracija klase osoba. class Osoba { public:

void koSi(); /* metod */ private:

char *ime; /* podatak: ime i prezime */ int god; /* podatak: koliko ima godina */

};

Funkcija koSi() iz klase se mora i definisati. void Osoba::koSi() { cout<<”Ja sam "<<ime<<" i imam "<<god<<" godina.\n"; }

Primer. U sledećem kôdu deklariše se klasa CIoStr izvedena iz osnovne klase CStr. Ovom deklaracrjom se deklariše i nekoliko objekata (str1, str2 i str3). Član status je privatan, jer su članovi klase podrazumevano privatni.

Class CIoStr : public CStr { int status; public: void input(void); void output(void); } str1, str2, str3;

U ovom slučaju, definicije funkcija članica input i output moraju biti obezbeđene kasnije.

Klase i objekti Klasom se definiše novi, korisnički tip za koji se mogu formirati instance (primerci, objekti). Klasa

može da predstavlja realizaciju apstrakcije iz domena problema. Klasa predstavlja šablon za generisanje objekata, a objekti se nazivaju pojedinačni primerci (instance) klase.

Svaki objekat ima sopstvene elemente koji su navedeni u deklaraciji klase. Ovi elementi klase nazivaju se članovi klase (class members). Članovima klase se pristupa pomoću operatora ”.” (tačka):

Objekat novog tipa se definiše kao i bilo koja promenljiva: unsigned int Weight; // definise neoznacenu celobrojnu promenljivu Cat Frisky; // definise Cat

Ovaj kôd definiše promenljivu nazvanu Weight, čiji tip je neoznačen ceo broj. On, takođe, definiše objekat Frisky, čija je klasa (ili tip) Cat.

Definicija mačke nije nikada ljubimac: individualne mačke su ljubimci. Postoji razlika između mačke kao ideje i određene mačke. Na isti način, C++ pravi razliku između klase Cat, koja je ideja mačke, i svakog individualnog Cat objekta. Objekat Frisky je tipa Cat, na isti način na koji je Weight promenljiva tipa unsigned int.

Primer. Korišćenje klase Osoba. Negde u programu se definišu objekti tipa Osoba. Osoba Pera, mojOtac, direktor;

Zatim se ti objekti mogu koristiti: Pera.koSi(); /* poziv funkcije koSi objekta Pera */ Prijatelj.koSi(); /* poziv funkcije koSi objekta mojOtac */

Page 8: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

371

direktor.koSi(); /* poziv funkcije koSi objekta direktor */

Specifikator Public: govori prevodiocu da su samo članovi i metodi koji se nalaze iza njega dostupni spoljašnjim funkcijama. Ovi članovi se nazivaju javnim. Članovi iza specifikatora private: su nedostupni funkcijama izvan klase (ali ne i članovima klase), i nazivaju se privatnim. Sledeća dodeljivanja vrednosti podacimačlanovima nisu dozvoljena izvan funkcija članica klase:

Pera.ime = ”Petar Markovic”; /* nedozvoljeno */ Prijatelj.god = 55; /* takođe nedozvoljeno*/

Javni, zaštićeni i privatni članovi klase Članovi (podaci ili funkcije) klase iza ključne reči private: zaštićeni su od pristupa spolja. Ovim

članovima mogu pristupati samo funkcije članice same klase. Ovi članovi nazivaju se privatni članovi klase (private class members).

Članovi klase iza ključne reči public: dostupni su spolja i nazivaju se javnim članovima klase (public class members). Javnim članovima se može pristupiti koristeći bilo koji objekat klase.

Članovi iza ključne reči protected: dostupni su funkcijama članicama date klase kao i funkcijama članicama klasa koje su izvedene iz te klase, ali nisu dostupni funkcijama izvan klase. Ovakvi članovi se nazivaju zaštićeni članovi klase (protected class members).

Redosled sekcija public, protected i private proizvoljan je, ali se preporučuje baš navedeni redosled. Podrazumeva se da su članovi klase privatni (ako se ne navede specifikator ispred).

Objekat unutar svoje funkcije članice može pozivati funkciju članicu neke druge ili iste klase, odnosno može uputiti poruku drugom objektu. Objekat koji šalje poruku (poziva funkciju) naziva se objekat-klijent, a onaj koji je prima je objekat-server.

Podrazumevano je da su privatni svi članovi klase, podaci i metodi. Uočimo poznati primer: class Cat { unsigned int starost;

unsigned int tezina; Miau();

}

U ovoj deklaraciji, podaci starost, visina i Miau() su privatni, zato što su svi članovi klase podrazumevano privatni. Zbog toga u sledećem kodu kompajler prijavljuje grešku: Cat Boots; Boots.starost=5; //greska! ne mozete pristupiti privatnim podacima! Boots.Miau(); //greska, privatna funkcija članica!

U stvari, rekli ste prevodiocu: "članovima starost, tezina i Miau() može se pristupati samo iz funkcija članica klase Cat." Ali ovde je pokušan pristup privatnom članu starost objekta Boots izvan metode klase Cat.

Deklaracija klase Cat se može promeniti tako da se može pristupiti njenim članovima i funkcijama koje su definisane izvan te klase. Potrebno je da ti podaci budu deklarisani kao javni:

class Cat { public:

unsigned int starost; unsigned int tezina; Miau();

};

Sada su svojstva starost, tezina, kao i metod Miau() javni. Sada se izraz Boots.starost = 5 prevodi bez problema.

Primer. Deklarisati klasu koja opisuje položaj tačke i sledeće operacije: translaciju, centralnu simetriju, rotaciju, ispis koordinata tačke. Funkcije-članove klase napisati kao inline funkcije. Napisati test program.

Page 9: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

372

#include <iostream.h> #include<math.h> class tacka { float x, y; // podaci - podrazumevajuce skriveni (private) public: // javni deo - dostupan u svim delovima programa void inic(float a,float b) { x=a; y=b; } void translacija(float dx, float dy) { x+=dx; y+=dy; } void pozicija() { cout << "x=" << x << " " << "y=" << y << endl; } void simetrija() { x=-x; y=-y;} void rotacija(float a) { float xr,yr; xr=x*cos(a)-y*sin(a); yr=y*cos(a)+x*sin(a); x=xr; y=yr; } }; void main() { tacka t1; float x1,y1; const float pi=3.14159; cout << "Unesite X - koordinatu tacke - ->"; cin >> x1; cout << "Unesite Y - koordinatu tacke - ->"; cin >> y1; // inicijalizacija objekta koji reprezentuje tacku t1.inic(x1,y1); t1.translacija(5.5,5.5); // translacija tacke t1.pozicija(); //ispis polozaja tacke t1.simetrija(); t1.pozicija(); t1.rotacija(pi/2); t1.pozicija(); }

Učinite podatke članove privatnim Opšte je pravilo dizajna da podaci članovi klase budu deklarisani kao privatni, osim ukoliko

postoje jaki razlozi za suprotnu odluku. Javni članovi bi trebalo da budu samo funkcije članice. Zato se moraju kreirati javne funkcije, poznate kao metodi pristupa, za postavljanje vrednosti i pristup vrednostima privatnih promenljivih. Ovi metodi pristupa su funkcije članice, koji se pozivaju iz drugih delova programa da bi se dobile ili postavile vrednosti privatnih promenljivih.

Zaštićene su obično jednostavne operacije klase koje ne predstavljaju operacije interfejsa date klase, nego su pomoćne funkcije koje služe za implementaciju javnih operacija, i koriste se više puta. Ovakve jednostavnije operacije su pomoćne funkcije (helper functions), koje često nisu potrebne u izvedenim klasama, pa su zbog toga zaštićene.

Unutar funkcije članice (metoda) klase, članovima objekta čija je funkcija pozvana pristupa se direktno, samo navođenjem imena tih članova.

Privatnost nije svojstvo objekta, nego klase: jedan objekat neke klase iz svojih funkcija članica može pristupiti privatnim članovima nekog drugog objekta iste klase. Međutim, objekti neke druge klase nemaju mogućnost direktnog pristupa tim članovima.

Moguće je preklopiti (overload) funkcije članice, uključujući i konstruktore.

Primer. Data je deklaracija klase kojom se opisuju kompleksni brojevi. class complex // deklaracija klase complex { public:

void cAdd(complex); void cSub(complex);

Page 10: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

373

float cRe(); float cIm(); //...

private: float real, imag;

};

Gore navedena deklaracija je, zapravo, definicija klase, ali se iz istorijskih razloga naziva deklaracijom.

Pravu deklaraciju klase predstavlja samo deklaracija oblika class <ImeKlase>, u ovom slučaju class complex. Pre prave deklaracije mogu samo da se definišu pokazivači i reference na tu klasu, ali ne i objekti te klase, jer se njihova veličina ne zna.

Pristup članovima klase Kada neki stvarni objekat klase Cat bude definisan, na primer Frisky, tada se operator tačka (piše

se .) koristi za pristup članovima tog objekta (odnosno članovima klase kojoj objekat pripada). Na primer, u cilju dodele vrednosti 5 promenljivoj tezina, koja je članica objekta Frisky, piše se

Frisky.tezina = 5;

Slično kao i podaci, i metodi klase se pozivaju koristeći objekte te klase. U sledećem primeru poziva se metod Miau() za objekat Frisky:

Frisky.Miau();

Primer. Sledeći listing prikazuje deklaraciju klase Cat sa javnim promenljivim članicama. 1: // Demonstrira deklaraciju klase i 2: // definiciju objekta k1ase, 3: 4: #include <iostream.h> // zbog upotrebe funkcije cout 5: 6: class Cat // deklaracija klase 7: { 8: public: //clanovi koji slede su javni 9: int starost; 10: int tezina; 11: }; 12: 13: 14: void main() 15: { 16: Cat Frisky; 17: Frisky.starost = 5; //dodela vrednosti promenljivoj clanici 18: cout << "Frisky je macka koja ima " ; 19: cout << Frisky.starost << " godina.\n"; 20: }

Izlaz: Frisky je macka koja ima 5 godina.

Linija 6 sadrži ključnu reč Class. Ovo govori kompajleru da je ono što sledi deklaracija klase. Ime nove klase dolazi posle ključne reči class. U ovom slučaju, nova klasa je Cat.

Telo deklaracije klase počinje otvarajućom zagradom u liniji 7, a završava se zatvarajučom zagradom i znakom tačka-zarez u liniji 11. Linija 8 sadrži ključnu reč public, što pokazuje da su sve deklaracije koje slede javne, sve do reči private ili kraja deklaracije klase.

Linije 9 i 10 sadrže deklaracije članova starost i tezina klase Cat.

Linija 14 započinje glavnu funkciju programa. Objekat Frisky je definisan u liniji 16 kao primerak klase Cat. Vrednost člana starost objekta Frisky postavlja se u liniji 17. U liniji 19 promenljiva članica starost se koristi za štampanje poruke o objektu Frisky.

Page 11: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

374

Pretvorite u komentar liniju 8 i probajte da ponovo kompajlirate program. Dobićete grešku u liniji 17, jer podatak starost više neće imati javni pristup (za članove klasa podrazumeva se privatni pristup).

Za čitanje vrednosti privatne promenljive ili za postavljanje njene vrednosti koristi se javna funkcija članica klase. Zašto dosađivati ovim dodatnim nivoom indirektnog pristupa? Ipak, jednostavnije je i lakše koristiti podatke kao u strukturama, nego raditi kroz funkcije pristupa. Funkcije pristupa omogućavaju da odvojite detalje o tome kako se podaci čuvaju od toga kako se koriste. Ovo omogućava da promenite način čuvanja podataka bez potrebe za ponovnim pisanjem funkcija koje koriste podatke. Deklarisanje metoda ili podataka kao privatnih omogućava kompajleru da pronađe programske greške, pre nego što one postanu bagovi.

Ako odlučite da podatak tezina ima privatni pristup, odnosno da taj podatak učinite privatnim, potrebno je da se napiše funkcija koja pristupa članu tezina direktno. Ova tehnika olakšava održavanje programa, daje napisanom kôdu duži život, jer promene dizajna ne prouzrokuju zastarevanje programa.

Implementacija metoda klase Kao što je rečeno, funkcija pristupa obezbeđuje javni interfejs za privatne podatke članove klase.

Svaka funkcija pristupa kao i svaki drugi metod klase koji deklarišete, mora imati implementaciju. Implementacija se naziva definicija funkcije. Definicija funkcije članice počinje imenom klase, posle čega sledi zapis ::, zatim ime funkcije i njeni parametri. Kao i kod ostalih funkcija, telo metoda se piše između velikih zagrada.

Primer. Sledeći listing prikazuje kompletnu deklaraciju i upotrebu jednostavne klase Cat sa privatnim podacima članovima.

1: // Demonstrira deklaraciju klase i 2: // definiciju metoda klase 3: 4: #include <iostream.h> //zbog funkcije cout 5: 6: class Cat // pocinje deklaraciju klase 7: { 8: public: // zapocinje javnu sekciju 9: int DajStarost(); // funkcija pristupa 10: void PostaviStarost(int godine); // funkcija pristupa 11: void Miau(); // opsta funkcija 12: private: // pocinje privatna sekcija 13: int starost; // promenljiva clanica 14: }; 15: 16: // javna funkcija pristupa DajStarost 17: // vraca vrednost clana starost 18: int Cat::DajStarost() 19: { 20: return starost; 21: } 22: 23: // definicija funkcije PostaviStarost, 24: // javna funkcija pristupa 25: // postavlja clan starost 26: void Cat::PostaviStarost(int godine) 27: { 28: // postavi promenljivu clanicu starost na 29: // vrednost koja je predata u parametru godine 30: starost = godine; 31: } 32: 33: // definicija metoda Miau()

Page 12: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

375

34: // vraca void 35: // parametri: Nema 36: // akcija: Na ekranu stampa "mijau" 37: void Cat::Miau() 38: { 39: cout << "Mijau.\n"; 40: } 41: 42: // kreira objekat klase Cat, postavlja njegovu starost, 43: //pravi miauk, saopstava starost, zatim ponovo pravi mijauk 44: int main() 45: { 46: Cat Frisky; 47: Frisky.PostaviStarost(5); 48: Frisky.Miau(); 49: cout << "Frisky je macka koja ima "; 50: cout << Frisky.DajStarost() << " godina.\n"; 51: Frisky.Miau(); 52: return 0; 53: }

Izlaz: Mijau. Frisky je macka koja ima 5 godina, Mijau.

Linije 6-14 sadrže definiciju klase Cat. Linija 8 sadrži ključnu reči public, što govori kompajleru da je ono što sledi skup javnih članova. Linija 9 ima deklaraciju javnog metoda pristupa DajStarost(). Metod DajStarost() obezbeđuje pristup privatnoj promenljivoj članici starost, koja je deklarisana u liniji 13. Linija 10 jeste prototip javne funkcije pristupa PostaviStarost(), koja prihvata celobrojnu vrednost kao argument i postavlja svojstvo starost objekta na vrednost tog argumenta.

Linija 11 deklariše metod klase Miau(). Metod Miau() nije funkcija pristupa, već opšti metod koji na ekranu štampa reč "Mijau".

Linija 12 započinje privatnu sekciju, koja uključuje samo deklaraciju privatne promenljive članice starost u liniji 13. Deklaracija klase se završava zatvarajućom zagradom i znakom ‘;’ u liniji 14.

Linije 18-21 sadrže definiciju funkcije članice DajStarost(). Ovaj metod ne prihvata parametre: on vraća celobrojnu vrednost koja jejednaka vrednosti svojstva starost objekta. Uočite da metodi klase uključuju ime klase, praćeno zapisom :: i imenom funkcije (Linija 18). Ova sintaksa saopštava kompajleru da je funkcija DajStarost(), koju ovde definišete, ona koju ste deklarisali u klasi Cat. Posle ovakvog zaglavlja, funkcija DajStarost() ima sintaksu kao i bilo koja druga funkcija u jeziku C++.

Funkcija DajStarost() zauzima samo jednu liniju; ona vraća vrednost koja je sadržana u svojstvu starost. Uočite da funkcija main() ne može pristupiti promenljivoj starost, jer je osobina starost privatna za klasu Cat. Funkcija main() ima pristup javnom metodu DajStarost(). Ovaj pristup omogućava funkciji DajStarost() da vrati vrednost svojstva starost u funkciju main().

Linija 26 sadrži definiciju funkcije članice PostaviStarost(). Ona prihvata celobrojni parametar i postavlja vrednost promenljive starost na vrednost tog parametra u liniji 30. Zato što je član klase Cat, metod PostaviStarost() ima direktan pristup promenljivoj članici starost.

Linija 37 započinje definiciju, ili implementaciju metoda Miau() klase Cat. To je jednolinijska funkcija, koja na ekranu štampa reč "Mijau", praćenu prelazom u novi red.

Linija 44 započinje telo funkcije main(). U liniji 46 main() deklariše objekat klase Cat, po nazivu Fnsky. U liniji 47, vrednost 5 se dodeljuje promenljivoj članici starost, korišćenjem metoda pristupa PostaviStarost(). Uočite da se metod poziva korišćenjem imena objekta (Frisky) iza koga slede operator člana (označen sa .) i ime metoda (PostaviStarost()). Na isti način se poziva bilo koji drugi metod u klasi.

Page 13: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

376

Linija 48 poziva funkciju članicu Miau(), a linijia 49 štampa poruku, koristeći metod pristupa PostaviStarost(). Linija 51 poziva ponovo metod Miau() .

Doseg klase i funkcije Oblast važenja klase imaju svi članovi klase: to su imena deklarisana unutar definicije klase. Imenu koje ima oblast važenja klase, van te oblasti, može se pristupiti preko operatora:

., gde je levi operand objekat, ->, gde je levi operand pokazivač na objekat, ::, gde je levi operand ime klase

Oblast važenja funkcije imaju samo labele (za goto naredbe) labele se mogu navesti bilo gde unutar tela funkcije, a u dosegu su u celoj funkciji

Primer dosega klase class X { public: // ili: int x; // void f(); void f(); // int x; }; void X::f () {/*...*/} // :: proširenje dosega void g(){ X xx, *px; px=&xx; xx.x=0; // moze: xx.X::x; ali nema potrebe xx.f(); // moze: xx.X::f(); ali nema potrebe px->x=1; px->f(); }

9.3.2. Konstruktori i destruktori

Postoje dva načina za definisanje celobrojne promenljive. Možete definisati promenljivu, a onda joj, kasnije, u programu dodeliti vrednost. Na primer,

int tezina; // deklaracija promenljive terzina … // ovde dolazi ostatak kôda tezina = 7; // dodela vrednosti deklarisanoj promenljivoj tezina

U drugoj alternativi, možete deklarisati celobrojnu promenljivu i odmah je inicijalizovati. Na primer, int tezina = 7; // deklaracija promenljive tezina i njena inicijalizacija na 7

Inicijalizacija kombinuje deklaraciju promenljive sa dodelom njene inicijalne vrednosti. Kasnije se tako dodeljena inicijalna vrednost može promeniti. Inicijalizacija osigurava da promenljiva uvek ima vrednost koja ima značenje. Kako da inicijalizujete podatke članove klase? Klase imaju specijalnu funkciju članicu, koja je nazvana konstruktor. Konstruktor može da ima parametre, ali ne može imati povratnu vrednost, čak ni void. On je metod klase sa istim imenom kao i sama klasa.

Kao što konstruktori kreiraju i inicijalizuju objekte klase, destruktori oslobađaju memoriju, koja je alocirana. Ime destruktora se gradi dodavanjem znaka tilda (~) ispred imena klase. Destruktori ne prihvataju argumente i nemaju povratnu vrednost. Zbog toga deklaracija klase Cat može da uključi destruktor oblika ~Cat();

Podrazumevani konstruktori i destruktori Ako ne deklarišete konstruktor ili destruktor, kompajler koristi podrazumevani konstruktor ili

destruktor. Podrazumevani konstruktor i podrazumevani destruktor ne prihvataju argumente. Svi objekti moraju biti konstruisani i dealocirani, pa se ove funkcije pozivaju u odgovarajuće vreme. Da biste deklarisali objekat bez upotrebe parametara, mora da s upotrebi konstruktor bez parametara.

Na primer, sledeća deklaracija Cat Rags; // Rags ne dobija ni jedan parametar

Page 14: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

377

zahteva konstruktor oblika Cat();

Kada definišete objekat klase, poziva se odgovarajući konstruktor.

U slučaju da je konstruktor klase Cat definisan tako da prihvata dva formalna parametra, objekat te klase se može oformiti pomoću izraza oblika

Cat Frisky (5,7); Kada bi konstruktor prihvatao jedan parametar, odgovarajući izraz može da bude oblika

Cat Frisky (3);

U slučaju kada konstruktor ne prihvata parametre uopšte, izostavljate zagrade i pišete Cat Frisky ;

Ovo je izuzetak od pravila, koje zahteva da sve funkcije imaju zagrade, čak i ako ne privataju ni jedan parametar. Izraz

Cat Frisky; predstavlja poziv podrazumevanog konstruktora. On ne koristi zagrade. Nije neophodno da se koristi podrazumevani konstruktor, koji se automatski formira kompajliranjem programa. Može se napisati konstruktor bez parametara. Čak i konstruktori bez parametara mogu imati funkcijsko telo, u kome inicijalizuju svoje objekte, ili obavljaju druge poslove.

Može se definisati funkcija koja se poziva uvek kada objekat prestaje da živi. Ova funkcija se naziva destruktor. Ako deklarišete konstruktor, deklarišite i destruktor, čak i ako destruktor ne radi ništa. Iako bi podrazumevani destruktor radio ispravno, neće biti suvišno ako deklarišete sopstveni. To čini kôd jasnijim.

Primer. Sledeći program definiše klasu Cat. U njemu se koristi konstruktor Cat(int) za inicijalizaciju Cat objekta koji nije podrazumevani, i demonstrira upotrebu destruktora.

1: // Demonstrira deklaraciju konstruktora i 2: // destruktora za klasu Cat 3: 4: #include <iostream.h> //zbog funkcije cout 5: 6: class Cat // pocinje deklaracija klase 7: { 8: public: // pocinje javna sekcija 9: Cat(int InicGod); // konstruktor 10: ~Cat(); // destruktor 11: int DajStarost(); // funkcija pristupa 12: void PostaviStarost(int godine); // funkcija pristupa 13: void Miau(); 14: private: // pocinje privatna sekcija 15: int starost; // promenljiva clanica 16: }; 17: 18: // konstruktor klase Cat, 19: Cat::Cat(int InicGod) 20: { 21: starost = InicGod; 22: } 23: 24: Cat::~Cat() // destruktor, ne radi nista 25: { 26: } 27: 28: // DajStarost, Javna funkcija pristupa 29: // vraca vrednost clana starost 30: int Cat::DajStarost()

Page 15: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

378

31: { 32: return starost; 33: } 34: 35: // Definicija za DajStarost, 36: // javna funkcija prlstupa 37: 38: void Cat::PostaviStarost(int godine) 39: { 40: // postavi promenljivu clanicu starost na 41: // vrednost koja je predata u parametru godine 42: starost = godine; 43: } 44: 45: // definicija metoda Miau 46: // vraca: void 47: // parametri: Nema 48: // akcija: Na ekranu stampa "mijau" 49: void Cat::Miau() 50: { 51: cout << "Mijau.\n"; 52: } 53: 54: //kreira objekat, inicijalizuje polje starost, pravi mijauk, 55: // saopstava vrednost polja starost, i ponovo pravi mijauk 56: int main() 57: { 58: Cat Frisky(5); 59: Frisky.Miau(); 60: cout << "Frisky je macka koja ima "; 61: cout << Frisky.DajStarost() << " godina.\n"; 62: Frisky.Miau(); 63: Frisky.PostaviStarost(7); 64: cout << "Sada Frisky ima "; 65: cout << Frisky.DajStarost() << " godina.\n"; 66: return 0; 67: }

Izlaz: Mijau. Frisky je macka koja ima 5 godina. Mijau. Sada Frisky ima 7 godina.

Ovaj program je sličan prethodnom, osim što linija 9 dodaje konstruktor sa celobrojnim parametrom. Linija 10 deklariše destruktor, koji ne prihvata ni jedan parametar. Destruktori nikada ne prihvataju parametre, a ni konstruktori ni destruktori ne vraćaju vrednost, čak ni void.

Linije 19-22 prikazuje implementaciju konstruktora.

Linije 24-26 prikazuju implementaciju destruktora ~Cat(). Ova funkcija ne radi ništa, ali morate uključiti njenu definiciju ako je označite u deklaraciji klase.

Linija 58 sadrži definiciju objekta Frisky klase Cat. Vrednost 5 se predaje konstruktoru objekta Frisky. Nema potrebe da se pozove funkcija PostaviStarost(), jer je Frisky kreiran koristeći podrazumevanu vrednost 5 za promenljivu članicu starost, kao što je potvrđeno u liniji 61. U liniji 63 promenljivoj starost objekta Frisky se dodeljuje vrednost 7. Linija 65 štampa novu vrednost promenljive svojstva starost.

Napomena. Konstruktore koristite za inicijalizaciju objekata. Nemojte davati konstruktorima ili destruktorima povratnu vrednost. Nemojte davati parametre destruktoru.

Page 16: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

379

Primer. Sledeći program je nastavak prethodnog. U njemu se koriste dva konstruktora koji nisu podrazumevani: konstruktor Cat(int), ali i konstruktor Cat(int, float). Konstruktor sa jednim parametrom postavlja godine objekta, dok konstruktor sa dva parametra postavlja godine i težinu objekta koji se kreira. #include<iostream.h> class Cat { public: Cat(int InitGod); //konstruktor sa jednim parametrom Cat(int InitGod, float InitTez); //konstruktor sa dva parametra ~Cat(); //destruktor void PostaviStarost(int godine); int DajStarost(); void PostaviTezinu(float t); float DajTezinu(); void Miau(); void Grebe(); private: int starost; float tezina; }; Cat::~Cat() {cout<< "Ode macka\n"; } Cat::Cat(int InitGod) { starost=InitGod; } Cat::Cat(int InitGod, float InitTez) { starost=InitGod; tezina=InitTez; } void Cat::PostaviStarost(int godine) { starost=godine; } int Cat::DajStarost() { return starost; } void Cat::PostaviTezinu(float t) { tezina=t; } float Cat::DajTezinu() { return tezina; } void Cat::Miau() { cout<<"Miau\n"; } void Cat::Grebe() { cout<<"Jao, ogreba me\n"; } int main() { Cat Frisky(5); Frisky.Miau(); Frisky.Grebe(); cout<<"Frisky je macka koja mjauce, grebe i ima " <<Frisky.DajStarost()<<" godina\n"; Frisky.~Cat(); //eksplicitni poziv destruktora Frisky.Miau(); Frisky.PostaviStarost(7); cout<<"Frisky je macka koja mjauce, grebe i sada ima " <<Frisky.DajStarost()<<" godina\n";

Page 17: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

380

Cat Frisky2(9,4.5); cout<<"Frisky2 je macka koja mjauce, grebe" <<" Ima tezinu "<<Frisky2.DajTezinu()<<"kg i sada ima " <<Frisky2.DajStarost()<<" godina\n"; return(0); }

Izlaz na ekranu:

Miau Jao, ogreba me Frisky je macka koja mjauce, grebe i ima 5 godina Ode macka Miau Frisky je macka koja mjauce, grebe i sada ima 7 godina Frisky2 je macka koja mjauce, grebe Ima tezinu 4.5kg i sada ima 9 godina Ode macka Ode macka

Primer. Deklaracija klase Osoba. #include<iostream.h> class Osoba { public:

Osoba(char *ime, int godine); /* konstruktor*/ void koSi(); /* funkcija clanica */ private: /* privatni članovi */ char *ime; /* podatak: ime i prezime */ int god; /* podatak: koliko godina ima osoba */

};

/* Svaka funkcija se mora i definisati: */

void Osoba::koSi() {cout << "Ja sam " << ime << " i imam "<< god << " godina.\n"; }

Osoba::Osoba(char *i, int g) { ime=i; /* dodeli ime */

god=((g>=0 && g<=100) ? g:0); /*proveri godine*/ }

/* Upotreba Osoba: */ void main() { Osoba Pera("Petar Markovic", 25); /* poziv konstruktora Osoba */ Osoba Prijatelj ("Dragan Mitic", 58); Pera.koSi(); Prijatelj.koSi(); }

Ovaj program proizvodi sledeći izlaz: Ja sam Petar Markovic i imam 25 godina. Ja sam Dragan Mitic i imam 58 godina.

Još o konstruktorima Funkcija članica koja nosi isto ime kao i klasa naziva se konstruktor. Ova funkcija se uvek poziva

prilikom formiranja objekta te klase.

Konstruktor nije funkcija koja vraća rezultat. On može da ima proizvoljan broj argumenata proizvoljnog tipa. Unutar konstruktora, članovima objekta se pristupa kao i u bilo kojoj funkciji članici.

Podrazumevani konstruktor se implicitno poziva pri nastanku objekta klase, ukoliko se ne upotrebi neki od definisanih konstruktora.

Page 18: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

381

Konstruktor, kao i svaka funkcija članica, može biti preklopljen. Konstruktor koji se može pozvati bez stvarnih argumenata naziva se podrazumevanim konstruktorom.

Ukoliko u klasi nije eksplicitno deklarisan nijedan konstruktor, prevodilac implicitno generiše podrazumevani konstruktor koji je javni i koji vrši podrazumevanu inicijalizaciju objekta osnovne klase i objekata-članova (ako ih ima) pozivima njihovih podrazumevanih konstruktora.

Primer. U slučaju da su u klasi Point podaci članovi deklarisani kao javni, atributi objekta Centar tipa Point mogu se definisati na sledeći način: Point centar; Centar.X=17; Centar.Y:=42; Centar.Vidljivost=False;

U ovom slučaju je pristup podacima koji čine objekat izvršen kao i u slučaju bilo koje druge strukture podataka.

Pomoću konstruktora i destruktora eksplicitno se stvara, odnosno uništava određeni objekat.

Primer. U slučaju klase Point se može koristiti sledeći kôd: #include <iostream.h> enum Boolean {False, True}; class point { float X,Y; Boolean Vidljivost; public: // ovim funkcijama je dozvoljen pristup spolja point(float NewX = 0, float NewY = 0); // konstruktor sa podrazumevanim argumentima ~point(); // destruktor

// Ostale funkcije (metodi)

float GetX() { return X; } float GetY() { return Y; } };

point::point(float NewX, float NewY) { X = NewX; Y = NewY; Vidljivost = False; cout<<"Koordinate su prihvacene"; } point::~point() { cout << "memorija je oslobodjena\n"; }

void main() { float n,m,a,b; Boolean T;

point Tacka(5,6); //poziv konstruktira sa zadatim argumentima n=Tacka.GetX(); cout << "n dobija vrednost koordinate X,\n"; m=Tacka.GetY(); cout << "m dobija vrednost koordinate Y.\n"; cin>>a>>b; Centar.SetX(a); Centar.SetY(b); cout<<"Centar.X= "<<Centar.GetX()<< " Centar.Y= "<<Centar.GetY()<<endl; T=True; Centar.SetVidljivost(T); if(Centar.GetVidljivost()==True) cout<<"Tacka je vidljiva\n"; else cout<<"Tacka nije vidljiva\n"; }

Page 19: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

382

Primer. Realizacija klase kompleksnih brojeva. Klasa Complex bi trebalo da sadrži dva podatka člana, koji predstavljaju realni i imaginarni deo kompleksnog broja. Metodi klase su:

funkcija add(Complex), kojom se vrši sabiranje dva kompleksna broja, funkcija sub(Complex), kojom se vrši oduzimanje dva kompleksna broja, funkcija Re(), kojom se izdvaja realni deo kompleksnog broja, funkcija Im(), kojom se izdvaja imaginarni deo kompleksnog broja, funkcija Print(), koja služi za ispis kompleksnog broja, Konstruktor Complex(float real, float imag);

#include <stdio.h>

class Complex // Klasa Complex { public: Complex(float real, float imag); Complex add(Complex); Complex sub(Complex); float Re(); float Im(); void print(); private: float real,imag; }; Complex::Complex (float r, float i) {real=r; imag=i;}

Complex Complex::add (Complex c) {return Complex(real+c.real,imag+c.imag); }

Complex Complex::sub (Complex c) {return Complex (real-c.real, imag-c.imag); } float Complex::Re() {return real;} float Complex::Im() {return imag;} void Complex::print() { printf("(%f,%f)",real,imag) ; }

// Glavni program

void main() { Complex c1(3.5,-17.5), c2(-3.5,17.5), c3(0,0); c3=c1.add(c2); c1=c2.sub(c3); printf ("c1= "); c1.print(); printf ("\n") ; printf ("c2= "); c2.print(); printf ("\n") ; printf ("c3= "); c3.print(); printf ("\n") ; }

Primer. Definisati klasu razlomak koja će omogućiti osnovne operacije nad kompleksnim brojevima. Klasa razlomak sadrži sledeća polja:

dva privatna podatka – celi brojevi brojilac i imenilac, destruktor sa praznim telom, metod kojim se ispisuje razlomak, metod za skraćivanje razlomka, metod razlomak Zbir(razlomak); za sabiranje dva razlomka, konstruktor sa dva parametra, konstruktor sa jednim parametrom, kojim se vrednost imenioca postavlja na 1,

U funkciji main() testirati napisane funkcije. Pozvati konstruktor sa dva parametra za razlomke -3/-5 i 2/5, ispisati ih, sabrati ta dva razlomkai ispisati njihov zbir. Zatim učitati brojilac, pozvati konstruktor sa jednim parametrom kojim se dodeljuje vrednost 1 za imenilac i ispisati odgovarajući razlomak. #include<iostream.h>

Page 20: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

383

#include<math.h> int nzd(int a, int b) { while(a!=b) if(a<b)a-=b; else b-=a; return a; } class razlomak { int brojilac, imenilac; public: razlomak(int b, int i) //Konstruktor sa dva parametra { if(i<0) {b =-b; i=-i; } brojilac=b; imenilac=i; } ~razlomak() {} // Destruktor void ispis() // Ispis razlomka { cout<<brojilac<<'/'<<imenilac<<endl; } void skrati() { if(imenilac<0) { brojilac=-brojilac; imenilac=-imenilac; } int n=nzd(abs(imenilac), abs(brojilac)); brojilac/=n; imenilac/=n; } razlomak Zbir(razlomak); razlomak (int b):imenilac(1) // Konstruktor sa jednim parametrom { brojilac=b; } razlomak(); // Konstruktor }; razlomak razlomak::Zbir(razlomak r) { int b, i; i=imenilac*r.imenilac; b=brojilac*r.imenilac+imenilac*r.brojilac; razlomak rez= razlomak(b,i); rez.skrati(); return(rez); } void main() { razlomak r1(-3,-5), r2(2,5); cout<<"r1: "; r1.ispis(); cout<<"r2: "; r2.ispis(); razlomak r=r1.Zbir(r2); cout<<"r1+r2 = "; r.ispis(); int b; cout<<"brojilac = ? "; cin>>b; razlomak r3(b); r3.ispis(); }

Kada se poziva konstruktor? Konstruktor je funkcija koja popunjava prazne memorijske lokacije koje je sistem odvojio za novi

objekat u “prave” podatke novog objekta koji ima svoje članove i koji može da prima poruke.

Konstruktor se poziva uvek kada objekat klase nastaje, tj. kada se:

Page 21: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

384

1. izvršava definicija statičkog objekta; 2. izvršava definicija automatskog objekta unutar bloka; formalni argumenti pri pozivu funkcije,

nastaju kao lokalni automatski objekti; 3. inicijalizuje objekat, pozivaju se konstruktori njegovih podataka članova; 4. stvara dinamički objekat operatorom new; 5. stvara privremeni objekat, pri povratku iz funkcije, koji se inicijalizuje vraćenom vrednošću

funkcije

Načini pozivanja konstruktora Konstruktor se poziva kada nastaje objekat klase. Na tom mestu je moguće navesti inicijalizatore,

tj. stvarne argumente u pozivu konstruktora. Poziva se onaj konstruktor koji se slaže po broju argumenata i tipovima argumenata sa izrazom kojim se definiše novi objekat.

class X { public: X(); // konstruktor bez parametara X(double); // konstruktor sa parametrom tipa double X(char *); // konstruktor sa parametrom tipa char * //... };

void main() { double d = 3.4; char *p = "Niz znakova"; X a(d); //poziva se X(double) X b(p); //poziva se X(char *) X c; //poziva se X() //... }

Pri definisanju objekta c u kome se poziva podrazumevani konstruktor klase X, ne treba pisati X c(),već samo X c;

Pre izvršavanja samog tela konstruktora klase mogu se pozvati konstruktori članova te klase. Argumenti ovih poziva mogu da se navedu iza zaglavlja definicije konstruktora klase, posle znaka : (dvotačka):

class YY { public: YY (int j) {...} //... };

class XX { public: XX (int);

private: YY y; int i; };

XX::XX (int k) : y(k+1), i(k-1) { //y je inicijalizovan sa k+1, a i sa k-1 //... ostatak konstruktora }

Prvo se pozivaju konstruktori članova, po redosledu deklarisanja u deklaraciji klase, pa se onda izvršava telo konstruktora klase. Ovaj način ne samo da je moguć, već je i jedino ispravan: navođenje inicijalizatora u zaglavlju konstruktora predstavlja specifikaciju inicijalizacije članova, što je različito od operacije dodele koja se može vršiti unutar tela konstruktora

Page 22: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

385

Primer. U ovom primeru je definisana klasa tacka koja ima dva konstruktora. Jedan konstruktor je bez parametara, dok drugi konstruktor ima jedan parametar kojim se definiše vrednost x. Pre tela konstruktora sa jednim parametrom definiše se vrednost parametra y. Osim toga, klasa tacka sadrži sledeće elemente:

privatne podatke x, y koji predstavljaju realne koordinate tačke, metod kojim se tačka translira za zadate realne vrednosti x i y, metod kojim se ispisuju koordinate tačke u jednom redu, metod kojim se za zadatu tačku nalazi njoj simetrična tačka u odnosu na koordinatni početak, metod kojim se tačka rotira oko koordinatnog početka za zadati realni ugao, metod void inic(float a, float b) kojim se podacima x i y dodeljuju vrednosti a i b,

redom. #include<iostream.h> #include<math.h> class tacka { float x,y; public: void inic(float a, float b) {x=a; y=b;} void tranlacija(float dx, float dy) {x+= dx; y+=dy; } void pozicija() { cout << "x= "<<x<<" "<<"y= "<<y<<endl; } void simetrija() {x=-x; y=-y; } void rotacija(float a) { float xr,yr; xr=x*cos(a)-y*sin(a); yr=y*cos(a)+x*sin(a); x=xr; y=yr; } tacka(float a) : y(-5.2) { x=a; } tacka() {} }; void main() { tacka t1; float x1,y1; const float pi=3.14159; cin>>x1; cin>>y1; t1.inic(x1,y1); t1.pozicija(); t1.tranlacija(5.5,5.5); t1.pozicija(); t1.simetrija(); t1.pozicija(); t1.rotacija(pi/2); t1.pozicija(); tacka t2(-1.2); t2.pozicija(); }

Konstruktor se može pozvati i eksplicitno u nekom izrazu. Tada nastaje privremeni objekat klase pozivom odgovarajućeg konstruktora sa navedenim argumentima. Isto se dešava ako se u inicijalizatoru eksplicitno navede poziv konstruktora: void main()

{ complex c1(1,2.4); // poziv konstruktora sa zadatim vrednostima

Page 23: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

386

complex c2; // poziv konstruktora sa podrazumevanim vrednostima c2=c1+complex(3.4,-1.5); // privremeni objekat complex c3=complex(0.1,5); // opet privremeni objekat koji se kopira u c3 }

Kada se pravi niz objekata neke klase, poziva se podrazumevani konstruktor za svaku komponentu niza ponaosob, po rastućem redosledu indeksa.

Još o destruktorima Funkcija članica klase koja čije se ime dobija pisanjem znaka ~ ispred imena te klase, naziva se

destruktor. Ova funkcija se poziva automatski pri prestanku života objekta klase: class X { public: ~X() {cout<<" poziv destruktora klase X!\n";} }

void main() { X x; //... } // ovde se poziva destruktor objekta x

Destruktor nema tip koji vraća i ne može imati argumente. Unutar destruktora, članovima se

pristupa kao i u bilo kojoj drugoj funkciji članici. Svaka klasa može da ima najviše jedan destruktor.

Destruktor se implicitno poziva i pri uništavanju dinamičkih objekta pomoću operatora delete. Za niz, destruktor se poziva za svaki element ponaosob. Redosled poziva destruktora je, u svakom slučaju obratan od redosleda poziva konstruktora.

Ako klasa nema eksplicitno deklarisani destruktor, prevodilac implicitno generiše podrazumevani destruktor koji je javni i koji vrši destrukciju podataka članova i podobjekta osnovne klase pozivom njihovih destruktora.

Destruktori se uglavnom koriste kada objekat treba da dealocira memoriju ili neke sistemske resurse koje je konstruktor alocirao; to je najčešće potrebno kada klasa sadrži članove koji su pokazivači na pridružene dinamičke objekte koji ekskluzivno pripadaju datom objektu, pa ih je potrebno uništiti prilikom uništavanja tog objekta. Destruktor se automatski poziva kada neka akcija, kao što je korišćenje operatora delete, izaziva uništenje objekta.

9.2.3. Const funkcije članice

Namena. Const sprečava menjanje promenljive, argumenta ili (ako se radi sa pokazivačima) vrednosti na koju pokazuje pokazivač.

Sintaksa. Najjednostavnija verzija funkcije const sprečava menjanje promenljive na koju se ona primenjuje. Na primer, sledećim izrazom se sprečava promena vrednosti promenljive vr:

const tip vr

Osim toga, funkcija const može menjati i deklaraciju pokazivača. Tako se sprečavaju promene onoga na šta taj pokazivač pokazuje.

const tip *pokazivač

Rezervisana reč const može da se koristi i unutar deklaracije pokazivača, i tada sprečava menjanje samog pokazivača.

tip * const pokazivač

Moguće je, čak, deklarisati const pokazivač na const tip – što sprečava izmenu i pokazivača i onoga na šta on pokazuje.

const tip * const pokazivač

Page 24: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

387

Primena 1: const promenljive Rezervisana reč const se može upotrebiti za običnu promenljivu. Tada prevodilac odbacuje sve

iskaze u kojima se toj promenljivoj dodeljuje neka vrednost, ali je inicijalizacija dozvoljena. const int id = 12345; // Ovo je ispravno; // id može biti inicijalizovana. id = 10000; // GREŠKA!

Složene tipove, kao što su znakovni nizovi i klase, možete takode deklarisati kao const. U tom slučaju će svi članovi i elementi objekata takvih tipova biti zaštićeni od menjanja.

Primena 2: const pokazivači Sledeća deklaracija znači "pokazivač p pokazuje na konstantan, ceo broj". Drugim rečima, p

pokazuje na celobrojnu vrednost koja ne može da se promeni; const int *p;

U ovom slučaju se samo p može menjati, dok se vrednost *p ne može menjati. U C++ važe sledeća pravila o vezi između pokazivača i const podataka:

▪ Const pokazivač može pokazivati i na const podatke i na one koji to nisu. ▪ Običan pokazivač ne može pokazivati na const podatke.

const int id = 12345; int *p = &id; // GREŠKA! Ovo nije dozvoljeno. (*p)++;

Ako je p pokazivač na const podatke, tada se *p ne može menjati, const int id = 12345; const int *p = &id; // Ovo je u redu. (*p)++; // GREŠKA! *p je const!

Primena 3: const tipovi argumenata Pomoću const argumenta pokazivača može se proslediti adresa kao vrednost parametra funkcije, a

sprečiti promene podataka na toj adresi. Funkcija uzima pokazivač na podatke, ali ga ne koristi da bi ih menjala. Na primer:

void fnct(const char str[], const double *px, const double *py);

U okviru funkcije mogu biti pročitane sve vrednosti *str, *px i *py. Ipak, sledeći iskazi, koji pokušavaju da dodele vrednost podacima na koje se pokazuje, izazivaju greške:

*str = 'a'; // GREŠKA! str[2] = 'z'; // GREŠKA! *px = 0.0; // GREŠKA! *py = 98.6; // GREŠKA!

Nije dopušteno korištiti const pokazivač za menjanje podataka na koje pokazuje čak i ako je upotrebljen pomeraj (kao gore, u izrazu str[2]).

Primena 4: const funkcije članice i objekti Ako pomoću const deklarišete objekat, možete pozivati samo const funkcije članice tog objekta.

Takve funkcije ne menjaju podatke članove.

Funkciju članicu deklarišete kao const tako što rezervisanu reč unesete na kraj deklaracije, ali ispred definicije, ako postoji. Na primer;

class:CStr { int getlength(void) const { return nLength; } char *get(void) const { return pData;} //... };

Page 25: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

388

Možete preklopiti funkciju članicu tako da ima i const verziju i verziju koja nije const (bez razlike u sintaksi osim po korišćenju rezervisane reči const).

Ako deklarišete metod klase sa const, podrazumeva se da metod neće promeniti vrednost ni jednog člana klase. Da biste deklarisali metod klase kao konstantu, stavite ključnu reč const posle zagrada, ali pre znaka tačka-zarez.

Funkcije pristupa se često deklarišu kao konstantne funkcije, korišćenjem modifikatora const. Klasa Cat ima dve funkcije pristupa:

void PostaviStarost(int godine); int DajStarost();

Metod PostaviStarost() ne može biti const, jer je napisan sa tendencijom da menja promenljivu članicu starost. Metod DajStarost(), sa druge strane, može i trebalo bi da bude const, jer ne menja klasu. Jednostavno, metod DajStarost() vraća tekuću vrednost promenljive članice starost. Zato bi deklaracija ovih funkcija trebalo da bude napisana ovako

void PostaviStarost(int godine); int DajStarost() const;

Ako deklarišete funkciju sa const, a implementacija te funkcije menja objekat, menjajući vrednost nekog od njegovih članova, kompajler će to označiti kao grešku.

Napomena. Koristite const uvek kada je to moguće. Deklarišite funkcije članice sa const uvek kada one ne bi trebalo da promene objekat, omogućavajući kompajleru da pomogne u pronalaženju grešaka; to je brže i jeftinije nego da sami tražite greške.

Dobra programerska praksa je da se korisnicima klase najavi da li neka funkcija članica menja unutrašnje stanje objekta ili ga samo čita i vraća informaciju korisniku klase.

Funkcije članice koje ne menjaju unutrašnje stanje objekta nazivaju se inspektori ili selektori (engl. inspector, selector). Reč const iza zaglavlja funkcije ukazuje korisniku klase da je funkcija članica inspektor. Ovakve funkcije članice nazivaju se u jeziku C++ konstantnim funkcijama članicama (engl. constant member functions).

Funkcija članica koja menja stanje objekta naziva se mutator ili modifikator (engl. mutator, modifier) i ne označava se posebno:

class X { public: int read() const { return i; } int write (int j=0) { int temp=i; i=j return temp; } private: int i; };

Deklarisanje funkcije članice kao inspektora samo je notaciona pogodnost i stvar lepog ponašanja prema korisniku. To je obećanje projektanata klase da funkcija ne menja stanje objekta koje je projektant definisao. Prevodilac nema načina da u potpunosti proveri da li inspektor posredno menja neke podatke članova klase.

Inspektor može da menja podatke članove pomoću eksplicitne konverzije, koje probija kontrolu konstantnosti. To je ponekad slučaj kada inspektor treba da izračuna podatak koji vraća, pa ga onda sačuva u nekom članu da bi sledeći put brže vratio odgovor.

Za prethodni primer važi: X x; const X cx; x.read(); // u redu: konstantna funkcija nekostantnog objekta; x.write(); //u redu: nekonstantna funkcija nekostantnog objekta; cx.read(); //u redu: konstantna funkcija konstantnog objekta; cx.write(); //greška: nekonstantna funkcija konstantnog objekta;

Page 26: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

389

9.2.4. Inline implementacija

Metode klase možete učiniti inline. Ključna reč inline se pojavljuje pre povratnog tipa. Inline implementacija funkcije DajTezinu(), na primer, izgleda ovako;

Inline int Cat::DajTezinu() { return tezina; // vraca podatak clan tezina }

Takođe možete staviti definiciju funkcije članice u deklaraciju klase, što automatski čini funkciju inline. Na primer:

class Cat { public: int DajTezinu() { return tezina; } //inline funkcija void PostaviTezinu(int NekaTezina);

};

Uočite sintaksu definicije funkcije DajTezinu(). Telo inline funkcije počinje odmah nakon njene deklaracije. Kao i kod svake funkcije, definicija počinje otvarajućom, a završava se zatvarajućom zagradom. Znak tačka-zarez ne piše se posle zagrada

Sledeća dva programa obnavljaju klasu Cat, ali stavljaju deklaraciju klase u CAT.HPP, a implementaciju funkcija u CAT.CPP.

Primer. Deklaracija klase Cat u CAT.HPP. 1: #include <iostream.h> 2: class Cat 3: { 4: public: 5: Cat (int start); 6: ~Cat(); 7: int DajStarost() { return starost;} // inline! 8: void PostaviStarost(int godine) {starost = godine;} // inline! 9: void Miau() { cout << "Mijau.\n";} // inline! 10: private: 11: int starost; 12:};

Primer. Implementacija klase Cat u CAT.CPP. 1: // Demonstrira inline funkcije 2: // i ukljucenje zaglavlja 3: #include<iostream.h> 4: #include "cat.hpp" // proverite da li ste ukljucili zaglavlja! 5: 6: 7: Cat::Cat(int start) //konstruktor 8: { 9: starost = start; 10: } 11: 12: Cat::~Cat() //destruktor, ne radi nista 13: { 14: } 15: 16: // Kreira objekat, postavlja njenu starost, pravi mijauk, 17: // saopstava starost i opet pravi mijauk 18: int main() 19: { 20: Cat Frisky(5); 21: Frisky.Miau(); 22: cout << "Frisky je macka koja ima " ; 23: cout << Frisky.DajStarost() << " godina.\n";

Page 27: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

390

24: Frisky.Miau(); 25: Frisky.PostaviStarost(7); 26: cout << "Sada Frisky ima " ; 27: cout << Frisky.DajStarost() << " godina.\n"; 28: return 0; 29: }

Kôd koji je predstavljen u ovim listinzima sličan je prethodnom osim što su tri metoda napisana kao inline u deklaracionoj datoteci i deklaracija je izdvojena u CAT.HPP.

Metod DajStarost() je deklarisan u liniji 7 i obezbeđena je njegova inline implementacija. Linije 8 i 9 obezbeđuju još neke inline funkcije, ali upotrebljivost ovih funkcija je nepromenjena u odnosu na prethodne "outline" (outline = izvanlinijske) implementacije.

Linije 18-29 ponavljaju glavnu funkciju iz prethodnog primera. To pokazuje da proglašavanje ovih funkcija inline ne menja njihovo delovanje.

Primer. Kreirati klasu za rad sa datumima koja ima sledeću strukturu:

privatni članovi: d,m,g koji predstavljaju dan, mesec i godinu; metod koji proverava da li je godina prestupna; metod koji izračunava broj dana u mesecu;

javni članovi: konstruktor koji postavlja dan, mesec i godinu; metod za inicilalizaciju objekta; metod koji nalazi sutrašnji datum; metod koji nalazi prethodni datum; metod koji ispisuje datum.

Napisati test program.

#include<iostream.h> class datum { unsigned d,m,g; bool Prestupna() { return ((g%4==0 && g%100 !=0)||(g%400==0)); } unsigned BrojDana(); public: datum(unsigned dan=21, unsigned mes=8, unsigned god=2006) //konstruktor { d=dan; m=mes; g=god; } void inic(unsigned dan=21, unsigned mes=8, unsigned god=2006) { d=dan; m=mes; g=god; } void Sledeci(); void Prethodni(); void Ispis(); }; unsigned datum::BrojDana() { switch(m) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 4: case 6: case 9: case 11: return 30; case 2: return 28+Prestupna(); } } void datum::Sledeci() { if(d<BrojDana())d++; else { d=1; m++; if(m==13){ m=1; g++; } }

Page 28: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

391

} void datum::Prethodni() { if(d>1)d--; else { m--; if(m==0){ m=12; g--; } d=BrojDana(); } } void datum::Ispis() { cout<<" Dan: "<<d<< " Mesec: "<<m<<" Godina: "<<g<<endl; } void main() { unsigned d,m,g, i; cout<<"Pocetni datum? "; cin>>d>>m>>g; datum dat(d,m,g); cout<<"Pocetni datum: "; dat.Ispis(); cout<<"Datumi u naredna 5 dana:\n"; for(i=1; i<=5; i++){ dat.Sledeci(); dat.Ispis(); } dat.inic(); cout<<"Datum posle inicijalizacije: "; dat.Ispis(); cout<<"Datumi u prethodna 5 dana:\n"; for(i=1; i<=5; i++) {dat.Prethodni(); dat.Ispis(); } }

9.2.5. Klase sa drugim klasama kao podacima članovima

Nije neuobičajeno izgraditi kompleksnu klasu deklarisanjem jednostavnijih klasa i njihovim uključivanjem u deklaraciju komplikovanije klase. Na primer, možda ćete deklarisati klasu točka, klasu motora, klasu transmisije i tako dalje, a onda ih kombinovati u klasu automobila.

Razmotrite drugi primer. Tačka se definiše koordinatama X i Y Sledeći primer prikazuje kompletnu deklaraciju klase Rectangle. Zato što se pravougaonik definiše pomoću četiri tačke, na početku je deklarisana klasa Point, koja će čuvati X i Y koordinate svake tačke.

Primer. Deklarisanje kompleksne klase Rectangle koja sadrži elemente iz druge klase Point. #include <iostream.h> #include<math.h> class Point // sadrzi x i y koordinate { // nema konstruktora, koristi se podrazumevani public: void SetX(double x) { X = x; } void SetY(double y) { Y = y; } double GetX()const { return X;} double GetY()const { return Y;} void Print() { cout<<"x = "<<X<<" y = "<<Y<<endl; } private: double X; double Y; }; // kraj deklaracije klase Point double Distance(Point M, Point N) { return(sqrt(pow(M.GetX()-N.GetX(),2)+pow(M.GetY()-N.GetY(),2))); } class Rectangle {

Page 29: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

392

public: Rectangle(double t, double l, double b, double r, double px, double py); ~Rectangle() {} double GetTop() const { return Top; } double GetLeft() const { return Left; } double GetBottom() const { return Bottom; } double GetRight() const { return Right; } Point GetD() const { return D; } Point GetA() const { return A; } Point GetC() const { return C; } Point GetB() const { return B; } void SetD(Point Location) {D = Location;} void SetA(Point Location) {A = Location;} void SetC(Point Location) {C = Location;} void SetB(Point Location) {B = Location;} void SetTop(double t) { Top = t; } void SetLeft (double l) { Left = l; } void SetBottom (double b) { Bottom = b; } void SetRight (double r) { Right = r; } double GetArea() const; private: Point A,B,C,D; double Top, Left, Bottom, Right, dx, dy; }; Rectangle::Rectangle(double t, double l, double b, double r, double px, double py) { Top = t; Left = l; Bottom = b; Right = r; dx=px; dy=py; A.SetX(l); A.SetY(b+dy); cout<<"Teme A: "; A.Print(); B.SetX(r-dx); B.SetY(b); cout<<"Teme B: "; B.Print(); C.SetX(r); C.SetY(t-dy); cout<<"Teme C: "; C.Print(); D.SetX(l+dx); D.SetY(t); cout<<"Teme d: "; D.Print(); } // proracunava povrsinu pravougaonika, pronalazenjem temena, // kreira sirinu i visinu, a onda mnozi double Rectangle::GetArea() const { double Width = Distance(A,B); double Height = Distance(B,C); return (Width * Height); } double main() { //inicijalizuje lokalnu Rectangle promenljivu Rectangle MyRectangle(100, 20, 50, 80, 10, 10); double Area = MyRectangle.GetArea(); cout << "Povrsina: " << Area << "\n"; cout << "Gornja leva X koordinata: "; cout << MyRectangle.GetD().GetX()<<endl; return 0; }

Teme A: x = 20 y = 60 Teme B: x = 70 y = 50 Teme C: x = 80 y = 90 Teme D: x = 30 y = 100

Page 30: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

393

Povrsina: 2102.38 Gornja leva X koordinata: 30

Unutar deklaracije klase Point, deklarisane su dve promenljive članice (X i Y). Ove promenljive čuvaju vrednosti koordinata.

Klasa Point koristi inline funkcije pristupa za dobijanje i postavljanje X i Y koordinata. Klasa Point koristi podrazumevani konstruktor i destruktor. Zato, morate postaviti njihove koordinate eksplicitno.

Klasa Rectangle se sastoji od četiri tačke, koje predstavljaju temena pravougaonika.

Konstruktor za Rectangle prihvata 6 realnih vrednosti, sa nazivima t, l, b, r, px i py. Četiri parametra za konstruktor se kopiraju u četiri promenljive članice, a onda se kreiraju četiri objekta klase Point.

Pored uobičajenih funkcija pristupa, klasa Rectangle ima funkciju GetArea(). Da bi ovo uradila, ona proračunava širinu i visinu pravougaonika, a, zatim množi ove dve vrednosti.

Uzimanje X-koordinate gornjeg levog ugla pravougaonika zahteva da pristupite tački UpperLeft i zatražite njenu X vrednost. Zato što je GetUpperLeft() metod klase Rectangle, on može direktno pristupiti privatnim podacima klase Rectangle, uključujući i tačku D.

#include<iostream.h> #include<math.h> class Point { public: void SetXY(double x, double y) { X=x; Y=y; } void GetXY(double *x, double *y) { *x=X; *y=Y; } void Print() { cout<<"x = "<<X<<" y = "<<Y<<endl; } private: double X,Y; }; double Distance(Point M, Point N) { double xM, yM, xN, yN; M.GetXY(&xM, &yM); N.GetXY(&xN, &yN); return(sqrt(pow(xM-xN,2)+pow(yM-yN,2))); } class Rectangle { public: Rectangle(double t,double b,double l,double r, double px,double py); Rectangle(Point P, Point Q, double px, double py):A(P), B(Q) { double xA,yA, xB, yB; A.GetXY(&xA, &yA); B.GetXY(&xB, &yB); D.SetXY(xA+px, yA+py); C.SetXY(xB+px, yB+py); Top=yA+py; Bottom=yB; Left=xA; Right=xB+dx; } ~Rectangle() {} void SetTBLR(double t, double b, double l, double r) { Top=t; Bottom=b; Left=l; Right=r; } void GetTBLR(double *t, double *b, double *l, double *r) { *t=Top; *b=Bottom; *l=Left; *r=Right; } void GetABCD(Point *TA, Point *TB, Point *TC, Point *TD) { *TA=A; *TB=B; *TC=C; *TD=D; } void SetABCD(Point LA,Point LB, Point LC, Point LD) { A=LA; B=LB; C=LC; D=LD; }

Page 31: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

394

double GetArea() const; private: Point A,B,C,D; double Top, Bottom, Left, Right, dx, dy; }; Rectangle::Rectangle(double t, double b,double l,double r, double px,double py) { SetTBLR(t,b,l,r); dx=px; dy=py; A.SetXY(l,t-dy); cout<<"A:\n"; A.Print(); B.SetXY(r-dx, b); cout<<"B:\n"; B.Print(); C.SetXY(r, b+dy); cout<<"C:\n"; C.Print(); D.SetXY(l+dx,t); cout<<"D:\n"; D.Print(); } double Rectangle::GetArea() const { double width=Distance(A,B); double height=Distance(C,D); return(width*height); } void main() { Rectangle MyRectangle(100,20,50,80,10,10); double area=MyRectangle.GetArea(); cout<<"Povrsina pravougaonika = "<<area<<endl; double t,b,l,r; MyRectangle.GetTBLR(&t, &b, &l, &r); cout<<"Top = "<<t<<" Bottom = "<<b<<" Left = "<<l<<" Right = "<<r; double xA,yA, xB, yB, px, py; cout<<endl<<"A: "; cin>>xA>>yA; cout<<"B: "; cin>>xB>>yB; cout<<"dx: "; cin>>px; cout<<"dy: "; cin>>py; Point PA, PB; PA.SetXY(xA, yA); PB.SetXY(yA, yB); Rectangle Rect(PA, PB, px, py); double ar=Rect.GetArea(); cout<<"Povrsina drugog pravougaonika = "<<ar<<endl; } Primer. Rad sa kompleksnim brojevima. Klasa Complex sadrži dva privatna člana: dva broja Re,

Im koji predstavljaju realni i imaginarni deo kompleksnog broja. Osim toga, ova klasa sadrži i sledeće funkcije članice:

Konstruktor Complex(double r):Im(0) koji postavlja realni deo kompleksnog broja na vrednost r, a imaginarni deo na 0.

Konstruktor Complex(double r,double i) koji postavlja realni deo kompleksnog broja na vrednost r, a imaginarni deo na i.

Konstruktor bez parametara, koji nema dejstvo. Funkciju void GetReIm(double *r, double *i) koja izračunava realni i imaginarni deo

kompleksnog broja. Funkciju void SetReIm(double r, double i) postavlja realni deo kompleksnog broja na

vrednost r, a imaginarni deo na i. Funkciju void Print()koja ispisuje kompleksni broj. Prijateljsku funkciju friend Complex operator+(Complex z1, Complex z2); koja

sabira dva kompleksna broja z1 i z2. Prijateljsku funkciju friend Complex operator+=(double r, Complex z1); koja

povećava realni deo kompleksnog broja z1 za vrednost r.

Page 32: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

395

#include<iostream.h> class Complex { private: double Re,Im; public: Complex(double r):Im(0) { Re=r; } Complex(double r, double i) { Re=r; Im=i; } Complex(){} void GetReIm(double *r, double *i) { *r=Re; *i=Im; } void SetReIm(double r, double i) { Re=r; Im=i; } void Print() { cout<<Re<<"+I*"<<Im<<endl; } friend Complex operator+(Complex z1, Complex z2); friend Complex operator+=(double r, Complex z1); }; Complex operator+(Complex z1, Complex z2) { Complex z; double r1,i1,r2,i2; z1.GetReIm(&r1,&i1); z2.GetReIm(&r2,&i2); z.SetReIm(r1+r2, i1+i2); return z; } Complex operator+=(double a, Complex z1) { Complex z; double r1,i1; z1.GetReIm(&r1, &i1); z.SetReIm(a+r1,i1); return z; } void main() { double r1,i1,r2,i2; cout<<"z1: "; cin>>r1>>i1; cout<<"z2: "; cin>>r2>>i2; Complex z1(r1,i1), z2(r2,i2),z; z=z1+z2; z.Print(); cout<<"Unesi double broj koji se dodaje "; cin>>r1; z=r1+=z; z.Print(); }

Slično rešenje, u kome se koristi preklapanje operatora +. #include<iostream.h> class Complex { double Re, Im; public: Complex(double r); Complex(double r, double i); Complex(){} void GetReIm(double *r, double *i); void SetReIm(double r, double i); void Print(); Complex operator +(Complex z2); friend Complex operator +(double a, Complex z1);

Page 33: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

396

}; Complex::Complex(double r):Im(0) { Re=r; } Complex::Complex(double r, double i) { Re=r; Im=i; } void Complex::GetReIm(double *r, double *i) { *r=Re; *i=Im; } void Complex::SetReIm(double r, double i) { Re=r; Im=i; } void Complex::Print() { cout<<Re<<"+I*"<<Im<<endl; } Complex Complex::operator +(Complex z2) { Complex z; z.Re=Re+z2.Re; z.Im=Im+z2.Im; return z; } Complex operator +(double a, Complex z1) { Complex z; z.Re=a+z1.Re; z.Im=z1.Im; return z; }

9.2.6. Strukture i klase

Veoma bliska ključnoj reči class je ključna reč struct, koja se koristi za deklarisanje strukture. U C++ struktura je isto što i klasa, osim što su članovi strukture podrazumevano javni dok su članovi klase podrzumevano privatni. Možete deklarisati strukturu kao što deklarišete i klasu i možete joj dodeliti potpuno iste podatke članove i funkcije. U stvari, ako sledite dobru programersku praksu da uvek eksplicitno deklarišete privatne i javne sekcije klase, u tom slučaju neće biti nikakve razlike.

U programu u kome su definisane klase Point pokušajte da unesete sledeće promene. Promenite class Point u struct Point. Zatim promenite class Rectangle u struct Rectangle. Sada ponovo izvršite program i uporedite izlaz. Ne bi trebalo da bude bilo kakvih promena.

Zašo dve ključne reči proizvode slične akcije? Kada je C++ razvijen, on je izgrađen kao proširenje jezika C. Jezik C ima strukture, iako one nemaju metode klase. Bjarne Stroustrup, kreator jezika C++, temeljio je izgradnju na struct, ali je promenio ime u class, da bi predstavio novu, proširenu funkcionalnost.

Struktura je klasa kod koje su svi članovi podrazumevano javni. To se može promeniti eksplicitnim umetanjem public: i private:

struct a { isto što i : class a { public: //… //… private: private: //… //… }; };

Struktura se obično koristi za definisanje slogova podataka koji ne predstavljaju apstrakciju, odnosno nemaju ponašanje (nemaju značajnije operacije), nego najčešće služe samo za implementaciju složene strukture neke apstrakcije. Strukture tipično poseduju samo konstruktore i možda destruktore kao funkcije članice.

Page 34: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

397

9.3. Nasleđivanje Šta je pas? Kada pogledate svog ljubimca, šta vidite? Biolog će videti mrežu međusobno povezanih

organa, fizičar će videti atome i sile koje deluju, a klasifikator će videti predstavnika vrste canine domesticus. Ovo je poslednja stvar koja nas u ovom trenutku interesuje. Pas pripada porodici canine, koja spada u grupu sisara i tako dalje. Klasifikatori dele svet živih bića u carstva, plemena, klase, redove, familije, rodove i primerke.

Ova hijerarhija ostvaruje je relaciju. Pas je primerak iz porodice canine. Ovu relaciju možemo pronaći gde god da pogledamo: "toyota" je vrsta kola, a kola su vrsta vozila. Puding je vrsta deserta, koji je vrsta hrane.

Koncept psa se nasleđuje, što će reći da on automatski preuzima sve osobine sisara. S obzirom da je u pitanju sisar, mi znamo da se on kreće, da udiše vazduh, pošto se svi sisari kreću i udišu vazduh, po definiciji. Koncept psa nam u postojeću definiciju sisara dodaje nove ideje o lajanju, mahanju repa i tako dalje. Dalje, psi se mogu podeliti na lovačke i terijere, a terijeri se dalje mogu podeliti na jorkširske terijere, Dandie Dinmont terijere i tako dalje. Jorkširski terijer je vrsta terijera, pa je, prema tome, on i vrsta psa i sisar, pa je stoga i vrsta životinje i stoga je, na kraju, i vrsta živog bića.

C++ pokušava da predstavi ove relacije tako što omogućava da definišete klase koje se izvode jedne iz drugih. Ovo izvođenje je način predstavljanja je relacije. Novu klasu Pas izvešćete iz klase Sisar. Nećete morati eksplicitno da navodite da se psi kreću, pošto su ovu osobinu oni nasledili od klase Sisar. Za klasu koja dodaje novu funkcionalnost postojećoj klasi kaže se da je izvedena iz originalne klase. Za originalnu klasu se kaže da je bazna klasa nove klase.

Ako se klasa Pas izvodi iz klase Sisar, tada je Sisar bazna klasa klase Pas. Izvedene klase su nadskup svojih baznih klasa. Kao što pas dodaje određene karakteristike pojmu sisara, klasa Pas će dodati određene metode, ili podatke klasi Sisar.

Obično, bazne klase imaju više od jedne izvedene klase. Baš kao što su psi, mačke i konji -tipovi sisara, tako će i njihove klase biti izvedene iz klase Sisar.

9.3.1. Kako se definišu izvedene klase u jeziku C++?

Kada deklarišete klasu, u mogućnosti ste da označite klasu iz koje je ona izvedena, tako što ćete napisati znak : iza imena klase, tip izvođenja (public, ili neki drugi) i ime klase iz koje je izvedena. Tip izvođenja će biti objašnjen kasnije. Za sada, koristite public. Klasa iz koje se vrši izvođenje mora da bude deklarisana, ili se dobija greška u kompilaciji.

class Base { int i; public: void f(); };

class Derived : public Base { int j; public: void g(); };

Objekti izvedene klase imaju sve članove osnovne klase, ali i svoje posebne članove koji su navedeni u deklaraciji izvedene klase.

Objekti izvedene klase definišu se i koriste na uobičajen način: void main() { Base b: Derived d; b.f(); b.g(); //ovo, naravno ne može d.f(); // d ima i funkciju f,

Page 35: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

398

d.g(); // i funkciju g }

Primer. U listingu koji sledi ilustrovano je kako se deklariše klasa Dog, koja je izvedena iz klase Mammal.

1: //Jednostavno nasleđivanje 2: 3: #include <iostream.h> 4: enum VRSTE {YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB}; 5: 6: class Mammal 7: { 8: public: 9: // konstruktori 10: Mammal(); 11: ~Mammal(); 12: 13: //metodi pristupa 14: int DajStarost()const; 15: void PostaviStarost(int); 16: int DajTezinu() const; 17: void PostaviTezinu(); 18: 19: //Drugi metodi 20: void Govori(); 21: void Spava(); 22: 23: 24: protected: 25: int starost; 26: int tezina; 27: }; 28: 29: class Dog : public Mammal 30: { 31: public: 32: 33: // Konstruktor i destruktor 34: Dog(); 35: ~Dog(); 36: 37: // Metodi pristupa 38: VRSTE DajVrstu() const; 39: void PostaviVrstu(VRSTE); 40: 41: // Drugi metodi 42: // MahanjeRepom(); 43: // Hrana(); 44: 45: protected: 46: VRSTE vrsta; 47: };

Ovaj program ne daje nikakav izlaz, budući da je on samo niz deklaracija klase bez njene implementacije. No, bez obzira na to, u njemu je prikazano dosta stvari koje treba upoznati.

U linijama 6-27 deklarisana je klasa Mammal. Primetićete da se u ovom primeru klasa Mammal ne izvodi iz neke druge klase. U realnom svetu, sisari su izvedeni, s obzirom da su deo klase životinja. U C++ programima možete predstaviti samo deo raspoloživih informacija o bilo kojem datom objektu. Realnost je previše kompleksna, da biste obuhvatili sve informacije. Hijerarhija mora negde da

Page 36: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

399

započne. U ovom slučaju, na vrhu hijerarhije je klasa Mammal. S obzirom da praktično sve životinje imaju godište i težinu, te se stoga ovi atributi pojavljuju u klasi Mammal.

Da bi program bio jednostavan za razumevanje, u klasu Mammal je definisano samo šest metoda - četiri pristupna metoda kao i dva dodatna metoda Govori() i Spava().

Klasa Dog se nasleđuje iz klase Mammal, kao što je naznačeno u liniji 29. Svaki objekat klase Dog će imati tri promenljive članice: starost, tezina i vrsta. Primetićete da deklaracija za klasu Dog ne uključuje promenljive članice starost i tezina. Objekti klase Dog će ove promenljive naslediti iz klase Mammal, zajedno sa svim Mammal metodima, izuzev operatora za kopiranja i konstruktora i destrukotora.

9.3.2. Prava pristupa u nasleđivanju

Ključna reč public u zaglavlju deklaracije izvedene klase znači da su svi javni članovi osnovne klase ujedno i javni članovi izvedene klase. Privatni članovi osnovne klase uvek to i ostaju. Funkcije članice izvedene klase ne mogu da pristupaju privatnim članovima osnovne klase.

Javnim članovima osnovne klase se iz funkcija članica izvedene klase pristupa neposredno, kao i sopstvenim članovima:

class Base { int pb; public: int jb; void put (int x) {pb=x;} };

class Derived : public Base { int pd; public: void write (int a, int b, int c) { pd=a; jb=b; pb=c; //ovo ne može put(c); //ovako može } };

Ako deklaracija člana izvedene klase sakriva istoimeni član osnovne klase, tada se sakrivenom članu osnovne klase može da se pristupi pomoću operatora : :. Na primer, ako bi u izvedenoj klasi Derived bio definisan podatak jb, tada možemo pisati Base::jb.

Često postoji potreba da nekim članovima osnovne klase pristupaju funkcije članice izvedenih klasa, ali ne i korisnici klasa. Članovi koji su dostupni samo izvedenim klasama, ali ne i korisnicima spolja, navode se iza ključne reči protected: i nazivaju se zaštićeni članovi.

Verovatno ste primetili novu ključnu reč protected u linijama 24 i 45 u kodu kojim je klasa Dog izvedena iz klase Mammal. U dosadašnjim primerima su podaci u klasi bili deklarisani kao private. Međutim, private članovi nisu na raspolaganju izvedenim klasama. Možete svojstva starost i tezina proglasiti za public, ali nije poželjno ukoliko ne želite da druge klase direktno pristupaju ovim članovima podataka.

Zaštićeni članovi ostaju takvi i za sledeće izvedene klase pri sukcesivnom nasleđivanju. Uopšte, ne može se povećati pravo pristupa nekom članu koji je privatan, zaštićen ili javni.

Primer. Modifikacija klasa Base i Derived. class Base { int pb; protected: int zb; public:

Page 37: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

400

int jb; //... };

class Derived : public Base { //... public: void write (int x) { jb=zb=x //može da pristupi javnom i zaštićenom članu, pb=x; //ali ne i privatnom: greška! } };

void f() { Base b; b.zb=5; //odavde ne može da se pristupa zaštićenom članu }

Primer. U ovom header fajlu (point.h) definisane su dve klase. Klasa Lokacija opisuje X i Y koordinate tačke. Klasa Point opisuje vidljivost tačke.

enum boolean { False, True }

class Lokacija { Protected: // U izvedenim klasama je dozvoljen pristup internim podacima int X; int Y; // Sve klase izvedene iz klase Lokacija imaće pristup X i Y public: // Ovim funkcijama je dozvoljen pristup spolja Lokacija (int InitX, int InitY); // konstruktor int GetX(); int GetY(); };

class Point : public Lokacija // klasa izvedena iz klase Lokacija { Protected: Boolean Vidljivost; Public: Point(int InitX, int InitY); // konstruktor void Show(); void hide(); boolean Isvisible(); void MoveTo(int NewX, int NewY); };

Zaštićeni članovi podataka i funkcije su dostupni u izvedenim klasama, a u svim drugim slučajevima se ponašaju kao private.

U jeziku C++ postoje tri specifikacije pristupa: public, protected i private. Ako funkcija sadrži jedan objekat klase, ona može pristupiti svim public članovima podacima i funkcijama. Funkcije članovi, na dalje, mogu pristupiti svim private članovima podacima i funkcijama sopstvene klase i svim protected članovima podacima i funkcijama bilo koje klase iz koje su izvedeni.

Stoga, funkcija Dog::MahanjeRepom() može da pristupi private podatku vrsta, kao i protected podacima klase Mammal.

Čak i ako su druge klase postavljene između klasa Mammal i Dog (na primer Domaće životinje), klasa Dog će i dalje biti u mogućnosti da pristupi protected članovima klase Mammal, podrazumevajući da sve te druge klase koriste public nasleđivanje.

Page 38: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

401

Sledeći program demonstrira kako da kreirate objekat tipa Dog i kako da, zatim, pristupite podacima i funkcijama tog tipa.

Primer. Jednostovno nasleđivanje. 1: //Upotreba izvedenog objekta 2: 3: #include <iostream.h> 4: enum VRSTE {YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB}; 5: 6: class Mammal 7: { 8: public: 9: // konstruktori i destruktor 10: Mammal(): starost(2), tezina(5) {}; 11: ~Mammal(){}; 12: 13: //metodi pristupa 14: int DajStarost()const {return starost;} 15: void PostaviStarost(int godine){ starost = godine;} 16: int DajTezinu() const {return tezina; } 17: void PostaviTezinu(int weight){ tezina = weight; } 18: 19: //Drugi metodi 20: void Govori() const { cout << "Zvuk\n"; } 21: void Spava() const {cout << ”Ja spavam.\n”; } 22: 23: 24: protected: 25: int starost; 26: int tezina; 27: }; 28: 29: class Dog : public Mammal 30: { 31: public: 32: 33: // Konstruktor i destruktor 34: Dog(): vrsta(YORKIE){} 35: ~Dog(){}; 36: 37: // Metodi pristupa 38: VRSTE DajVrstu() const {return vrsta;} 39: void PostaviVrstu(VRSTE); 40: 41: // Drugi metodi 42: void MahanjeRepom() {cout << "Mahanje repom...\n"; } 43: void Hrana() {cout << "Vrsta hrane\n";} 44: 45: protected: 46: VRSTE vrsta; 47: }; 48: 49: int main() 50: { 51: Dog fido; 52: fido.Spava(); 53: fido.MahanjeRepom(); 54: cout << "Fido je " << fido.DajStarost() << "godina star\n"; 55: return(0); 56: }

Page 39: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

402

9.3.3. Konstruktori i destruktori u nasleđivanju

Dog objekti su Mammal objekti. Ovo je suština relacije je. Kada je kreiran objekat Fido, najpre je pozvan njegov bazni konstruktor, koji je kreirao Mammal. Zatim je pozvan konstrukor Dog, koji je kompletirao konstrukciju objekta klase Dog. S obzirom da u deklaraciji objekta Fido nisu korišćeni parametri, u svim slučajevima su pozvani podrazumevani konstruktori. Kada se objekat Fido uništava, prvo se poziva destruktor za Dog, a zatim dekstruktor za Mammal deo objekta Fido. Svakom destruktoru je data mogućnost da očisti sopstveni deo objekta Fido.

Primer. Pozivanje konstruktora i destruktora. 1: //Upotreba izvedenog objekta 2: 3: #include <iostream.h> 4: enum VRSTE {YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB}; 5: 6: class Mammal 7: { 8: public: 9: // konstruktori 10: Mammal(); 11: ~Mammal(); 12: 13: //metodi pristupa 14: int DajStarost()const {return starost;} 15: void PostaviStarost(int godine){ starost = godine;} 16: int DajTezinu() const {return tezina; } 17: void PostaviTezinu(int weight){ tezina = weight; } 18: 19: //Drugi metodi 20: void Govori() const { cout << "Zvuk\n"; } 21: void Spava() const {cout << "Ja spavam.\n"; } 22: 23: 24: protected: 25: int starost; 26: int tezina; 27: }; 28: 29: class Dog : public Mammal 30: { 31: public: 32: 33: // Konstruktori 34: Dog(); 35: ~Dog(); 36: 37: // Metodi pristupa 38: VRSTE DajVrstu() const {return vrsta;} 39: void PostaviVrstu(VRSTE rasa) {vrsta = rasa; } 40: 41: // Drugi metodi 42: void MahanjeRepom() {cout << "Mahanje repom...\n"; } 43: void Hrana() {cout << "Vrsta hrane\n";} 44: 45: private: 46: VRSTE vrsta; 47: }; 48: 49: Mammal::Mammal(): 50: starost(1),

Page 40: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

403

51: tezina(5) 52: { 53: cout << "Mammal constructor...\n"; 54: } 55: 56: Mammal::~Mammal() 57: { 58: cout << "Mammal destructor...\n"; 59: } 60: 61: Dog::Dog(): 62: vrsta(YORKIE) 63: { 64: cout <<"Dog constructor ...\n"; 65: } 66: 67: Dog::~Dog() 68: { 69: cout <<"Dog destructor...\n"; 70: } 71: int main() 72: { 73: Dog fido; 74: fido.Spava(); 75: fido.MahanjeRepom(); 76: cout << "Fido je " << fido.DajStarost() << " godina star\n"; 77: return(0); 78: }

Mammal constructor... Dog constructor... Ja spavam Mahanje repom... Fido je 1 godina star Dog destructor... Mammal destructor...

Listing je sličan prethodnom, osim što konstuktori i destruktori sada prikazuju na ekranu poruku da su pozvani. Prvo je pozvan konstruktor Mammal, a, zatim, Dog. U ovom trenutku objekat Fido klase Dog je potupuno funkcionalan i njegovi metodi mogu biti pozvani. Kada Fido izađe iz opsega, biće pozvan destuktor za Dog, a, odmah zatim, i za Mammal.

Primer. U ovom primeru je definisana klasa Poligon, a na osnovu nje su izvedene klase Trougao i Kvadrat.

a) Napisati program u kome se definiše klasa pod nazivom Poligon, kojom se opisuje pravilan poligon. Ova klasa koristi u polju podataka:

• Dužinu strane (DuzinaStrane tipa float) i broj stranica (BrojStrana tipa int).

Kao metode klase definisati:

• Funkciju PreuzmiStranu() koja kao rezultat vraća zadatu dužinu strane.

• Funkciju PostaviStranu() koja promenljivoj DuzinaStrane dodeljuje prosleđenu realnu vrednost.

• Funkciju PreuzmiBrojStrana koja preuzima definisan broj strana poligona.

• Funkciju PostaviBrojStrana koja promenljivoj BrojStrana dodeljuje zadatu celobrojnu vrednost.

b) Kao podklasu klase Poligon definisati klasu Trougao koja raspolaže sledećom metodom.

• Funkciju PovrsinaTrougla koja se računa po formuli DuzinaStrane* DuzinaStrane* 3 /2.

Page 41: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

404

• Funkciju ObimTrougla koja računa njegov obim po formuli 3 * DuzinaStrane.

c) Kao podklasu klase Poligon definisati klasu Kvadrat koja raspolaže sledećom metodom.

• Funkciju PovrsinaKvadrata koja se računa po formuli DuzinaStrane * DuzinaStrane.

• Funkciju ObimKvadrata koja računa njegov obim po formuli 4 * DuzinaStrane.

Na osnovu definisanih klasa sračunaj površinu i obim trougla sa zadatom dužinom stranice, kao i površinu i obim kvadrata sa zadatom dužinom stranice.

#include<iostream.h> #include<math.h> class Poligon { float DuzinaStrane; int BrojStrana; public: float PreuzmiStranu(); void PostaviStranu(float duzina); int PreuzmiBrojStrana(); void PostaviBrojStrana(int Broj); Poligon(const int Bs, const float D); Poligon() {} ~Poligon() { cout<<"Poligon je unisten\n"; } }; class Trougao:public Poligon { public: float PovrsinaTrougla(); float ObimTrougla(); ~Trougao() { cout<<"Trougao je unisten\n"; } }; class Kvadrat:public Poligon { public: float PovrsinaKvadrata(); float ObimKvadrata(); ~Kvadrat() { cout<<"Kvadrat je unisten\n"; } }; float Poligon::PreuzmiStranu() { return DuzinaStrane; } void Poligon::PostaviStranu(float duzina) { DuzinaStrane=duzina; } int Poligon::PreuzmiBrojStrana() { return BrojStrana; } void Poligon::PostaviBrojStrana(int Broj) { BrojStrana=Broj; } Poligon::Poligon(int Bs, float D) { BrojStrana=Bs; DuzinaStrane=D; } float Trougao::PovrsinaTrougla() { return PreuzmiStranu()*PreuzmiStranu()*sqrt(3)/2; } float Trougao::ObimTrougla()

Page 42: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

405

{ return PreuzmiStranu()*3; } float Kvadrat::PovrsinaKvadrata() { return PreuzmiStranu()*PreuzmiStranu(); } float Kvadrat::ObimKvadrata() { return PreuzmiStranu()*4; } void main() { float s; Poligon P(5,17.3); Trougao P3; Kvadrat P4; cout<<"Formiran je poligon sa "<<P.PreuzmiBrojStrana() <<" strana cija je duzina "<<P.PreuzmiStranu()<<endl; cout<<"Duzina strane trougla? "; cin>>s; P3.PostaviStranu(s); cout<<"Jednakostranicni trougao sa duzinom strane " <<P3.PreuzmiStranu() <<" Ima Povrsinu "<<P3.PovrsinaTrougla() <<" i obim "<<P3.ObimTrougla()<<endl; cout<<"Duzina strane kvadrata? "; cin>>s; P4.PostaviStranu(s); cout<<"Kvadrat sa duzinom strane "<<P4.PreuzmiStranu() <<" Ima Povrsinu "<<P4.PovrsinaKvadrata()<<" i obim " <<P4.ObimKvadrata()<<endl; } Izlaz: Formiran je poligon sa 5 strana cija je duzina 17.3 Duzina strana trougla? 2.3 Jednakostranicni trougao sa duzinom strane 2.3 Ima povrsinu 4.58127 i obim 6.9 Duzina strane kvadrata? 0.98 Kvadrat sa duzinom strane 0.98 Ima povrsinu 0.9604 i obim 3.92 Kvadrat je unisten Poligon je unisten Trougao je unisten Poligon je unisten Poligon je unisten Primer. Definisati klasu niz koja će omogućiti rad sa nizom realnih brojeva. Elementi klase niz su: Privatni član BrElem koji predstavlja broj elemenata u nizu, Zaštićeni član float Elementi[100] koji predstavlja elemente niza,

Javni metodi klase su:

Funkcija void PostaviElemente() kojom se postavljaju vrednosti elemenata niza, Funkcija void PrikaziElemente() za ispis elemenata niza, Funkcija float Element(int i) čiji je rezultat element niza sa indeksom i, Funkcija void Sort() za sortiranje elemenata niza,

Iz klase niz izvedena je klasa Vector. U ovoj klasi je definisan javni metod PomeriUlevo() kojim se elementi niza ciklički pomeraju za jedno mesto ulevo.

U glavnom programu uraditi sledeće:

1. Učitati broj elemenata i elemente niza, a zatim ispisati te elemente. 2. Sortirati elemente niza, a zatim ispisati sortirane elemente. 3. Učitati broj elemenata vektora i elemente. 4. Pomeriti ciklički elemente vektora za jedno mesto ulevo i ispisati tako dobijene elemente. 5. Za zadati indeks nekog elementa vektora ispisati njegovu vrednost.

#include<iostream.h>

Page 43: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

406

class niz { int BrElem; protected: float Elementi[100]; public: void PostaviElemente() { int i; cout<<"Broj elemenata: "; cin>>BrElem; cout<< "Elementi:\n"; for(i=0;i<BrElem;i++)cin>>Elementi[i]; } void PrikaziElemente() { int i; for(i=0; i<BrElem; i++) cout<<"Elementi["<<i<<"] = "<<Elementi[i]<<endl; } int BrojElemenata() {return BrElem; } float Element(int i) { return Elementi[i]; } void Sort(); }; //Kraj definicije klase void niz::Sort() //Outline implementacija funkcije Sort() { int i,j; for(i=0; i<BrElem-1; i++) for(j=i+1; j<BrElem; j++) if(Elementi[i]>Elementi[j]) { float p=Elementi[i]; Elementi[i]=Elementi[j]; Elementi[j]=p; } } class Vektor:public niz { public: void PomeriUlevo() { int i; float p=Elementi[0]; for(i=1; i<=BrojElemenata()-1; Elementi[i-1]=Elementi[i++]); Elementi[BrojElemenata()-1]=p; } }; void main() { niz a; a.PostaviElemente(); a.PrikaziElemente(); cout<<"\nSortirani niz\n"; a.Sort(); a.PrikaziElemente(); Vektor b; b.PostaviElemente(); b.PomeriUlevo(); cout<<"Posle pomeranja ulevo elementi su:\n"; b.PrikaziElemente(); int i; cout<<"Redni broj elementa ? "; cin>>i; cout<<i<<". element niza = "<<b.Element(i)<<endl; }

Page 44: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

407

Ukoliko bi niz Elementi bio deklarisan sa privatnim pristupom, tada bi u klasi niz bilo neophodno da se doda sledeći metod za postavljanje vrednosti elementu niza

void PostaviElement(int i, float r) { Elementi[i]=r; }

Takođe, metod PomeriUlevo() bi se morao predefinisati na sledeći način: void PomeriUlevo() { int i; float p=Element(0); for(i=0; i<=BrojElemenata()-2; PostaviElement(i,Element(i+1)), i++); PostaviElement(BrojElemenata()-1,p); }

Primer. Definišemo novi tip, Maloletnik. Maloletnik je “jedna vrsta“ osobe, koja “poseduje sve što i svaka druga osoba, samo ima još nešto“, tj. ima staratelja.

Kada nova klasa predstavlja “jednu vrstu“ druge klase (engl. a-kind-of), kaže se da je ona izvedena iz osnovne klase. class Maloletnik: public Osoba { public:

Maloletnik(char *ime, char *staratelj, int godine); /* konstruktor */ void koJeOdgovoran(); private: char *staratelj;

}; void Maloletnik::koJeOdgovoran () { cout<<"Za mene odgovara " << staratelj<< ".\n "; }

}

Maloletnik::Maloletnik(char *i, char *s, int g): Osoba(i,g), staratelj(s) {}

Izvedena klasa Maloletnik ima sve članove kao i osnovna klasa Osoba, ali ima još i članove staratelj i koJeOdgovoran. Konstruktor klase Maloletnik inicijalizuje definiše objekat ove klase zadavanjem imena staratelja i godina, i to tako da se konstruktor osnovne klase Osoba (koji inicijalizuje ime i godine) poziva koristeći odgovarajuće argumente. Konstruktor klase Maloletnik samo inicijalizuje ime staratelja.

Sada se mogu koristiti nasleđene osobine objekata klase Maloletnik a na raspolaganju su i njihova posebna svojstva kojih nije bilo u klasi Osoba:

Osoba otac(“Petar Petrovic“, 40 ); Maloletnik dete(“Milan Petrovic“, “Petar Petrovic“, 12 ); otac.koSi(); dete.koSi(); dete.koJeOdgovoran(); otac.koJeOdgovoran(); /* Ovo, naravno, ne može !*/

Izlaz će biti: Ja sam Petar Petrović i imam 40 godina. Ja sam Milan Petrović i imam 12 godina. Za mene odgovara Petar Petrović.

Primer. U ovom primeru je definisana klasa Lokacija kojom se definiše položaj tačke, dok se preko druge klase Point definišu svojstva tačke. // point.h sadrži dve klase // Klasa Lokacija opisuje X i Y koordinate tačke. // Klasa Point opisuje da li je tačka vidljlva ili ne.

Page 45: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

408

enum Boolean {False, True};

class Lokacija { protected: // U izvedenlm klasama dozvoljen

// pristup internim podacima. int X;

int Y; public: // Ovim funkcijama dozvoljen je pristup spolja. Lokacija (int InitX, int InitY); int GetX(); int GetY();

}; class Point : public Lokacija // izvedena iz klase Lokaclja { protected: // public pokazuje da su X i Y // protected i u klasi Point Boolean Vidljivost; // klasama izvedenim iz Point biće // dozvoljen pristup public: Point(int InitX, int InitY); // konstruktor void Show();

void Hide(); Boolean Invisible(); void MoveTo (int NewX, int NewY);

};

9.3.4. Privatno, javno i zaštićeno izvođenje

Ključna reč public u zaglavlju deklaracije izvedene klase znači da je osnovna klasa javna, odnosno da su svi javni članovi osnovne klase ujedno i javni članovi izvedene klase. Privatni članovi osnovne klase nisu dostupni izvedenoj klasi, a zaštićeni članovi osnovne klase ostaju zaštićeni i u izvedenoj klasi. Ovakvo izvođenje se u jeziku C++ naziva još i javno izvođenje.

U zaglavlje deklaracije može se, ispred imena osnovne klase, umesto reči public upisati reč private, što se i podrazumeva ako se ne navede ništa drugo. U ovom slučaju javni i zaštićeni članovi osnovne klase postaju privatni članovi izvedene klase. Ovakvo izvođenje se u jeziku C++ naziva privatno izvođenje.

U zaglavlje deklaracije može se, ispred imena osnovne klase, upisati reč protected. Tada javni i zaštićeni članovi osnovne klase postaju zaštićeni članovi izvedene klase. Ovakvo izvođenje u jeziku C++ naziva se zaštićeno izvođenje.

U svakom slučaju, privatni članovi osnovne klase nisu dostupni izvedenoj klasi. Izvedena klasa može samo nadalje “sakriti” zaštićene i javne članove osnovne klase, zavisno od načina izvođenja.

U svakom slučaju, izvedena klasa ne može povećati nivo prava pristupa do člana osnovne klase.

Veza između mehanizma nasleđivanja i nasleđenog nivoa zaštite data je u sledećoj tabeli.:

Polazni nivo zaštite Mehanizam nasleđivanja Nasleđeni nivo zaštite private private public private

protected public protected public public public private protected private

protected protected protected public protected protected private private private

protected private private public private private

Page 46: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

409

U slučaju privatnog i zaštićenog izvođenja, kada izvedena klasa smanjuje nivo prava pristupa do javnih i zaštićenih članova osnovne klase, može se ovaj nivo vratiti na početni eksplicitnim navođenjem deklaracije javnog ili zaštićenog člana osnovne klase u javnom ili zaštićenom delu izvedene klase.

Primer. Jednostavni primer različitih mehanizama nasleđivanja. #include<iostream.h>

class Base { private: int bpriv; protected: int bprot; public: int bpub; };

class PrivDerived : Base { //privatno izvođenje protected: Base:: bprot; //vraćanje na nivo protected public: PrivDerived() { bprot=2; bpub=3; //može se pristupiti cout<<"bprot= "<<bprot<<" bpub = "<<bpub<<endl; bpriv=4;//ne može se pristupiti privatnom članu osnovne klase } };

class ProtDerived : protected Base { //zaštićeno izvođenje //... };

void main() { PrivDerived pd; pd.bpub=0; //greška: bpub nije javni član, već privatni }

9.3.5. Semantička razlika između privatnog i javnog izvođenja

Javno izvođenje realizuje koncept nasleđivanja, koji je iskazan relacijom “B je vrsta A”. Ova relacija podrazumeva da izvedena klasa ima sve što i osnovna, što znači da je sve što je dostupno korisniku osnovne klase, dostupno i korisniku izvedene klase. U jeziku C++ to znači da javni članovi osnovne klase treba da budu javni i u izvedenoj klasi.

Privatno izvođenje ne oslikava ovu relaciju, jer korisnik izvedene klase ne može da pristupi onome čemu je mogao u osnovnoj klasi. Javnim članovima osnovne klase mogu pristupiti samo funkcije članice izvedene klase, što znači da objekat izvedene klase u sebi sakriva podobjekat osnovne klase. Zato privatno izvođenje realizuje sasvim drugu relaciju, relaciju ”A je deo od B”. Ovo je suštinski različito od relacije nasleđivanja.

Prilikom projektovanja, trebalo bi strogo voditi računa o tome u kojoj su relaciji (od ove dve relacije) neke dve uočene klase. U zavisnosti od toga treba izabrati način izvođenja.

Ako je relacija između dve klase “A je deo od B”, izbor između privatnog izvođenja i članstva zavisi od manje važnih detalja: da li je potrebno redefinisati virtualne funkcije klase A, da li je unutar klase B potrebno konvertovati pokazivače, da li klasa B treba da sadrži jedan ili više primeraka klase A i slično.

Page 47: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

410

9.3.6. Nizovi i izvedene klase

Objekat izvedene klase je vrsta objekta osnovne klase. Međutim, niz objekata izvedene klase nije vrsta niza objekata osnovne klase. Uopšte neka kolekcija objekata izvedene klase nije vrsta kolekcije objekata osnovne klase.

Na primer, iako je automobil vrsta vozila, parking za automobile nije i parking za sve vrste vozila, jer na parking za automobile ne mogu da stanu i kamioni. Ili, ako korisnik neke funkcije prosledi toj funkciji korpu banana ne bi valjalo da mu ta funkcija vrati korpu u kojoj je jedna šljiva, smatrajući da je korpa banana isto što i korpa bilo kakvog voća.

Ako se računa sa nasleđivanjem, u programu ne treba koristiti nizove objekata, već nizove pokazivača na objekte. Ako se formira niz objekata izvedene klase i on prenese kao niz objekata osnovne klase (što, po prethodno rečenom, semantički nije ispravno, ali je moguće) može doći do greške:

#include<iostream.h>

class Base { public: int bi; };

class Derived : public Base { public: int di; };

void f (Base *b) { cout << b[2].bi<< endl; } void main() { Derived d[5]; d[2].bi=77; f(d); //neće se ispisati 77 }

U prethodnom primeru, funkcija f smatra da je dobila niz objekata osnovne klase koji su kraći (nemaju sve članove) od objekata izvedene klase. Kada joj se prosledi niz objekata izvedene klase, funkcija nema načina da odredi da se niz sastoji samo od objekata izvedene klase. Rezultat je, u opštem slučaju, neodređen.

Pored navedene greške, fizički nije moguće direktno smeštati objekte izvedene klase u niz objekata osnovne klase. Objekti izvedene klase su duži, a za svaki element niza je odvojen samo prostor koji je dovoljan za smeštanje objekata osnovne klase.

Zbog svega što je rečeno, kolekcije (nizove) objekata treba realizovati kao nizove pokazivača na objekte:

#include<iostream.h>

void f(Base **b, int i) {cout<<b[i]->bi<< endl;} void main () { Base b1,b2; Derived d1,d2,d3; Base *b[5]; //b se može konvertovati u tip Base** b[0]=&d1; b[1]=&b1; b[2]=&d2; //konverzija Derived* u Base* b[3]=&d3; b[4]=&b2; d2.bi=77; f(b,2); //ispisaće se 77 }

Kako je objekat izvedene klase vrsta objekta osnovne klase, C++ dozvoljava implicitnu konverziju pokazivača Derived* u Base* . Zbog logičkog pravila da niz objekata izvedene klase nije vrsta niza objekata osnovne klase, a kako se nizovi ispravno realizuju pomoću nizova pokazivača, C++ ne dozvoljava implicitnu konverziju pokazivača Derived** (u koji se može konvertovati tip niza pokazivača na objekte izvedene klase) u Base** (u koji se može konvertovati tip niza pokazivača na objekte osnovne klase). Za prethodni primer nije dozvoljeno:

Page 48: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

411

void main() { Derived *d[5]; //d je tipa Derived** //... f(d,2); //nije dozveljena konverzija Derived** u Base** }

9.3.7. Apstraktne klase

Čest je slučaj da neka osnovna funkcija nema nijedan konkretan primerak (objekat), već samo predstavlja generalizaciju izvedenih klasa

Na primer, svi izlazni znakovno orijentisani uređaji računara imaju funkciju za ispis jednog znaka, ali se u osnovnoj klasi izlaznog uređaja ne može definisati način ispisa tog znaka, već je to specifično za svaki uređaj posebno, ili ako iz osnovne klase osoba izvedemo dve klase muškaraca i žena, onda klasa osoba ne može imati primerke, jer ne postoji osoba koja nije ni muškog ni ženskog pola.

Klasa koja nema instance (objekte) već su iz nje samo izvedene druge klase, naziva se apstraktna klasa.

u jeziku C++, apstraktna klasa sadrži bar jednu virtualnu funkciju članicu koja je u njoj samo deklarisana, ali ne i definisana. Definicija te funkcije daće izvedene klase. Ovakva virtualna funkcija naziva se čistom virtualnom funkcijom. Njena deklaracija u osnovnoj klasi završava se sa =0: class OCharDevice { public:

virtual int put (char)= 0; // Čista virtualna funkcija //...

};

U jeziku C++ apstraktna klasa je klasa koja sadrži bar jednu čistu virtualnu funkciju. Ovakva klasa ne može imati instance, već se iz nje izvode druge klase. Ako se u izvedenoj klasi ne navede definicija neke čiste virtualne funkcije iz osnovne klase, i ova izvedena klasa je takođe apstraktna.

Pokazivači i reference na apstraktnu klasu mogu da se definišu, ali oni ukazuju na objekte izvedenih konkretnih (neapstraktnih) klasa.

Primer. U ovom primeru je definisana klasa tacka, i iz nje je izvedena klasa Krug. Privatni podaci su koordinate tačke, dok javni metodi predstavljaju funkcije koje realizuju translaciju, simetriju i rotaciju tačke oko koordinatnog početka, kao i ispis podataka vezanih za tačku i krug. #include<iostream.h>

#include<math.h> class tacka { protected: double x,y; public: tacka(double a, double b) {x=a; y=b;} tacka(double a) : y(-5.2) { x=a; } tacka() {} void tranlacija(double dx, double dy) {x+=dx; y+=dy; } void simetrija() {x=-x; y=-y; } void rotacija(double a) { double xr,yr;

Page 49: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

412

xr=x*cos(a)-y*sin(a); yr=y*cos(a)+x*sin(a); x=xr; y=yr; } void zaglavlje() { cout<<"Tacka se nalazi na poziciji: "; } void pozicija() { cout<<"x= "<<x<<" "<<"y= "<<y<<endl; } void izvestaj() { zaglavlje(); pozicija(); } }; class krug:public tacka { double r; public: krug(double a, double b, double R) { x=a; y=b; r=R; } void zaglavlje() { cout<<"Poluprecnik kruga je: "<<r; cout<<" a nalazi se na poziciji: "; } void izvestaj() { zaglavlje(); pozicija(); } }; void Testiraj(double StartX, double StartY, double dx, double dy, double ugao) { char ch; tacka t(StartX, StartY); cout<<"Tacka se nalazi na startnoj poziciji\n"; t.izvestaj(); do { cout<<"Pritisni T za translaciju za "<<dx<<","<<dy<<endl; cout<<" S za simetriju\n"; cout<<" R za rotaciju\n"; cout<<" K za kraj\n"; cin>>ch; switch(ch) { case 'T': case 't': t.tranlacija(dx,dy); break; case 'R': case 'r': t.rotacija(ugao); break; case 'S': case 's': t.simetrija(); break; } t.izvestaj(); } while(ch!='k' && ch!='K'); } void TestirajKrug(double StartX, double StartY, double dx, double dy, double ugao,double R) { char ch; krug K(StartX, StartY, R); cout<<"Krug na pocetku\n"; K.izvestaj(); do { cout<<"Pritisni T za translaciju za "<<dx<<","<<dy<<endl; cout<<" S za simetriju\n"; cout<<" R za rotaciju\n"; cout<<" K za kraj\n"; cin>>ch; switch(ch)

Page 50: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

413

{ case 'T': case 't': K.tranlacija(dx,dy); break; case 'R': case 'r': K.rotacija(ugao); break; case 'S': case 's': K.simetrija(); break; } K.izvestaj(); } while(ch!='k' && ch!='K'); } void main() { double x1,y1; const double pi=3.14159; cin>>x1; cin>>y1; Testiraj(x1,y1,10,10,pi/2); tacka t2(-1.2); t2.pozicija(); t2.izvestaj(); double R; cout<<"Poluprecnik = ? "; cin>>R; TestirajKrug(x1,y1,10,10,pi/2,R); }

9.4. Polimorfizam i virtualni metodi

Namena. Ključna reč Virtual odlaže razrešavanje poziva funkcije članice do vremena izvršavanja. Bez obzira na to kako je pristupljeno objektu, izvršava se ispravan kôd, čak i ako pri pozivanju ne znate tačan tip objekta.

Rezervisanom reči virtual mogu se deklarisati i virtualne osnovne klase.

Primena 1: Virtualne funkcije.

Da biste deklarisali virtualnu funkciju, stavite rezervisanu reč virtual ispred prototipa funkcije. class naziv

{ // ... virtual prototip_funkcije; // ... };

Virtualna funkcija mora biti funkcija članica klase (klasa može biti deklarisana sa class, struct ili union). Ne možete staviti definiciju funkcije unutar deklaracije klase, jer virtualna funkcija ne može biti umetnuta (inline).

Kada se funkcija jednom definiše kao virtualna, automatski je virtualna i u svim izvedenim klasama.

Primena 2: Virtualne osnovne klase.

I osnovne klase mogu da se označe ovom rezervisanom reči. Takve klase daju samo po jednu kopiju svojih članova svakom potomku - klasi, čak i ako se osnovna klasa nasleđuje nekoliko puta. Do ovoga dolazi u složenim hijerarhijama u kojima ima višestrukog nasleđivanja. Na primer, CDete nasleđuje samo jednu kopiju članova CPredak, iako CDete nasleđuje preko dve osnovne klase: CMama i CTata.

class CMama : virtual public CPredak { // ... }; class CTata : virtual public CPredak { // ... }; class CDete : public CMama, public CTata { // ... };

Primer. Pretpostavimo da nam je potrebna nova klasa Zena koja je “jedna vrsta osobe“, samo što još ima i devojačko prezime. Klasa Zena biće izvedena iz klase Osoba.

Page 51: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

414

I objekti klase Zena treba da se odazivaju na funkciju koSi, ali je teško pretpostaviti da će jedna dama otvoreno priznati svoje godine. Zato objekat klase Zena treba da ima funkciju koSi, samo što će ona izgledati malo drugačije, svojstveno izvedenoj klasi Zena.

Klasa Maloletnik poseduje sve elemente iz klase Osoba, i još jedan element –ime staratelja. // nasledjivanje, polimorfizam

#include <iostream.h> // Klasa: Osoba class Osoba { public: Osoba(char *ime, int godine); //konstruktor klase Osoba virtual void koSi(); // virtualna funkcija protected: // Dostupno naslednicima char* ime; // podatak: ime i prezime int god; // podatak: koliko ima godina }; void Osoba::koSi() { cout<<"Ja sam "<<ime<<" i imam "<<god<<" godina"<<endl; } Osoba::Osoba (char *i, int g) { ime=i; god = ((g>=0 && g<=100) ? g: 0); } // Klasa: Zena class Zena : public Osoba { public: //konstruktor klase Zena Zena(char *ime, char *devojacko, int godine); // nova verzija funkcije koSi virtual void koSi(); // virtualna funkcija private: char* devojacko; // privatni podatak }; Zena::Zena (char *i, char *d, int g):Osoba(i,g) { devojacko = d; } void Zena::koSi() {cout<<"Ja sam "<<ime<<", devojacko prezime "<<devojacko<<endl; } // Klasa: Maloletnik class Maloletnik : public Osoba {public: //konstruktor klase Maloletnik Maloletnik(char* ime, char* staratelj, int godine); void koOdgovara(); private: char* staratelj; // privatni podatak }; Maloletnik::Maloletnik (char* i, char* s, int g): Osoba(i,g) { staratelj = s;} void Maloletnik::koOdgovara() {cout<<"Ja sam "<<ime<<" i za mene odgovara "<<staratelj<<endl;} // Funkcija; ispitaj void ispitaj (Osoba *hejTi)

Page 52: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

415

{ hejTi->koSi();} // Glavni program void main() { Osoba otac("Petar Petrovic",40); Zena majka ("Milka Petrovic","Mitrovic",35); Maloletnik dete("Milan Petrovic","Petar Petrovic", 12); ispitaj(&otac); ispitaj(&majka); ispitaj(&dete); dete.koOdgovara(); }

Na ekranu se ispisuje sledeći rezultat: Ja sam Petar Petrovic i imam 40 godina Ja sam Milka Petrovic, devojacko prezime Mitrovic Ja sam Milan Petrovic i imam 12 godina Ja sam Milan Petrovic i za mene odgovara Petar Petrovic

Funkcija članica koja će u izvedenim klasama imati nove verzije deklariše se u osnovnoj klasi kao virtualna funkcija (pomoću ključne reči virtual). Izvedena klasa može da dâ svoju definiciju virtualne funkcije, ali i ne mora. U izvedenoj klasi ne mora se navoditi reč virtual .

Da bi članovi osnovne klase Osoba bili dostupni izvedenoj klasi Zena, ali ne i korisnicima spolja, oni se deklarišu iza specifikatora protected: i nazivaju se zaštićenim članovima.

Drugi delovi programa, ako su dobro projektovani, ne vide nikakvu promenu zbog upotrebe objekata iz izvedenih klasa. Oni ne moraju da se menjaju. Funkcija ispitaj ispisuje podatke za osobe koje pripadaju različitim klasama, ali ne mora da se menja:

void ispitaj (Osoba *hejTi) { hejTi->koSi(); }

U funkciji main() koristimo nove klase Zena i Maloletnik. Prvo koristimo konstruktore za uvedene klase:

Osoba otac("Petar Pertovic", 40); Zena majka("Milka Petrovic", "Mitrovic", 35); Maloletnik dete("Milan Petrovic", "Petar Petrovic", 12);

a zatim pozivamo funkciju ispitaj sa adresama objekata kao stvarnim parametrima. Zavisno od klase kojima objekti pripadaju, pozivaju se različite funkcije koSi():

ispitaj(&otac); /* pozvaće se osoba::koSi() */ ispitaj(&majka); /* pozvaće se Zena::koSi() */ ispitaj(&dete) /* pozvaće se Osoba::koSi() */

Izlaz će biti: Ja sam Petar Petrovic i imam 40 godina.

Ja sam Milka Petrovic, devojacko prezime Mitrovic. Ja sam Milan Petrovic i imam 12 godina.

Poziv objekat.f() prevodilac prevodi u kod koji ima semantiku kao f (&objekat).

Funkcija ispitaj dobija kao stvarni parametar pokazivač na tip Osoba. Kako je klasa Zena izvedena iz klase Osoba, C++ dozvoljava da se pokazivač na tip Zena(sa vrednošću &majka) konvertuje u pokazivač na tip Osoba (hejTi). Mehanizam virtualnih funkcija obezbeđuje da funkcija ispitaj, preko pokazivača hejti, pozove pravu verziju funkcije koSi. Zato će se za argument &majka pozivati funkcija Zena::koSi, a za argument &otac funkcija Osoba::koSi. Za argument &dete takođe će se pozvati funkcija Osoba::koSi, jer klasa Maloletnik nije redefinisala virtualnu funkciju koSi.

Navedeno svojstvo da se odaziva prava verzija funkcije klase čiji su naslednici dali nove verzije te funkcije, naziva se polimorfizam (polymorphism).

Page 53: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

416

Program ne radi kao što bi trebalo ako se funkcija ispitaj napiše obliku void ispitaj (Osoba hejTi) { hejTi.koSi(); }

i u funkciji main se koriste sledeći pozivi: ispitaj(otac); ispitaj(majka); ispitaj(dete);

Takođe, i klasa Maloletnik može da redefiniše virtualnu funkciju ispitaj. Tada bi definicija te klasa sadržala najavu funkcije ispitaj

virtual void koSi();

i njenu definiciju void Maloletnik::koSi()

{ cout<<"Ja sam "<<ime<<", imam "<<god <<" godina i za mene odgovara "<<staratelj<<endl; }

Primer. Pretpostavimo da smo projektovali klasu geometrijskih figura sa namerom da sve figure imaju funkciju crtaj() kao članicu. Iz ove klase izveli smo klase kruga, kvadrata, trougla itd. Naravno, svaka izvedena klasa treba da realizuje funkciju crtanja na sebi svojstven način. Sada nam je potrebno da u nekom delu programa iscrtamo sve figure koje se nalaze na našem crtežu. Ovim figurama pristupamo preko niza pokazivača tipa Figura*. C++ omogućava da figure iscrtamo prostim navođenjem:

void crtanje() { for (int i=0; i<brojFigura; i++) nizFigura[i]->crtaj (); }

Iako se u ovom nizu mogu naći različite figure, mi im pristupamo kao figurama, jer sve vrste figura imaju zajedničku osobinu “da mogu da se nacrtaju”. Ipak, svaka od figura svoj zadatak ispuniće onako kako joj to i priliči, odnosno svaki objekat će “prepoznati” kojoj izvedenoj klasi pripada, bez obzira na to što mu se obraćamo uopšteno, kao objektu osnovne klase. To je posledica naše pretpostavke da su i krug i kvadrat i trougao vrste figura.

Svojstvo da svaki objekat izvedene klase, čak i kada mu se pristupa kao objektu osnovne klase, izvršava metod tačno onako kako je to definisano u njegovoj izvedenoj klasi, naziva se polimorfizam.

9.4.1. Virtuelne funkcije

Jezik C++ je statički implementiran. To znači da u fazi prevođenja programa kompilator koristi opise klasa objekata na sličan način kao i opise tipova podataka. Pripadnost objekta klasi je analogna pripadnosti promenljive određenom tipu. Već u fazi prevođenja programa odlučuje se koji će se metod izvršiti. To važi i u slučaju kada u hijerarhiji nasleđivanja postoje funkcije sa istim imenom. Pri tome se koriste tri mehanizma kojima se odlučuje koja će funkcija biti pozvana:

▪ Na osnovu razlike u spisku argumenata. Na primer, funkcije show(int, char) i show(char, float) su dve različite funkcije, iako imaju isto ime.

▪ Drugi mehanizam je operator pridruživanja funkcije klasi. Operatorom Circle::Show() funkcija Show pridružuje se klasi Circle i predstavlja različitu funkciju u odnosu na funkciju Show koja se operatorom Point::Show() pridružuje klasi Point.

▪ Treći mehanizam se zasniva na pripadnosti klasi onog objekta na koji se deluje određenom funkcijom. Na primer, neka objekat Krug pripada klasi Circle, a objekat Tacka pripada klasi Point. Porukom Tacka.Show poziva se funkcija Show iz klase Point, a porukom Krug.Show poziva se funkcija Show iz klase Circle.

Funkcije članice osnovne klase koje se u izvedenim klasama mogu realizovati specifično za svaku

Page 54: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

417

izvedenu klasu, nazivaju se virtualne funkcije.

Virtualna funkcija se u osnovnoj klasi deklariše pomoću ključne reči virtual na početku deklaracije. Prilikom definisanja virtualnih funkcija u izvedenim klasama ne mora se stavljati reč virtual ali se to preporučuje radi povećanja čitljivosti i razumljivosti programa.

Prilikom poziva, odaziva se ona funkcija koja pripada klasi kojoj pripada i objekat koji prima poziv. #include<iostream.h> typedef float Racun; #define clanarina 1000 class ClanBiblioteke { public: virtual void platiClanarinu() //virtualna funkcija { r-=clanarina; } void PostaviStanje(Racun f) { r=f; } Racun DajStanje() { return r; } void PostaviIme(char *s) { ime=s; } char *DajIme() { return ime; } //... private: char *ime; Racun r; //... }; class PocasniClan : public ClanBiblioteke { public: virtual void platiClanarinu() {} //... }; void main () { ClanBiblioteke *Clanovi[10]; PocasniClan *PocasniClanovi[10]; int i, BrojClanova, BrojPocasnihClanova; char ImeIP[30]; Racun stanje; cout<<"Broj clanova ? "; cin>>BrojClanova; for(i=0;i<BrojClanova;i++) { cout<<"Ime i prezime? "; cout<<ImeIP; Clanovi[i]->PostaviIme(ImeIP); cout<<"Stanje na racunu? "; cin>>stanje; Clanovi[i]->PostaviStanje(stanje); cout<<"Placa clanarinu "; Clanovi[i]->platiClanarinu(); cout<<"Novo stanje na racunu: "<<Clanovi[i]->DajStanje(); } cout<<"Broj pocasnih clanova ? "; cin>>BrojPocasnihClanova; for(i=0;i<BrojPocasnihClanova;i++) { cout<<"Ime i prezime? "; ImeIP; PocasniClanovi[i]->PostaviIme(ImeIP);

Page 55: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

418

cout<<"Stanje na racunu? "; cin>>stanje; PocasniClanovi[i]->PostaviStanje(stanje); cout<<"Placa clanarinu "; PocasniClanovi[i]->platiClanarinu(); cout<<"Novo stanje na racunu: "<<PocasniClanovi[i]->DajStanje(); } }

Virtualna funkcija osnovne klase ne mora da se redefiniše u svakoj izvedenoj klasi. U izvedenoj klasi u kojoj virtualna funkcija nije redefinisana, važi značenje te virtualne funkcije iz osnovne klase.

Deklaracija neke virtualne funkcije u svakoj izvedenoj klasi mora da se u potpunosti slaže sa deklaracijom te funkcije u osnovnoj klasi (broj i tipovi argumenata, kao i tip rezultata).

Ako se u izvedenoj klasi deklariše neka funkcija koja ima isto ime kao i virtualna funkcija iz osnovne klase, ali ražličit broj i/ili tipove argumenata, onda ona sakriva (a ne redefiniše) sve ostale istoimene funkcije iz osnovne klase.

Primer. U ovom primeru je definisana klasa tacka, i iz nje je izvedena klasa Krug. Privatni podaci su koordinate centra kruga, dok javni metodi predstavljaju funkcije koje realizuju translaciju, simetriju i rotaciju tačke oko koordinatnog početka, kao i ispis podataka vezanih za tačku i krug. U ovom slučaju, klasa krug je nasledila metod izveštaj iz klase tacka. Funkcija izvestaj je postala virtualna. #include<iostream.h> #include<math.h> class tacka { protected: double x,y; public: tacka(double a, double b) {x=a; y=b;} tacka(double a) : y(-5.2) { x=a; } tacka() {} void tranlacija(double dx, double dy) {x+=dx; y+=dy; } void simetrija() {x=-x; y=-y; } void rotacija(double a) { double xr,yr; xr=x*cos(a)-y*sin(a); yr=y*cos(a)+x*sin(a); x=xr; y=yr; } virtual void zaglavlje() { cout<<"Tacka se nalazi na poziciji: "; } void pozicija() { cout<<"x= "<<x<<" "<<"y= "<<y<<endl; } void izvestaj(tacka *pt) { pt->zaglavlje(); pt->pozicija(); } }; class krug:public tacka { double r; public:

Page 56: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

419

krug(double a, double b, double R) { x=a; y=b; r=R; } virtual void zaglavlje() { cout<<"Poluprecnik kruga je: "<<r; cout<<" a nalazi se na poziciji: "; } }; void Testiraj(double StartX, double StartY, double dx, double dy, double ugao) { char ch; tacka t(StartX, StartY); cout<<"Tacka se nalazi na startnoj poziciji\n"; t.izvestaj(&t); do { cout<<"Pritisni T za translaciju za "<<dx<<","<<dy<<endl; cout<<" S za simetriju\n"; cout<<" R za rotaciju\n"; cout<<" K za kraj\n"; cin>>ch; switch(ch) { case 'T': case 't': t.tranlacija(dx,dy); break; case 'R': case 'r': t.rotacija(ugao); break; case 'S': case 's': t.simetrija(); break; } t.izvestaj(&t); } while(ch!='k' && ch!='K'); } void TestirajKrug(double StartX, double StartY, double dx, double dy, double ugao,double R) { char ch; krug K(StartX, StartY, R); cout<<"Krug na pocetku\n"; K.izvestaj(&K); do { cout<<"Pritisni T za translaciju za "<<dx<<","<<dy<<endl; cout<<" S za simetriju\n"; cout<<" R za rotaciju\n"; cout<<" K za kraj\n"; cin>>ch; switch(ch) { case 'T': case 't': K.tranlacija(dx,dy); break; case 'R': case 'r': K.rotacija(ugao); break; case 'S': case 's': K.simetrija(); break; } K.izvestaj(&K); } while(ch!='k' && ch!='K'); } void main() { double x1,y1; const double pi=3.14159; cin>>x1; cin>>y1; Testiraj(x1,y1,10,10,pi/2); tacka t2(-1.2); t2.pozicija(); t2.izvestaj(&t2); double R; cout<<"Poluprecnik = ? "; cin>>R; TestirajKrug(x1,y1,10,10,pi/2,R); }

Page 57: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

420

9.4.2. Dinamičko vezivanje

Pokazivač na objekat izvedene klase može se implicitno konvertovati u pokazivač na objekat osnovne klase. Isto važi i za reference. Ovo je interpretacija činjenice da se objekat izvedene klase može smatrati i objektom osnovne klase.

Pokazivaču na objekat izvedene klase može se dodeliti pokaživač na objekat osnovne klase samo uz eksplicitnu konverziju. Ovo je interpretacija činjenice da objekat osnovne klase nema sve osobine izvedene klase.

Objekat osnovne klase može se inicijalizovati objektom izvedene klase, i objektu osnovne klase može se dodeliti objekat izvedene klase bez eksplicitne konverzije. To se obavlja ”odsecanjem” članova izvedene klase koji nisu i članovi osnovne klase.

Virtualni mehanizam se aktivira ako se objektu pristupa preko reference ili pokazivača: class Base { public: virtual void f(); //... };

class Derived : public Base { public: virtual void f(); };

void g1(Base b) { b.f(); }

void g2(Base *pb) { pb->f(); }

void g3(Base &rb) { rb.f(); }

void main() { Derived d; g1(d); //poziva se Base::f g2(&d); //poziva se Derived::f g3(d); //poziva se Derived::f Base *pb=new Derived; pb->f(); //poziva se Derived::f Derived &rd=d; rd.f(); //poziva se Derived::f Base b=d; b.f(); //poziva se Base::f delete pb; pb=&b; pb->f() //poziva se Base::f }

Postupak koji obezbeđuje da se funkcija koja se poziva određuje u vreme izvršavanja, a ne prevođenja naziva se dinamičko vezivanje.

Primer. U ovom primeru se razmatraju efekti statičke implementacije klasa u C++. #include<iostream.h> #include<conio.h> enum Boolean {False,True}; class Lokacija { protected: int X; int Y; // Sve klase izvedene iz klase Lokacija imaće pristup

Page 58: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

421

// atributima X i Y public: // Ovim funkcijama je dozvoljen pristup spolja Lokacija (int InitX, int InitY); // konstruktor int GetX(); int GetY(); }; // klasa Point se izvodi iz klase Lokacija class Point : public Lokacija { protected: Boolean Visible; // X i Y se nasleđuju kao protected public: Point(int InitX, int InitY); // konstruktor void Show(); void Hide(); Boolean IsVisible(); void MoveTo(int NewX, int NewY); }; // pridružene funkcije klase Lokacija // prvo konstruktor klase Lokacija Lokacija::Lokacija(int InitX, int InitY) {X=InitX; Y=InitY; } // zatim i ostale int Lokacija::GetX() {return X; } int Lokacija::GetY() {return Y; } // pridružene funkcije klase Point // prvo konstruktor klase Point Point::Point(int InitX, int InitY) : Lokacija(InitX, InitY) { Visible=False; } // tačka je inicijalno nevidljiva // zatim i ostale void Point::Show(void) { Visible=True; //putpixel(X,Y, getcolor()); textcolor(CYAN); gotoxy(X,Y); cprintf("."); } void Point::Hide(void) { Visible=False; //putpixel(X,Y,getbkcolor()); // tačka se iscrtava bojom pozadine // na lokaciji X,Y textcolor(BLACK); gotoxy(X,Y); cprintf("."); } void Point::MoveTo(int NewX, int NewY) { Hide(); X=NewX; Y=NewY; Show(); } // klasu Circle izvodimo iz klase Point class Circle : Point // implicitno se podrazumeva { // mehanizam nasleđivanja private int Radius; public: Circle(int InitX, int InitY, int InitR); // konstruktor void Show(void); void Hide(void);

Page 59: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

422

void MoveTo(int NewX, int NewY); void Expand(int Delta); // klasa ima svoje metode Hide, Show i MoveTo, // kao i novi metod Expand }; // pridružene funkcije klase Circle // prvo konstruktor klase Circle Circle::Circle(int InitX, int InitY, int InitR) : Point(InitX,InitY) { Radius=InitR; Show(); } // zatim i ostale void Circle::Show(void) { Visible=True; // circle(X,Y, Radius); // Funkcija koja iscrtava krug cout << "Centar u: " << X << "," << Y; cout << " Poluprecnik: " << Radius; }

void Circle::Hide(void)

{ Visible=False; cout << "Kruznica je sakrivena\n"; cout << "Centar u: " << X << "," << Y; cout << "Poluprecnik: " << Radius; }

void Circle::MoveTo(int NewX, int NewY) { Hide(); X=NewX; Y=NewY; Show(); } void Circle::Expand(int Delta) { Radius+=Delta; } // definicija funkcije // Glavni program void main() { Point Tacka(2,3); Circle Krug(5,7,9); Tacka=Point(4,5); // kreira se tačka na lokaciji (4,5) Tacka.Show(); Tacka.MoveTo(10,20); // tačka se pomera na (10,20) Tacka.Show(); Krug=Circle(4,5,5); // krug sa centrom u (4,5) radijusa 5 Krug.MoveTo(10,20); // centar kruga se pomera u (10,20) while(1>0); }

U poruci Tacka.MoveTo(10,20) poziva se funkcija MoveTo iz klase Point kojoj pripada objekat Tacka i u okviru ove funkcije pozivaju se funkcije Show i Hide definisane u klasi Point.

Funkcije Hide i Show su predefinisane u klasi Circle jer je bilo potrebno da se za njih veže kod koji omogućava da se prikaže odnosno sakrije ceo krug, a ne samo tačka. Kako se radi o statičkoj implementaciji, u klasi Circle je morala da se ponovi definicija funkcije MoveTo iako je njen kod isti sa kodom istoimene funkcije u klasi Point koja se nasleđuje. Ovo je potrebno zbog eksplicitnog pridruživanja funkcije MoveTo klasi Circle. Da funkcija MoveTo nije eksplicitno pridružena klasi Circle poruka Krug.MoveTo bi se izvršila, ali se u tom slučaju poziva nasleđena funkcija iz klase Point. Kako je kod ove funkcije isti i u jednoj i u drugoj klasi, na prvi pogled se čini da nema razlike u ovim pozivima. Međutim, problem je u tome što će funkcijom MoveTo koja je pozvana iz iz klase Point biti pozvane i funkcije Hide i Show iz te klase. To znači da se neće izvršiti kod koji je potreban za sakrivanje i prikazivanje kruga, već samo tačke. Generalno, statička implementacija ne dozvoljava

Page 60: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

423

da profunkcioniše mehanizam polimorfizma, i da se aktivne funkcije Show i Hide odrede na osnovu objekta koji poziva funkciju MoveTo.

Da bismo mogli da iskoristimo polimorfizam u ovom primeru, potrebno je da se unesu sledeće izmene:

- Funkcije Hide i Show u klasi Point definisati kao virtualne. Ove funkcije ostaju predefinisane u klasi Circle i za njih se automatski primenjuje atribut virtual tako da se ne mora eksplicitno navoditi u klasi Circle.

- Zameniti privatno izvođenje klase Circle iz klase Point javnim izvođenjem. - Funkcija MoveTo u klasi Circle više nije potrebna. Ona se nasleđuje iz klase Point.

#include<iostream.h> #include<conio.h> enum Boolean {False,True}; class Lokacija { protected: int X; int Y; // Sve klase izvedene iz klase Lokacija imaće pristup // atributima X i Y public: // Ovim funkcijama je dozvoljen pristup spolja Lokacija (int InitX, int InitY); // konstruktor int GetX(); int GetY(); }; class Point : public Lokacija { protected: Boolean Visible; // X i Y se nasleđuju kao protected public: Point(int InitX, int InitY); // konstruktor virtual void Show(); virtual void Hide(); Boolean IsVisible(); void MoveTo(int NewX, int NewY); }; Lokacija::Lokacija(int InitX, int InitY) {X=InitX; Y=InitY; } int Lokacija::GetX() {return X; } int Lokacija::GetY() {return Y; } Point::Point(int InitX, int InitY) : Lokacija(InitX, InitY) { Visible=False; } // tačka je inicijalno nevidljiva void Point::Show(void) { Visible=True; //putpixel(X,Y, getcolor()); textcolor(CYAN); gotoxy(X,Y); cprintf("."); } void Point::Hide(void) { Visible=False; textcolor(BLACK); gotoxy(X,Y); cprintf(".");} void Point::MoveTo(int NewX, int NewY) { Hide(); X=NewX; Y=NewY; Show(); } class Circle : public Point // implicitno se podrazumeva { // mehanizam nasleđivanja private int Radius;

Page 61: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

424

public: Circle(int InitX, int InitY, int InitR); // konstruktor void Show(void); void Hide(void); void Expand(int Delta); }; Circle::Circle(int InitX, int InitY, int InitR) : Point(InitX,InitY) { Radius=InitR; Show(); } void Circle::Show(void) { Visible=True; // circle(X,Y, Radius); // Funkcija koja iscrtava krug cout << "Centar u: " << X << "," << Y; cout << " Poluprecnik: " << Radius; } void Circle::Hide(void) { Visible=False; cout << "Kruznica je sakrivena\n"; cout << "Centar u: " << X << "," << Y; cout << "Poluprecnik: " << Radius; } void Circle::Expand(int Delta) { Radius+=Delta; } // definicija funkcije void main() { Point Tacka(2,3); Circle Krug(5,7,9); Tacka=Point(4,5); // kreira se tačka na lokaciji (4,5) Tacka.Show(); Tacka.MoveTo(10,20); // tačka se pomera na (10,20) Tacka.Show(); Krug=Circle(4,5,5); // krug sa centrom u (4,5) radijusa 5 Krug.MoveTo(10,20); // centar kruga se pomera u (10,20) while(1>0); }

9.5. Višestruko nasleđivanje

9.5.1. Šta je višestruko nasleđivanje?

Nekad postoji potreba da izvedena klasa ima osobine više osnovnih klasa istovremeno. Tada se radi o višestrukom nasleđivanju.

Klasa se deklariše kao naslednik više klasa tako što se u zaglavlju deklaracije, iza znaka : , navode osnovne klase razdvojene zarezom. Ispred svake osnovne klase treba da stoji reč public . Na primer

class Derived : public Base1, public Base2, public Base3 { //...};

Sva navedena pravila o nasleđenim članovima važe i ovde. Konstruktori svih osnovnih klasa pozivaju se pre poziva konstruktora članova izvedene klase i pre izvršavanja tela konstruktora izvedene klase. Konstruktori osnovnih klasa pozivaju se po redosledu deklarisanja tih klasa u zaglavlju izvedene klase. Destruktori osnovnih klasa izvršavaju se na kraju, posle izvršavanja tela destruktora izvedene klase i destruktora članova.

Page 62: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

425

9.5.2. Virtualne osnovne klase Posmatrajmo sledeći primer:

class B {/*…*/}; class X : public B {/*...*/}; class Y : public B {/*...*/}; class Z : public X, public Y {/*...*/};

U ovom primeru klase X i Y nasleđuju klasu B, a klasa Z klase X i Y. Klasa Z ima sve što imaju X i Y. Kako svaka od klasa X i Y ima po jedan primerak članova klase B, to će klasa Z imati dva skupa članova klase B. Njih je moguće razlikovati pomoću operatora : : (na primer z.X::i ili z.Y::i).

Ako ovo nije potrebno, klasu B treba deklarisati kao virtualnu osnovnu klasu. class B {/*… */}; class X :virtual public B {/*… */}; class Y :virtual public B {/*… */}; class Z : public X, public Y {/*… */};

Sada klasa Z ima samo jedan skup članova klase B.

Ako neka izvedena klasa ima virtualne i nevirtualne osnovne klase, onda se konstruktori vurtuelnih osnovnih klasa pozivaju pre konstruktorsa nevirtualnih osnovnih klasa po redosledu deklarisanja. Svi konstruktori osnovnih klasa se, naravno pozivaju pre konstruktora članova i konstruktora izvedena klase

9.6. Prijatelji klasa Često je dobro da se klasa projektuje tako da ima povlašćene korisnike, odnosno funkcije ili druge

klase koje imaju pravo pristupa njenim privatnim članovima. Takve funkcije i klase nazivaju se prijateljima.

9.6.1. Prijateljske funkcije i klase

Prijateljske funkcije neke klase nisu članice te klase, ali imaju pristup do privatnih članova te klase. Te funkcije mogu da budu globalne funkcije ili članice drugih klasa. Rešenje takvog problema su takozvane prijateljske funkcije koje mogu da budu sastavni deo neke druge klase ili da su potpuno nezavisne u odnosu na sve klase. Takođe se mogu i cele klase proglasiti prijateljskim klasama, ali je to retko. U tom slučaju za pristup elementima date klase mogu koristiti funkcije definisane u prijateljskoj klasi.

Da biste deklarisali funkciju kao prijatelja klase (friend), morate u deklaraciju klase staviti prototip funkcije kome prethodi režervisana reč friend. Nivo pristupa nije bitan u ovom slučaju.

class ime { // ... friend prototip_funkcije;

Da biste deklarisali klasu kao prijateljsku, upotrebite sledeću sintaksu. (Zamenite class sa struct ili union ako je klasa deklarisana sa struct ili union).

class ime { // ... friend class ime_klase;

Obično bi u C++ trebalo izbegavati korišćenje ove rezervisane reči jer to slabi kapsuliranje. Glavna svrha reči friend funkcija je pisanje funkcija - binarnih operatora.

Primer. U sledećem primeru, funkcija operator+ je napisana kao globalna, ali joj treba pristup privatnom članu klase CFix, koji dobija kao prijatelj klase.

Page 63: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

426

#include<iostream.h> class CFix { private: long kolicina; public: friend CFix operator+ (long lng, CFix cf); CFix(long l) { kolicina=l; } CFix(){} long GetKol() { return kolicina; } //... }; // Funkcija - operator na long + CFix. // Pošto je ova funkcija friend, moze pristupati // privatnom clanu kolicina. // CFix operator+(long lng, CFix cf) { CFix rezultat; rezultat.kolicina = (lng * 1000) + cf.kolicina; return rezultat; } void main() { long kol, l; cout<<"Pocetna kolicina = ? "; cin>>l; CFix c(l); int i, n; cout<<"Koliko puta se dodaje? "; cin>>n; for(i=1; i<=n; i++) { cout<<"kolicina koja se dodaje = ? "; cin>>kol; c=kol+c; cout<<"Nova kolicina = ? "<<c.GetKol()<<endl; } }

9.6.2. Prijateljske klase

Ako je potrebno da sve funkcije članice klase Y budu prijateljske funkcije klasi X,onda se klasa Y deklariše kao prijateljska klasa klasi X. Tada sve funkcije članice klase Y mogu da pristupaju privatnim članovima klase X, ali obratno ne važi:

class X { friend class Y;

//... };

Prijateljske klase obično se koriste kada dve klase imaju tešnje međusobne veze. Pri tome je nepotrebno otkrivati delove neke klase da bi oni bili dostupni drugoj klasi, jer će onda biti dostupni i ostalima. U tom slučaju se ove dve klase proglašavaju prijateljskim.

Primer. Na sledeći način može se obezbediti da samo klasa Creator može da stvara objekte klase X: class X { public: ... private: friend class Creator; X(); //konstruktor je dostupan samo klasi Creator ... };

Page 64: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

427

Pri korišćenju rezervisane reči friend važe dva pravila netranzitivnosti koja su bitna samo u retkim situacijama:

• Ako je klasa C1 prijatelj klase C2, a C2 prijatelj klase C3, C1 nije automatski prijatelj klase C3. Prijatelj mog prijatelja nije uvek i moj prijatelj.

• Ako je klasa Cl prijatelj klase C2, onda klasa D koja je izvedena iz klase C1 nije automatski prijatelj klase C2. Dete mog prijatelja ne mora da bude moj prijatelj.

Primer. U ovom primeru je definisana klasa sat i prijateljska klasa proces. #include<iostream.h> class sat { int i; public: sat() { i=0; } // konstruktor; void ide_sat() { i++; } // prosta promena vremena; friend class proces; // prijateljska klasa tako da se // funkcijom iz klase proces može // pristupati klasi sat; void pisi() { cout<<"Iz klase sat: i = "<<i<<endl; } }; class proces { int i; public: proces() { i=0; } void ide_sat() { i++; // nova funkcija ide_sat, za i iz klase process cout << "i= " << i << "\n"; // i=1 } void istovremeno(sat &ST ) // Poziv po adresi { i = ST.i = 22; // sinhronizacija cout << "ST.i= " << ST.i << "\n"; // ST.i= 22 } }; void main( ) { sat Obj_Sat; proces Rerna; Rerna.ide_sat(); Rerna.istovremeno(Obj_Sat); Obj_Sat.pisi(); // Iz klase sat: i = 22 }

Ako se funkcija Rerna.istovremeno definiše zaglavljem void istovremeno(sat ST )

koje koristi poziv po vrednosti, tada Obj_Sat.pisi(); daje izlaz: Iz klase sat: i = 0.

Da bi se neka funkcija proglasila prijateljem klase, potrebno je bilo gde u deklaraciji te klase navesti deklaraciju te funkcije sa ključnom reči friend ispred. Prijateljska funkcija se definiše na uobičajeni način: #include<iostream.h> class Y { public: void h(int ip); }; class X

Page 65: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

428

{ public: void f(int ip) { i=ip; } void ispis() { cout<<"i = "<<i<<endl; } private: friend void g (int, X&); //prijateljska globalna funkcija friend void Y::h(int); //prijateljska clanica druge klase int i; }; void Y::h(int ip) { X xx; xx.i=ip; xx.ispis(); } void g (int k, X &x) { x.i=k; // prijateljska funkcija može da pristupa } //privatnim clanovima klase void main () { X x; x.f(5); //postavljanje preko clanice x.ispis(); // i = 5 g(6,x); //postavljanje preko prijatelja x.ispis(); // i = 6 Y y; y.h(7); // i = 7; }

Globalne funkcije koje predstavljaju usluge neke klase ili operacije nad tom klasom nazivaju se klasnim uslugama.

Nema formalnih razloga da se koristi globalna funkcija umesto funkcija članica. Globalne funkcije su ponekad pogodnije: 1. globalnoj funkciji može da se dostavi kao argument i objekat drugog tipa, koji će se

konvertovati u potreban tip, dok funkcija članica mora da se pozove za objekat date klase; 2. kada funkcija treba da pristupa članovima više klasa; 3. kada je notaciono pogodnije da se koriste globalne funkcije (poziv je f(x)) umesto članica (poziv

je x.f()); na primer , max(a,b) je čitljivije od a.max(b);

Prijateljstvo se ne nasleđuje: ako je funkcija f prijatelj klasi X, a klasa Y izvedena iz klase X, funkcija f nije prijatelj klasi Y.

9.7. Pokazivač this Unutar svake funkcije članice postoji implicitni (podrazumevani, ugrađeni) lokalni objekat this.

Tip ovog objekta je konstantni pokazivač na klasu u kojoj je ta funkcija članica (ako je klasa X, this je tipa X*const). Ovaj pokazivač ukazuje na objekat čija je funkcija članica pozvana: // definicija funkcije cAdd članice klase complex

complex complex::cAdd (complex c) { complex temp=*this;

// u temp se prepisuje objekat koji je prozvan temp.real+=c.real; temp.imag+=c.imag; return temp; }

Page 66: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

429

Pristup članovima objekta čija je funkcija članica povana obavlja se neposredno; implicitno je to pristup preko pokazivača this i operatora ->. Može se i eksplicitno pristupati članovima preko ovog pokazivača unutar funkcije članice:

// nova definicija funkcije cAdd članice klase complex complex complex::cAdd (complex c) { complex temp; temp.real = this->real+c.real; temp.imag = this->imag+c.imag; return temp; }

Pokazivač this je, u stvari, skriveni argumenat funkcije članice. Poziv objekat.f() prevodilac prevodi u kod koji ima semantiku kao f (&objekat).

Pokazivač this može da se iskoristi prilikom povezivanja dva objekta. Na primer, neka klasa X sadrži objekat klase Y, pri čemu objekat klase Y treba da zna ko ga sadrži. Veza se inicijalno može uspostaviti pomoću konstruktora:

class X { public: X () : y(this) {...} private: Y y; }; class Y { public: Y (X* theContainer) : myContainer (theContainer) {...} private: X* myContainer; };

U konstantnoj funkciji članici tip pokazivača this je const X*const, tako da pokazuje na konstantni objekat, pa nije moguće menjati objekat preko ovog pokazivača (svaki neposredni pristup članu je implicitni pristup preko ovog pokazivača). Takođe, za konstantne objekte klase nije dovoljno pozivati nekonstantnu funkciju članicu (korektnost konstantnosti).

9.8. Ugnježđivanje klasa Klase mogu da se deklarišu i unutar deklaracije druge klase (ugnježđivanje deklaracija klasa).

Ugnježđena klasa se nalazi i u oblasti važenja okružujuće klase, pa se njenom imenu može pristupiti samo preko operatora razrešavanja oblasti važenja : :.

Okružujuća klasa nema nikakva posebna prava pristupa članovima okružujuće klase. Ugnježđivanje je samo stvar oblasti važenja, a ne i kontrole pristupa članovima.

int x,y;

class Spoljna { public: int x; class Unutrasnja { void f (int i, Spoljna *ps) { x=i; //greška: pristup Spoljna::x nije korektan! ::x=i; //u redu: pristup globalnom x; y=i; //u redu: pristup globalnom y; ps->x=i; //u redu: pristup Spoljna::x objekta *ps; } }; };

Page 67: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

430

Unutrasnja u; //greška:Unutrasnja nije u oblasti važenja! Spoljna::Unutrasnja u; //u redu

Unutar deklaracije klase mogu se navesti i deklaracije nabrajanja (enum) i typedef deklaracije. Ugnježđivanje se koristi kada neki tip semantički pripada samo datoj klasi, a nije globalno važan i za druge klase. Ovakvo korišćenje povećava čitljivost programa i smanjuje potrebu za globalnim tipovima.

9.9. Neki posebni operatori

9.9.1. Operatori new i delete

Postoji pet područja memorije:

▪ Prostor globalnih imena ▪ slobodno skladište (heap) ▪ registri ▪ prostor za kôd ▪ stek

Lokalne promenljive su na steku, zajedno sa parametrima funkcije. Kod se nalazi u prostoru za kod, naravno, a globalne promenljive u prostoru globalnih imena. Registri se koriste za interne funkcije, kao što je pamćenje vrha steka i pokazivača instrukcija. Skoro sva preostala memorija se dodeljuje slobodnom skladištu, na koje se ponekad upućuje sa gomila.

Problem sa lokalnim promenljivama je taj što one nisu trajne: Po povratku iz funkcije, one se odbacuju. Globalne promenljive rešavaju taj problem po cenu neograničenog pristupa širom programa, što vodi do kreiranja koda koji je težak za razumevanje i održavanje. Stavljanje podataka u slobodno skladište rešava oba ova problema.

O slobodnom skladištu možete razmišljati kao o masovnoj sekciji memorije, u kojoj hiljade sekvencijalno označenih kockica leže, čekajući podatke. Ipak, ne možete označiti ove kockice kao što možete stek. Morate tražiti adresu kockice koju rezervišete, a onda tu adresu smestiti u pokazivač.

Stek se automatski čisti po povratku iz funkcije. Sve lokalne promenljive izlaze iz opsega i uklanjaju se sa steka. Slobodno skladište se ne čisti sve dok se program ne završi, i mora da se oslobodi svaki memorijski prostor koji je rezervisan.

Prednost slobodnog skladišta je što memorija koju rezervišete ostaje raspoloživa, dok je eksplicitno ne oslobodite. Ako rezervišete memoriju na slobodnom skladištu u funkciji, memorija je još uvek raspoloživa po povratku iz funkcije.

Prednost pristupanja memoriji na ovaj način, umesto korišćenja globalnih promenljivih, je to da samo funkcije sa pristupom pokazivaču imaju pristup podacima. Ovo obezbeđuje usko kontrolisan interfejs ka tim podacima i eliminiše opasnost da neka funkcija promeni podatke na neočekivane i nepripremljene načine.

Ponekad programer želi da preuzme kontrolu nad alokacijom dinamičkih objekata neke klase, a ne da je prepusti ugrađenom alokatoru. To je zgodno na primer kada su objekti klase mali i može se precizno kontrolisati njihova alokacija, tako da se smanje troškovi alokacije.

Za ovakve potrebe mogu se preklopiti operatori new i delete za neku klasu. Operatorske funkcije new i delete moraju biti statičke funkcije članice, jer se one pozivaju pre nego što je objekat stvarno inicijalizovan, odnosno pošto je uništen.

Ako je korisnik definisao ove operatorske funkcije za neku klasu, one će se pozivati kad god nastaje dinamički objekat te klase operatorom new, odnosno kada se takav objekat dealocira operatorom delete.

Unutar tela ovih operatorskih funkcija na treba eksplicitno pozivati konstruktor, odnosno destruktor. Konstruktor se implicitno poziva posle funkcije new, a destruktor se implicitno poziva pre

Page 68: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

431

funkcije delete. Ove operatorske funkcije služe samo da obezbede prostor za smeštanje objekta i da ga posle oslobode, a ne da naprave objekat. Operator new treba da vrati pokazivač na alocirani prostor.

Ove operatorske funkcije deklarišu se na sledeći način: void* operator new (size_t veličina) void operator delete (void* pokazivač)

Tip size_t je celobrojni tip definisan u <stdlib.h> i služi za izražavanje veličina objekata. Argument veličina daje veličinu potrebnog prostora koji treba alocirati za objekat. Argument pokazivač je pokazivač na prostor koji treba osloboditi.

Podrazumevani (ugrađeni) operatori new i delete mogu da se pozivaju unutar tela redefinisanih operatorskih funkcija ili eksplicitno, preko operatora : :, ili implicitno, kada se prave dinamički objekti za koje nisu redefinisani ovi operatori.

Operator new Memoriju na slobodnom skladištu u C++ alocira se korišćenjem ključne reči new. Posle nje sledi

tip objekta koji želite da alocirate tako da kompajler zna koliko se memorije zahteva.

Namena. Pravi jedan ili više podataka prostih tipova ili objekata klase; poziva konstruktor posle dodele memorije za neki objekat.

Sintaksa. Operator new ima više verzija. U svakoj vraća pokazivač na dodeljene podatke, a tip pokazivača je tip*. U poslednjoj verziji se dodeljuje više objekata; broj može biti svaki nenegativni celobrojni izraz.

new tip new tip(args) new tip[broj] // Dodeljuje više objekata.

Primer. U oba primera korišćenja operatora new vraćeni pokazivač je tipa int*: int *pl, *p2; pl = new int; // Dodeljuje jednu celobrojnu vrednost p2 = new int[50]; // Dodeljuje 50 celobrojnih vrednosti,

Kada pravite objekte, argumente možete navoditi sve dok postoji odgovarajući konstruktor. CAuto *pkola1, *pkola2, *pkola3; pkola1 = new CAuto; pkola2 = new CAuto("Jaguar", 97); pkola3 = new CAuto[10]; // Dodeljuje 10 objekata

Druga upotreba operatora new poziva konstruktor CAuto(char*, int). Prva i treća upotreba pozivaju podrazumevani konstruktor (u poslednjem slučaju 10 puta, jer se dodeljuje 10 objekata).

Posle upotrebe podataka, obrišite ih naredbom delete

Napomena. Ispravno je koristiti sintaksu new tip[veličina] da biste napravili samo jedan objekat, na primer, new Cauto[1]. Ovako napravljen objekat se briše naredbom delete [] tip.

Povratna vrednost iz new je memorijska adresa. Ona se mora dodeliti pokazivaču. Da biste kreirali unsigned short na slobodnom skladištu, napišite

unsigned short int *pPointer; pPointer = new unsigned short int;

Možete, naravno, inicijalizovati pokazivač pri njegovom kreiranju sa unsigned short int * pPointer = new unsigned short int;

U svakom slučaju, pPointer sada pokazuje na unsigned short int na slobodnom skladištu. Njega možete koristiti kao i svaki drugi pokazivač na promenljivu i dodeliti vrednost području memorije pisanjem

*pPointer = 72.

Page 69: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

432

Ovo znači: "Stavite 72 na vrednost u pPointer", ili: "Dodeli te vrednost 72 području na slobodnom skladištu na koje pokazuje pPointer".

Ako new ne može kreirati memoriju na slobodnom skladištu (memorija je, ipak, ograničen resurs) on vraća null pokazivač. Vi morate proveriti da li je pokazivač null svaki put kada zahtevate novu memoriju.

Operator delete Pokazivač delete vraća memoriju slobodnom skladištu. Zapamtite da je sam pokazivač, što je

suprotno memoriji na koju on pokazuje, lokalna promenljiva. Po povratku iz funkcije u kojoj je deklarisan, pokazivač izlazi iz opsega i postaje izgubljen. Memorija alocirana sa new se ne oslobađa automatski. Ona postaje neraspoloživa - situacija nazvana memorijska pukotina, jer se ta memorija ne može povratiti, dok se program ne završi.

Da biste vratili memoriju u slobodno skladište, koristite ključnu reč delete. Na primer delete pPointer;

Namena. Operator delete briše podatak ranije napravljen operatorom new. Ako klasa tog podatka ima destruktor, onda se ta funkcija poziva pre nego što se oslobodi memorija.

Sintaksa. Operator delete ima dve verzije: delete pokazivač; delete [] pokazivač;

Pokazivač mora da sadrži adresu memorije koja je prethodno dodeljena rezervisanom reči new. Ako je new korišćena za pravljenje više objekata, koristite drugu verziju kada hoćete da obrišete te objekte. Zagrade ([]) su obavezne.

Primer. int *pl, *p2; p1 = new int; p2 = new int[l00]; // Dodeljuje memoriju nizu. //... delete pl; delete [] p2; // Uočite kako se [] koristi za brisanje niza.

Kada brišete pokazivač, ono što stvarno radite je oslobađanje memorije čija se adresa čuva u pokazivaču. Vi govorite: "Vrati slobodnom skladištu memoriju, na koju ovaj pokazivač pokazuje." Pokazivač je još uvek pokazivač i njemu se ponovo može dodeliti vrednost.

Primer: #include <stdlib.h> class XX { public: void* operator new (size_t sz) { return new char[sz]; } //koristi se za ugrađeni new void operator delete (void *p ) { delete [] p; } //koristi se ugrađeni delete //... };

Primer. Alociranje, korišćenje, i brisanje pokazivaca. 1: // 2: // Alociranje i brisanje pokazivača 3: 4: #include <iostream.h> 5: int main() 6: { 7: int localVariable = 5;

Page 70: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

433

8: int * pLocal= &localVariable; 9: int * pHeap = new int; 10: if (pHeap == NULL) 11: { 12: cout << "Greska! Nema memorije za pHeap!!"; 13: return 0; 14: } 15: *pHeap = 7; 16: cout<<"localVariable: "<<localVariable<<"\n"; 17: cout<<"*pLoca1: "<<*pLocal<<"\n"; 18: cout<<"*pHeap: "<<*pHeap<<"\n"; 19: delete pHeap; 20: pHeap = new int; 21: if (pHeap == NULL) 22: { 23: cout << "Greska! Nema memorije za pHeap!"; 24: return 0; 25: } 26; *pHeap = 9; 27: cout << "*pHeap: " << *pHeap << "\n"; 28: delete pHeap; 29: return 0; 30: }

Izlaz: localVariab1e: 5 *pLocal: 5 *pHeap: 7 *pHeap: 9

Linija 7 deklariše i inicijalizuje lokalnu promenljivu. Linija 8 deklariše i inicijalizuje pokazivač adresom lokalne promenljive. Linija 9 deklariše drugi pokazivač, ali ga inicijalizuje rezultatom, koji je dobijen pozivanjem new int. Ovo alocira prostor na slobodnom skladištu za int. Linija 10 verifikuje da je memorija alocirana i da je pokazivač važeći (ne null). Ako memorija ne može biti alocirana, pokazivač je null i štampa se poruka o grešci.

Linija 15 dodeljuje vrednost 7 novoalociranoj memoriji. Linija 16 štampa vrednost lokalne promenljive, a linija 17 štampa vrednost na koju pokazuje pLocal. Kao što se očekuje, one su jednake. Linija 18 štampa vrednost na koju pokazuje pHeap. Ona pokazuje da je vrednost koja je dodeljena u liniji 15, u stvari, pristupačna.

U liniji 19 memorija alocirana u liniji 9 se vraća slobodnom skladištu, pozivom delete. Ovo oslobađa memoriju rastavlja pokazivač od memorije. Sada je pHeap slobodan da pokazuje na drugu memoriju. Njemu se ponovo dodeljuje vrednost u linijama 20 i 26, a linija 27 štampa rezultat. Linija 28 vraća tu memoriju slobodnom skladištu.

Iako je linija 28 suvišna (kraj programa bi vratio tu memoriju), dobra je ideja osloboditi ovu memoriju eksplicitno. Ako se program promeni, ili proširi, biće korisno što ste se već pobrinuli o ovom koraku.

Primer. Klasa StackOfInt kojom se definiše stek celih brojeva. #include<iostream.h> #include<stdlib.h> class StackOfInt { public: StackOfInt(int); // Konstruktor void push(int); int pop(); int top() const; int size() const;

Page 71: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

434

~StackOfInt(); // Destruktor private: int *data; // Niz podataka int length; // Duzina steka int ptr; // Indeks vrh steka }; StackOfInt::StackOfInt(int stk_size) { data = new int[length = stk_size]; ptr = 0; }; void StackOfInt::push(int x) { if (ptr < length) data[ptr++] = x; else { cout<<"overflow\n"; exit(1); } } int StackOfInt::pop() { if (ptr > 0) return data[--ptr]; else { cout<<"underflow\n"; exit(1); } } int StackOfInt::top() const { if (ptr > 0) return data[ptr-1]; else { cout<<"underflow\n"; exit(1); } } int StackOfInt::size() const { return ptr; } StackOfInt::~StackOfInt() { delete [] data; } #include <iostream> void main() { const int N = 5; StackOfInt stk(N); for (int i = 0; i < N; ++i) stk.push(i); while (stk.size() > -2) cout << stk.pop() << ' '; cout << endl; }

Brisanje objekata Kada pozovete delete za pokazivač na objekat na slobodnom skladištu, poziva se destruktor objekta

pre oslobađanja memorije. Ovo daje klasi šansu da obavi čišćenje, kao što to ona radi za objekte koji se uništavaju na steku.

Primer. Kreiranje i brisanje objekata na slobodnom skladištu. 1: // 2: // Kreiranje objekata na stobodnom skladistu 3: 4: #include <iostream.h> 5: 6: class SimpleCat 7: { 8: public: 9: SimpleCat();

Page 72: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

435

10: ~SimpleCat(); 11: private: 12: int starost; 13: }; 14: 15: SimpleCat::SimpleCat() 16: { 17: cout << "Pozvan konstruktor.\n"; 18: starost = 1; 19: } 20: 21: SimpleCat::~SimpleCat() 22: { 23: cout << "Pozvan destruktor.\n"; 24: } 25: 26: int main() 27: { 28: cout << "SimpleCat Frisky...\n"; 29: SimpleCat Frisky; 30: cout << "SimpleCat *pRags = new Simp1eCat...\n"; 31: SimpleCat *pRags = new SimpleCat; 32: cout << "brise pRags...\n"; 33: delete pRags; 34: cout << "Izlaz, evo ide Frisky ...\n"; 35: return 0; 36: }

Izlaz: SimpleCat Frisky... Pozvan konstruktor. SimpleCat *pRags = new SimpleCat.. Pozvan konstruktor. brise pRags... Pozvan destruktor. Izlaz, evo ide Frisky ... Pozvan destruktor.

Linije 6-13 deklarišu klasu SimpleCat. Linija 9 deklariše konstruktor klase SimpleCat, a linije 15-19 sadrže njenu definiciju. Linija 10 deklariše destruktor klase SimpleCat, a linije 21-24 sadrže njenu definiciju.

U liniji 29 Frisky se kreira na steku, što prouzrokuje zvanje konstruktora. U liniji 31 SimpleCat, na koji pokazuje pRags, kreira se na gomili; konstruktor se ponovo poziva. U liniji 33 delete se poziva za pRags i poziva se destruktor. Kada se funkcija main() završi, Frisky izlazi iz opsega, i poziva se destruktor.

Pristupanje podacima članovima Podacima članovima i funkcijama ste pristupali, korišćenjem operatora tačka (.) za Cat objekte koji

su lokalno kreirani. Da biste pristupili Cat objektu na slobodnom skladištu, morate dereferencirati pokazivač i pozvati operator tačka za objekat na koji pokazuje pokazivač. Da biste pristupili funkciji članici DajStarost, pišete (*Frisky).DajStarost(). Zagrade se koriste da bi se osiguralo da se pRags dereferencira, pre nego što se pristupi DajStarost().

Zato što je ovo nezgodno, C++ obezbeđuje operator za indirektan pristup: operator pokazuje na (->), koji se kreira ukucavanjem crtice (-), posle koje odmah sledi simbol veće od (>). C++ ovo tretira kao jedan simbol.

Primer. Pristupanje promenljivim članicama i funkcijama objekata koji su kreirani na slobodnom skladištu.

1: //

Page 73: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

436

2: // Pristup podacima članovlma objekata koji se nalaze na gomili 3: 4: #include <iostream.h> 5: 6: class SimpleCat 7: { 8: public: 9: SimpleCat() {starost = 2; } 10: ~SimpleCat() {} 11: int DajStarost() const { return starost; } 12: void PostaviStarost (int godine) { starost = godine; } 13: private: 14: int starost; 15: }; 16: 17: int main() 18: { 19: SimpleCat *Frisky = new SimpleCat; 20: cout<< "Frisky ima " << Frisky->DajStarost()<<" godina\n"; 21: Frisky->PostaviStarost(5); 22: cout<<"Frisky ima " << Frisky->DajStarost() << " godina\n"; 23: delete Frisky; 24: return 0; 25: }

Izlaz: Frisky ima 2 godina Frisky ima 5 godina

U liniji 19 SimpleCat objekat se instancira na slobodnom skladištu. Podrazumevani konstruktor postavlja svoju starost na 2 i poziva se metod DajStarost() u liniji 20. Zato što je ovo pokazivač, operator indirekcije (->) se koristi za pristup podacima članovima i funkcijama članicama. U liniji 21 poziva se metod PostaviStarost(), a funkciji DajStarost() se ponovo pristupa u liniji 22.

Podaci članovi na slobodnom skladištu Jedan, ili više podataka članova klase mogu biti pokazivači na objekat na slobodnom skladištu.

Memorija se može alocirati u konstruktoru klase, ili u jednom od njegovih metoda, a može se izbrisati u njenom destruktoru.

Primer. Pokazivači kao podaci članovi. 1: // 2: // Pokazivači kao podaci članovi 3: 4: #include <iostream.h> 5: 6: class SimpleCat 7: { 8: public: 9: SimpleCat(); 10: ~SimpleCat(); 11: int DajStarost() const { return *starost; } 12: void PostaviStarost(int godine) { *starost = godine; } 13: 14: int DajTezinu() const { return *tezina; } 15: void PostaviTezinu(int tez) { *tezina = tez; } 16: 17: private: 18: int *starost; 19: int *tezina; 20: }; 21:

Page 74: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

437

22: SimpleCat::SimpleCat() 23: { 24: starost = new int(2); 25: tezina = new int(5); 26: } 27: 28: SimpleCat::~SimpleCat() 29: { 30: delete starost; 31: delete tezina; 32: } 33: 34: int main() 35: { 36: SimpleCat *Frisky = new SimpleCat; 37: cout<< "Frisky ima "<<Frisky->DajStarost() << " godina\n"; 38: Frisky->PostaviStarost(5); 39: cout<< "Frisky ima "<<Frisky->DajStarost() << " godina\n"; 40: delete Frisky; 41: return 0; 42: }

Izlaz: Frisky ima 2 godina Frisky ima 5 godina

Klasa SimpleCat je deklarisana sa dve promenljive članice - obadve su pokazivači na celobrojne vrednosti, u linijama 18 i 19. Konstruktor (linije 22-26) inicijalizuje pokazivače na memoriju sa slobodnog skladišta i na podrazumevane vrednosti.

Destruktor (linije 28-32) čisti alociranu memoriju. Zato što je ovo destruktor, nema svrhe dodeljivati null pokazivačima, jer oni više neće biti pristupačni. Ovo je jedno od sigurnih mesta gde se može prekršiti pravilo da izbrisanim pokazivačima treba dodeliti null, iako pridržavanje pravila neće smetati.

U pozivajućoj funkciji (u ovom slučaju, main()) ne bi trebalo brinuti da su starost i tezina pokazivači na memoriju sa slobodnog skladišta. Zbog toga main() poziva metode DajStarost() i PostviStarost(), a detalji upravljanja memorijom su sakriveni u implementaciji klase, kao što i treba.

Kada se Frisky izbriše u liniji 40, poziva se njegov destruktor, koji briše svaki njegov pokazivač član. Ako oni, dalje, pokazuju na objekte drugih korisnički definisanih klasa, njihovi destruktori se takođe pozivaju.

Prenos objekata po referenci Svaki put kada predate objekat funkciji po vrednosti, pravi se njegova kopija. Svaki put kada

vratite objekat iz funkcije po vrednosti, pravi se druga kopija. Ovi objekti se kopiraju na stek. Zato ovo uzima vreme i memoriju. Za male objekte, kao što su ugrađene celobrojne vrednosti, to je trivijalna cena. Ipak, za veće korisnički kreirane objekte, cena je veća. Veličina korisnički kreiranih objekata na steku je suma svih njegovih promenljivih članica. Svaka od njih, dalje, može biti korisnički kreiran objekat i predavanje tako masivne strukture njenim kopiranjem na stek može biti veoma skupo zbog performansi i korišćenja memorije.

Postoji, takođe, i druga cena. Sa klasama koje kreirate, svaka ova privremena kopija se kreira kada kompajler pozove specijalan konstruktor: konstruktor kopije.

Kada se privremeni objekat uništava, što se dešava po povratku iz funkcije, poziva se destruktor objekta. Ako se neki objekat vraća iz funkcije po vrednosti, takođe se mora napraviti i uništiti kopija tog objekta. Sa velikim objektima, ovi pozivi konstruktora i destruktora mogu biti skupi zbog smanjenja brzine i povećanja korišćenja memorije. Radi ilustracije ove ideje, u programu koji sledi kreiran je pojednostavljen korisnički kreiran objekat: SimpleCat. Pravi objekat bi bio veći i skuplji, ali ovo je dovoljno da bi se pokazalo koliko često se pozivaju konstruktor kopije i destruktor.

Page 75: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

438

U programu se kreira objekat SimpleCat, a, onda, poziva dve funkcije. Prva funkcija prima Cat po vrednosti, a potom ga vraća po vrednosti. Druga prima pokazivač na objekat, a ne sam objekat, i vraća pokazivač na objekat.

Primer. Predavanje objekato po referenci. 1: // 2: // Predavanje pokazivača na objekte 3: 4: #include <iostream.h> 5: 6: class SimpleCat 7: { 8: public: 9: SimpleCat (); // konstruktor 10: SimpleCat(SimpleCat&); // konstruktor kopije 11: ~SimpleCat(); // destruktor 12: }; 13: 14: SimpleCat::SimpleCat() l5: { 16: cout << "Jednostavan Cat konstruktor...\n"; 17: } 18: 19: SimpleCat::SimpleCat(SimpleCat&) 20: { 21: cout << "Jednostavan Cat Copy konstruktor...\n"; 22: } 23: 24: SimpleCat::~SimpleCat() 25: { 26: cout << "Jednostavan Cat destructor...\n"; 27: } 28: 29: SimpleCat FunctionOne (SimpleCat theCat); 30: SimpleCat* FunctionTwo (SimpleCat *theCat); 31: 32: int main() 33: { 34: cout << "Kreiranje cat...\n"; 35: SimpleCat Frisky; 36: cout << "Poziv FunctionOne...\n"; 37: FunctionOne(Frisky); 38: cout << "Poziv FunctionTwo...\n"; 39: FunctionTwo(&Frisky); 40: return 0; 41: } 42: 43: // FunctionOne, predaje po vrednosti 44: SimpleCat FunctionOne(SimpleCat theCat) 45: { 46: cout << "Function One. Povratak...\n"; 47: return theCat; 48: } 49: 50: // functionTwo, predaje po referenci 51: SimpleCat* FunctionTwo (SimpleCat *theCat) 52: { 53: cout << "Function Two. Povratak...\n"; 54: return theCat; 55: }

Izlaz:

Page 76: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

439

1: Kreiranje cat... 2: Jednostavan cat konstruktor... 3: Poziv FunctionOne... 4: Jednostavan Cat Copy konstructor... 5: Function One. Povratak... 6: Jednostavan Cat Copy Constructor... 7: Jednostavan Cat Destructor... 8: Jednostavan Cat Destructor... 9: Poziv FunctionTwo... 10: Function Two. Povratak... 11: Jednostavan Cat Destructor...

Napomena. Brojevi linija se neće odštampati. Oni su dodati, radi pomoći u analizi.

Veoma pojednostavljena klasa SimpleCat je deklarisana u linijama 6-12. Konstruktor, konstruktor kopije i destruktor štampaju informativnu poruku, tako da možete znati kada su oni pozvani.

U liniji 34 main() štampa poruku, a ona se vidi u izlazu 1. U liniji 35 instancira se objekat SimpleCat. Ovo prouzrokuje da se pozove konstruktor, a izlaz iz konstruktora se vidi na izlaznoj liniji 2.

U liniji 36 main() izveštava da poziva funkciju FunctionOne, što kreira izlaznu liniju 3. Zato što se FunctionOne() poziva predavanjem SimpleCat objekta po vrednosti, kopija SimpleCat objekta se pravi na steku, kao lokalni objekat za pozvanu funkciju. Ovo prouzrokuje da se pozove konstruktor kopije, što kreira izlaznu liniju 4.

Izvršenje programa skače na liniju 46 u pozvanoj funkciji, koja štampa informativnu poruku, izlaznu liniju 5. Zatim se ostvaruje povratak iz funkcije i ona vraća SimpleCat objekat po vrednosti. Ovo kreira drugu kopiju objekta, pozivajući konstruktor kopije i proizvodeći liniju 6.

Povratna vrednost iz FunctionOne() se ne dodeljuje nijednom objektu i tako privremeno kreirani za povratak se baca, pozivajući destruktor, što proizvodi izlaznu liniju 7. Pošto je FunctionOne() završena, njena lokalna kopija izlazi iz opsega i uništava se, poziva se destruktor i proizvodi linija 8.

Izvršenje programa se vraća u main(), a poziva se FunctionTwo(), ali se parametar predaje po referenci. Ne proizvodi se kopija, pa, zato, nema izlaza. FunctionTwo() štampa poruku, koja se pojavljuje kao izlazna linija 10, a, onda, vraća SimpleCat objekat, ponovo po referenci, i tako ponovo ne proizvodi pozive konstruktora i destruktora.

Konačno, program se završava i Frisky izlazi iz opsega, prouzrokujući finalni poziv destruktora i štampajući izlaznu liniju 11.

Efekat je sledeći: da je poziv funkcije FunctionOne(), jer je ona predala objekat po vrednosti, proizveo je dva poziva konstruktora kopije i dva destruktora, dok poziv funkcije FunctionTwo() nije proizveo ni jedan.

Prenos const pokazivača Iako je predavanje pokazivača funkciji FunctionTwo() efikasnije, ono je opasno. Ne planira se da

funkciji FunctionTwo() bude dozvoljeno da promeni SimpleCat objekat, koji joj se predaje, ali joj se još uvek daje adresa od SimpleCat. Ovo ozbiljno izlaže objekat promeni i narušava zaštitu koja je ponuđena u predavanju po vrednosti.

Rešenje je da se preda const pokazivač na SimpleCat. Ovo sprečava pozivanje svake ne - const metode za SimpleCat i time se štiti objekat od promene.

Primer. Predavanje const pokazivača. 1: // 2: // Predavanje pokazivaca na objekte 3: 4: #include <iostream.h> 5: 6: class SimpleCat

Page 77: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

440

7: { 8: public: 9: SimpleCat(); 10: SimpleCat(SimpleCat&); 11: ~SimpleCat(); 12: 13: int DajStarost() const { return starost; } 14: void PostaviStarost(int godine) { starost = godine; } 15: 16: private: 17: int starost; 18: }; 19:

20: SimpleCat::SimpleCat() 21: { 22: cout << "Simple Cat Constructor...\n"; 23: starost =1; 24: } 25: 26: SimpleCat::SimpleCat(SimpleCat&) 27: { 28: cout << "Simple Cat Copy. Constructor...\n"; 29: } 30: 31: SimpleCat::~SimpleCat() 32: { 33: cout << "SimpleCat Destructor...\n"; 34: } 35: 36: const SimpleCat *const FunctionTwo (const SimpleCat *const theCat); 37: 38: int main() 39: { 40: cout << "Making a cat...\n"; 41: SimpleCat Frisky; 42: cout << "Frisky is " ; 43: cout << Frisky.DajStarost(); 44: cout << " years old\n"; 45: int godine = 5; 46: Frisky.PostaviStarost(godine); 47: cout << "Frisky is " ; 48: cout << Frisky.DajStarost(); 49: cout << " years old\n"; 50: cout << "Calling FunctionTwo...\n"; 51: FunctionTwo(&Frisky); 52: cout << "Frisky is " ; 53: cout << Frisky.DajStarost(); 54: cout << " years old\n"; 55: return 0; 56: } 57: 58: // functionTwo, predaje const pokazivač 59: const SimpleCat *const FunctionTwo(const SimpleCat *const theCat) 60: { 61: cout << "Function Two. Returning...\n"; 62; cout << "Frisky is now " << theCat->DajStarost(); 63: cout << " years old \n"; 64: // theCat->PostaviStarost(8); konstantan! 65: return theCat; 66: }

Page 78: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

441

Making a cat... Simple Cat constructor... Frisky is 1 years o1d Frisky is 5 years o1d Calling FunctionTwo... FunctionTwo. Returning... Frisky is now 5 years old Frsky is 5 years old Simple Cat Destructor...

SimpleCat je dodala dve fiinkcije pristupa, PostaviStarost() u liniji 13, koja je const funkcija, i PostaviStarost() u liniji 14, koja nije const funkcija. Takođe je dodala promenljivu članicu starost u liniji 17.

Konstruktor, konstruktor kopije i destruktor su još uvek definisani da štampaju svoje poruke. Ipak, konstruktor kopije se nikada ne poziva, zato što se objekat predaje po referenci, pa se ne prave kopije. U liniji 41 kreira se objekat i štampa se njegova podrazumevana starost, počevši od linije 42.

U liniji 46 starost se postavlja korišćenjem metoda pristupa PostaviStarost, a rezultat se štampa u liniji 47. Metod FunctionOne se ne koristi u ovom programu, ali FunctionTwo() se poziva. FunctionTwo() je promenjena malo; parametar i povratna vrednost su sada deklarisani, u liniji 36, da uzimaju konstantni pokazivač na konstantni objekat i da vrate konstantan pokazivač na konstantan objekat.

Zato što se parametar i povratna vrednost još uvek predaju po referend, ne prave se kopije i konstruktor kopije se ne poziva. Ipak, pokazivač u FunctionTwo() je sada konstantan i zato ne može pozvati ne-const metod, PostaviStarost(). Da poziv funkđe PostaviStarost() u liniji 64 nije isključen komentarom, program se ne bi kompajlirao.

Uočite da objekat kreiran u main() nije konstantan, a Frisky može pozvati PostaviStarost(), Adresa ovog nekonstantnog objekta se predaje funkđi FunctionTwo(), ali zato što deklaracija funkđe FunctionTwo() deklariše pokazivač kao konstantan, objekat se tretira kao konstantan!

Reference kao alternativa

Listing 9.11 rešava problem kreiranja dodatnih kopija i time štedi pozive konstruktora kopije i destruktora. On koristi konstantne pokazivače na konstantne objekte i tako rešava problem funkcije koja menja objekat. Ipak, on je još uvek u izvesnoj meri nezgodan, zato što su objekti predati funkciji pokazivači.

Pošto znate da objekat neće nikada biti null, bilo bi lakše raditi sa njim u funkciji kada bi referenca bila predata, a ne pokazivač. Listing 9.12 ilustruje ovo.

Listing 9.12. Predavanje referenci na objekte. 1: // 2: // Predavanje referenci na objekte 3: 4: #include <iostream.h> 5: 6: class SimpleCat 7: { 8: public: 9: SimpleCat(); 10: SimpleCat(SimpleCat&); 11: ~SimpleCat(); 12: 13: int DajStarost() const { return starost; } 14: void PostaviStarost(int godine) { starost = godine; } 15: 16: private: 17: int starost; 18: };

Page 79: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

442

19: 20: SimpleCat::SimpleCat() 21: { 22: cout << "Simple Cat Constructor...\n"; 23: starost =1; 24: } 25: 26: SimpleCat::SimpleCat(SimpleCat&) 27: { 28: cout << "Simple Cat Copy Constructor...\n"; 29: } 30: 31: SimpleCat::~SimpleCat() 32: { 33: cout << "SimpleCat Destructor...\n"; 34: } 35: 36: const SimpleCat & FunctionTwo (const SimpleCat & theCat); 37: 38: int main() 39: { 40: cout << "Making a cat...\n"; 41: SimpleCat Frisky; 42: cout << "Frisky is " << Frisky.DajStarost() << " years old\n"; 43: int godine = 5; 44: Frisky.PostaviStarost(godine); 45: cout << "Frisky is " << Frisky.DajStarost()<< " years old\n"; 46: cout << "Calling FunctionTwo...\n"; 47: FunctionTwo(Frisky); 48: cout << "Frisky is " << Frisky.DajStarost() << " years old\n"; 49: return 0; 50: } 51: 52: // functionTwo, predaje referencu na const objekat 53: const SimpleCat & FunctionTwo (const SimpleCat & theCat) 54: { 55: cout << "Function Two. Returning...\n"; 56: cout << "Frisky is now " << theCat.DajStarost(); 57: cout << " years old \n"; 58: // theCat.PostaviStarost(8); konstantan! 59: return theCat; 60: }

Making a cat... Simple Cat constructor... Frisky is 1 years o1d Frisky is 5 years o1d CaHing FunctionTwo... FunctionTwo. Returning... Frisky is now 5 years o1d Frisky is 5 years o1d Simple Cat Destructor...

Izlaz je identičan onom koji je proizveden u prethodnom primeru. Jedina značajna promena je da FunctionTwo() sada prihvata i vraća referencu na konstantan objekat. Rad sa referencama je jednostavniji od rada sa pokazivačima, a postižu se iste uštede i efikasnost, kao i sigurnost, korišćenjem const.

Const reference C++ programeri, obično, ne razlikuju "konstantnu referencu na SimpleCat objekat" od "reference

na konstantan SimpleCat objekat". Samim referencama se nikada ne može ostvariti ponovna dodela da

Page 80: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

443

bi one upućivale na drugi objekat, pa su, zato, one uvek konstantne. Ako se reč const primeni na referencu, to će objekat na koji se upućuje učiniti konstantnim.

Kada koristiti reference, a kada pokazivače? C++ programeri izrazito više vole reference. One su čistije i lakše za korišćenje i obavljaju bolje

posao skrivanja informacija, kao što smo videli u prethodnom primeru.

Nad referencama se ne može ostvariti ponovna dodela. Ako je potrebno da prvo pokazujete na jedan objekat, a, onda, na drugi, morate koristiti pokazivač. Reference ne mogu biti null, pa, ako postoji šansa da objekat na koji upućuju može biti null, ne smete koristiti referencu. Morate koristiti pokazivač.

Na kraju, recimo nešto i o operatoru new. Ako new ne može alocirati memoriju na slobodnom skladištu, on vraća null pokazivač. Pošto referenca ne može biti null, Ne smete inicijalizovati referencu na ovu memoriju, dok ne ispitate da li je ona null.

Sledeći primer prikazuje kako da rukujete ovim: int *pint = new int; if (pint != NULL) int &rint = *pint

U ovom primeru pokazivač na int, pint, je deklarisan i inicijalizovan sa memorijom koja je vraćena operatorom new. Adresa u pint se testira i, ako nije null, pint se dereferencira. Rezultat dereferenciranja int promenljive je int objekat, a rlnt se inicijalizuje da upućuje na taj objekat. Tako, pint postaje alijas na int, koji je vraćen operatorom new.

Napomena. Predajte parametre po referenci, uvek kada je to moguće. Vratite po referenci uvek, kada je to moguće. Nemojte koristiti pokazivace, ako ce reference raditi. Upotrebite const da biste zastitili reference i pokazivače, uvek kada je to moguće. Nemojte vratiti referencu na lokalni objekat.

9.10. Zajednički članovi klasa

9.10.1. Zajednički podaci članovi

Pri nastanku objekata klase, za svaki objekat se pravi poseban komplet podataka članova. Ipak, moguće je definisati podatke članove za koje postoji samo jedan primerak za celu klasu, tj. za sve objekte klase.

Ovakvi članovi se nazivaju statičkim članovima, i deklarišu se pomoću reči static: class X {

public: //... private: static int i; // postoji samo jedan i za celu klasu int j; // svaki objekat ima svoj j //...

}

Svaki pristup statičkom članu iz bilo kog objekta klase znači pristup istom zajedničkom članu - objektu.

Statički član klase ima životni vek kao i globalni statički objekat: nastaje na početku programa i traje da kraja programa. Statički član klase ima sva svojstva globalnog statičkog objekta, osim oblasti važenja i kontrole pristupa.

Page 81: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

444

Statički član mora da se inicijalizuje posebnom definicijom van deklaracije klase. Obraćanje ovakvom članu van klase vrši se preko operatora : :. Za prethodni primer:

int X:: i=5;

Statičkom članu može se pristupiti iz funkcije članice, ali i van funkcija članica, čak i pre formiranja ijednog objekta klase, naravno uz poštovanje prava pristupa. Tada mu se pristupa preko operatora : : (X : : i).

Zajednički članovi se uglavnom koriste kada svi primerci jedne klase treba da dele neku zajedničku informaciju, na primer kada predstavljaju neku kolekciju, odnosno kada je potrebno imati ih “sve na okupu i pod kontrolom”. Na primer svi objekti neke klase se uvezuju u listu, a glava liste je zajednički član klase.

Zajednički članovi smanjuju potrebu za globalnim objektima i tako povećavaju čitljivost programa, jer je, za razliku od globalnih objekata, moguće ograničiti pristup do njih. Zajednički članovi logički pripadaju klasi i “upakovani” su u nju.

9.10.2. Zajedničke funkcije članice

I funkcije članice mogu da se deklarišu kao zajedničke za celu klasu, dodavanjem reči static ispred deklaracije funkcije članice.

Statičke funkcije članice imaju sva svojstva globalnih funkcija, osim oblasti važenja i kontrole pristupa. One poseduju pokazivač this i ne mogu neposredno (bez pominjanja konkretnog objekta klase) koristiti nestatičke članove klase. Statičke funkcije mogu neposredno koristiti samo statičke članove te klase.

Statičke funkcije članice mogu se pozivati za konkretan objekat, ali i pre formiranja ijednog objekta klase, preko operatora : :.

Primer. class X { static int x; // statički podatak član; int y; public: static int f(X,X&); //statička funkcija članica; int g(); };

int X:: x=5; //definicija statičkog podatka člana; int X:: f(X x1, X& x2) { //definicija statičke funkcije članice; int i=x; // pristup statičkom članu X: :x; int j=y; // greška: X: :y nije statički, // pa mu se ne može pristupiti neposredno! int k=x1.y; // ovo može; return x2.x; // i ovo može, } // ali se izraz “x2” ne izračunava;

int X: : g () { int i=x; //nestatička funkcija članica može da int j=y // koristi i pojedinačne i zajedničke return j; // članove; y je ovde this->y; }

void main () { X xx; int p=X: :f(xx,xx); // X: :f može neposredno, bez objekta; int q=X: :g(); // greška: za X:: g mora konkretan objekat! xx.g(); // ovako može; p=xx.f(xx,xx); // i ovako može, } // ali se izraz “xx” ne izračunava;

Page 82: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

445

Statičke funkcije predstavljaju operacije klase, a ne svakog posebnog objekta. Pomoću njih se definišu neke opšte usluge klase, na primer tipično stvaranje novih, dinamičkih objekata te klase (operator new je implicitno definisan kao statička funkcija klase). Na primer, na sledeći način obezbeđuje se da se za datu klasu mogu formirati samo dinamički objekti:

class X{ public: static X *create() { return new X; } private: X (); //konstruktor je privatan

};

9.11. Napredne funkcije

9.11.1. Preklopljene funkcije članice

Naučili ste da implementirate funkcijski polimorfizam, ili preklapanje funkcija, pisanjem dve, ili više funkcija, sa istim imenom, ali sa različitim parametrima. Funkcije članice klase se takođe mogu preklopiti, na veoma sličan način.

Klasa Rectangle ima dve funkcije DrawShape(). Jedna, koja ne uzima parametre, crta Rectangle (engl. rectangle = pravougaonik), bazirajući se na tekućim vrednostima klase. Druga prihvata dve vrednosti, width i length, i crta pravougaonik, bazirajući se na tim vrednostima, ignorišući tekuće vrednosti klase.

Primer. Preklapanje funkcija clanica. 1: // Preklapanje funkcija članica klasa 2: #include <iostream.h> 3: 4: typedef unsigned short int USHORT; 5: enum BOOL { FALSE, TRUE}; 6: 7: // Deklaracija za klasu Rectangle 8: class Rectangle 9: { 10: public: 11: // konstruktori 12: Rectangle(USHORT width, USHORT height); 13: ~Rectangle(){} 14: 15: // preklopljena klasna funkcija DrawShape 16: void DrawShape() const; 17: void DrawShape(USHORT width, USHORT height) const; 18: 19: private: 20: USHORT itsWidth; 21: USHORT itsHeight; 22: }; 23: 24: //Implementacija konstruktora 25: Rectangle::Rectangle(USHORT width, USHORT height) 26: { 27: itsWidth = width; 28: itsHeight = height; 29: } 30: 31: 32: // Preklopljena DrawShape - ne prihvata vrednosti 33: // Crta bazirajući se na tekućim vrednostima članova klase

Page 83: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

446

34: void Rectangle::DrawShape() const 35: { 36: DrawShape( itsWidth, itsHeight); 37: } 38: 39: 40: // preklopljena DrawShape - prihvata dve vrednosti 41: // crta oblik bazirajući se na parametrima 42: void Rectangle::DrawShape(USHORT width, USHORT height) const 43: { 44: for (USHORT i = 0; i<height; i++) 45: { 46: for (USHORT j = 0; j<width; j++) 47: { 48: cout << "*"; 49: } 50: cout << "\n"; 51: } 52: } 53: 54: // Demonstracioni program 55: int main() 56: { 57: // inicijalizuje pravougaonik na 30,5 58: Rectangle theRect(30,5); 59: cout << "DrawShape():\n"; 60: theRect.DrawShape(); 61: cout << "\nDrawShape(40,2):\n"; 62: theRect.DrawShape(40,2); 63: return 0; 64: }

DrawShape():

****************************** ****************************** ****************************** ****************************** ******************************

DrawShape(40,2):

****************************************

****************************************

Važan kod je u linijama 16 i 17, gde je preklopljena funkcija DrawShape(). Implementacija za ove preklopljene klasne metode je u linijama 32-52. Uočite da verzija funkcije DrawShape(), koja ne prihvata parametre, jednostavno, poziva verziju koja prihvata dva parametra, predajući tekuće promenljive članice. Uložite napor da izbegnete dupliranje koda u dve funkcije. Inače, održavanje njihove sinhronizacije kada se naprave promene u jednoj, ili drugoj će biti teško i podložno greškama.

Kontinuelni program, u linijama 54-64, kreira objekat pravougaonika, a, onda, poziva DrawShape(), ne predajući parametre, i predajući dve unsigned short celobrojne vrednosti.

Kompajler odlučuje koji metod da pozove, bazirajući se na broju i tipu navedenih parametara. Neko može zamisliti treću preklopljenu funkciju, nazvanu DrawShape(), koja prihvata jednu dimenziju i enumeraciju za to da li je u pitanju širina ili visina, u zavisnosti od korisnikovog izbora.

Page 84: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

447

Podrazumevani konstruktor Ako ne deklarišete eksplicitno konstruktor za klasu, kreira se podrazumevani konstruktor, koji ne

prihvata parametre i ne radi ništa. Vi imate slobodu da napravite sopstveni podrazumevani konstruktor, koji ne prihvata argumente, ali "postavlja" objekat prema zahtevu.

Tako obezbeđen konstruktor se naziva "podrazumevani" konstruktor. Po konvenciji, to je svaki konstruktor koji ne prihvata parametre. Ovo može biti malo zbunjujuće, ali obično je jasno iz konteksta na koji se misli. Obratite pažnju da, ako napravite bilo kakav konstruktor, kompajler ne pravi podrazumevani konstruktor. Zato, ako želite konstruktor koji ne prihvata parametre, a kreirali ste neke druge konstruktore, morate sami napraviti podrazumevani konstruktor.

Preklapajući konstruktori Namena konstruktora je da ustanovi objekat; na primer, namena konstruktora klase Rectangle je da

napravi pravougaonik. Pre izvršenja konstruktora, nema pravougaonika, samo područje memorije. Kada konstruktor završi, postoji kompletan, spreman za korišćenje objekat pravougaonika.

Na primer, možda ćete imati objekat rectangle sa dva konstruktora. Prvi prihvata dužinu i širinu i pravi pravougaonik te veličine. Drugi ne prihvata nikakve vrednosti i pravi pravougaonik podrazumevane veličine.

Primer. Preklapanje konstruktora. 1: // 2: // Preklapajuci konstruktori 3: 4: #include <iostream.h> 5: 6: class Rectangle 7: { 8: public: 9: Rectangle(); 10: Rectangle(int width, int length); 11: ~Rectangle() {} 12: int GetWidth() const { return itsWidth; } 13: int GetLength() const { return itsLength; } 14: private: 15: int itsWidth; 16: int itsLength; 17: }; 18: 19: Rectangle::Rectangle() 20: { 21: itsWidth = 5; 22: itsLength = 10; 23: } 24: 25: Rectangle::Rectangle (int width, int length) 26: { 27: itsWidth = width; 28: itsLength = length; 29: } 30: 31: int main() 32: { 33: Rectangle Rect1; 34: cout << "Rect1 width: " << Rect1.GetWidth() << endl; 35: cout << "Rect1 length: " << Rect1.GetLength() << endl; 36: 37: int aWidth, aLength; 38: cout << "Enter a width: ";

Page 85: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

448

39: cin >> aWidth; 40: cout << "\nEnter a length: "; 41: cin >> aLength; 42: 43: Rectangle Rect2(aWidth, aLength); 44: cout << "\nRect2 width: " << Rect2.GetWidth() << endl; 45: cout << "Rect2 length: " << Rect2.GetLength() << endl; 46: return 0; 47: }

Rect1 width: 5 Rectl length: 10 Enter a width: 20

Enter a length: 50

Rect2 width: 20 Rect2 length: 50.

Klasa Rectangle je deklarisana u linijama 6-17. Deklarisana su dva konstruktora: "podrazumevani konstruktor" u liniji 9 i konstruktor koji prihvata dve celobrojne promenljive.

U liniji 33 kreira se pravougaonik, korišćenjem podrazumevanog konstruktora, i štampaju se njegove vrednosti u linijama 34 i 35. U linijama 37-41 od korisnika se traže širina i dužina i poziva se konstruktor, koji prihvata dva parametra u liniji 43. Konačno, širina i dužina za ovaj pravougaonik se štampaju u linijama 44 i 45.

Kao što radi sa svakom preklopljenom funkcijom, kompajler bira ispravni konstruktor, bazirajući se na broju i tipu parametara.

Konstruktor kopije Pored podrazumevanih konstruktora i destruktora, kompajler obezbeđuje podrazumevani

konstruktor kopije, koji se poziva svaki put kada se napravi kopija objekta.

Kada prosleđujete objekat po vrednosti, bilo da ga predajete funkciji, ili kao povratnu vrednost iz funkcije, pravi se privremena kopija tog objekta. Ako je objekat korisnički definisan objekat, poziva se konstruktor kopije njegove klase.

Svi konstruktori kopije prihvataju jedan parametar, referencu na objekat iste klase. Dobra ideja je učiniti ga konstantnom referencom, zato što konstruktor neće morati da promeni predati objekat. Na primer:

CAT(const CAT & theCat);

Ovde CAT konstruktor prihvata konstantnu referencu na postojeći cat objekat. Cilj konstruktora kopije je da napravi kopiju od theCat. Podrazumevani konstruktor kopije, jednostavno, kopira svaku promenljivu članicu iz objekta, koji je predat kao parametar u promenljive članice novog objekta. Ovo se naziva člansko-odnosna (ili plitka) kopija i, iako je ovo dobro za većinu promenljivih članica, ona pada veoma brzo za promenljive članice, koje su pokazivači na objekte sa slobodnog skladišta.

Plitka, ili člansko-odnosna kopija kopira tačne vrednosti promenljivih, koje su članice objekta, u drugi objekat. Pokazivači u oba objekta završavaju, pokazujući na istu memoriju. Duboka kopija kopira vrednosti alocirane na gomili u novo alociranu memoriju.

Kada bi klasa CAT imala promenljivu članicu, itsAge, koja pokazuje na celobrojnu vrednost sa slobodnog skladišta, podrazumevani konstruktor kopije bi kopirao predatu promenljivu članicu itsAge klase CAT u novu promenljivu članicu itsAge klase CAT. Dva objekta bi tada pokazivala na istu memoriju.

Ovo će dovesti do katastrofe kada bilo koji CAT izađe iz opsega. Posao destruktora je da očisti ovu memoriju. Ako destruktor originalnog CAT oslobodi ovu memoriju, a novi CAT još uvek pokazuje na nju, kreiran je izgubljeni pokazivač, a program je u smrtnoj opasnosti. Slika ilustruje ovaj problem.

Page 86: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

Rešenje je da kreirate sopstveni konstruktor kopije i da alocirate memoriju prema potrebi. Pošto je memorija alocirana, stare vrednosti se mogu kopirati u novu memoriju. Ovo se naziva duboka kopija.

Primer. Konstruktori kopije. 1: // 2: // Konstruktori kopije 3: 4: #include<iostream.h> 5: 6: class CAT 7: { 8: public: 9: CAT(); // podrazumevani konstruktor 10: CAT (const CAT &); // konstruktor kopije 11: ~CAT(); // destruktor 12: int DajStarost() const { return *starost; } 13: int DajTezinu() const { return *tezina; } 14: void PostaviStarost(int godine) { *starost = godine; } 15: 16: private: 17: int *starost; 18: int *tezina; 19: }; 20: 21: CAT::CAT() 22: { 23: starost = new int; 24: tezina = new int; 25: *starost = 5; 26: *tezina = 9; 27: } 28: 29: CAT::CAT(const CAT & rhs) 30: { 31: starost = new int; 32: tezina = new int; 33: *starost = rhs.DajStarost(); 34: *tezina = rhs.DajTezinu(); 35: } 36: 37: CAT::~CAT() 38: { 39: delete starost; 40: starost = 0; 41: delete tezina; 42: tezina = 0; 43: } 44: 45: int main() 46: { 47: CAT frisky;

Page 87: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

48: cout << "friskyjeva starost: " << frisky.DajStarost() << endl; 49: cout << "Postavljanje friskyja na 6...\n"; 50: frisky.PostaviStarost(6); 51: cout << "Kreiranje boots-a iz frisky-ja\n"; 52: CAT boots(frisky); 53: cout << "friskyjeva starost: "<< frisky.DajStarost()<<endl; 54: cout << "bootsova starost: " << boots.DajStarost() << endl; 55: cout << "postavlja friskyja na 7...\n"; 56: frisky.PostaviStarost(7); 57: cout << "friskyjeva starost: " << frisky.DajStarost() << endl; 58: cout << "bootsova starost: " << boots.DajStarost() << endl; 59: return 0; 60: }

friskyjeva starost: 5 Postavlja friskyja na 6... Kreiranje boots-a za friskyja friskyjeva starost: 6 bootsova starost: 6 postavlja friskyja na 7... friskyjeva starost: 7 bootsova starost: 6

U linijama 6-19 deklariše se klasa CAT. Uočite da je u liniji 9 deklarisan podrazumevani konstruktor, a u liniji 10 konstruktor kopije.

U linijama 17 i 18 deklarišu se dve promenljive članice, svaka kao pokazivač na celobrojnu vrednost. Obično ima malo razloga da klasa čuva int promenljive članice kao pokazivače, ali ovo je urađeno da bi se ilustrovalo kako upravljati promenljivim članicama na slobodnom skladištu.

Konstruktor kopije počinje u liniji 29. Uočite da je parametar rhs. Uobičajeno je kao parametar konstruktora kopije koristiti rhs, što znači desnu stranu (engl. right-hand side). Kada pogledate dodele u linijama 33 i 34, videćete da je objekat predat kao parametar na desnoj strani znaka jednakosti.

U linijama 31 i 32 alocira se memorija na slobodnom skladištu. Zatim, u linijama 33 i 34 vrednosti na novoj memorijskoj lokaciji se dodeljuju vrednosti iz postojećeg CAT. Parametar rhs je CAT koji se predaje konstruktoru kopije kao konstantna referenca.

Funkcija članica rhs.DajStarost() vraća vrednost čuvanu u memoriji, na koju pokazuje starost promenljiva članica od rhs. Kao CAT objekat, rhs ima sve promenljive članice kao i bilo koja CAT.

Kada se pozove konstruktor kopije za kreiranje novog CAT, postojeći CAT se predaje kao parametar. Novi CAT može direktno upućivati na sopstvene promenljive članice; ipak, on mora pristupiti promenljivim članicama od rhs, korišćenjem javnih metoda pristupa.

Slika prikazuje dijagram o tome šta se ovde dešava. Vrednosti na koje pokazuje postojeći CAT se kopiraju u memoriju, koja je alocirana za novi CAT.

U liniji 47 kreira se CAT, nazvan frisky. Štampa se Friskyjeva starost, a onda se ona postavlja na 6 u

liniji 50. U liniji 52 kreira se novi CAT nazvan boots, korišćenjem konstruktora kopije i predavanjem

Page 88: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

451

frisky-ja. Da je frisky predat kao parametar funkciji, ovaj isti poziv konstruktora kopije napravio bi kompajler.

U linijama 53 i 54 štampaju se starosti oba CAT. Stvarno, boots ima frisky-jevu starost, 6, a ne podrazumevanu starost 5. U liniji 56 frisky-jeva starost se postavlja na 7, a onda se starosti ponovo štampaju. Ovog puta frisky-jeva starost je 7, ali boots-ova je još uvek 6, što demonstrira da se one čuvaju u odvojenim područjima memorije.

Kada CAT-ovi izađu iz opsega, njihovi destruktori se automatski pozivaju. Implementacija destruktora klase CAT je prikazana u linijama 37-43; delete se poziva za oba pokazivača, itsAge i itsWeight, što vraća alociranu memoriju slobodnom skladištu. Takođe, zbog bezbednosti, pokazivačima se dodeljuje vrednost NULL.

9.13. Operatorske funkcije

9.13.1. Osnovna pravila

U jeziku C++, pored običnih funkcija koje se eksplicitni pozivaju navođenjem identifikatora u zagradama, postoje i operatorske funkcije.

Operatorske funkcije su posebne vrste funkcija koje imaju posebna imena i način pozivanja, i one se mogu preklopiti za operande koji pripadaju korisničkim tipovima. Ovaj princip se naziva preklapanje operatora. on omogućava da se definišu značenja za korisničke tipove i formiraju operacije nad objektima ovih tipova, na primer operacije nad kompleksnim brojevima, matricama itd.

Postoje neka ograničenja u preklapanju operatora. Ne mogu se: 1. preklapati operatori .,.*,: :,?: i sizeof, dok svi ostali mogu; 2. definisati značenja operatora za ugrađene tipove podataka; 3. uvoditi novi simboli za operatore; 4. menjati osobine operatora koje su ugrađene u jezik: broj operanada, prioriteti i

asocijativnost

Operatorske funkcije imaju imena operator@ gde je @ znak operatora. Operatorske funkcije mogu biti članice ili globalne funkcije kod kojih je bar jedan argument tipa korisničke klase.

9.13.2. Pojam preklapanja operatora

C++ ima nekoliko ugrađenih tipova, uključujući int, real, char i tako dalje. Svaki od njih ima nekoliko ugrađenih operatora, kao što su sabiranje (+) i množenje (*). C++ omogućava da dodate ove operatore klasama. Da bismo potpuno proučili preklapanje operatora, sledeći listing kreira novu klasu, Counter. Counter objekat će se koristiti za brojanje u petljama i drugim aplikacijama, gde broj mora biti inkrementiran, dekrementiran, ili na neki drugi način održavan.

operator Namena. Definiše funkciju operatora (kao što su + ili *) kada se primenjuju na posebne tipove. Ona govori programu kako da ,,sabira" (ili množi ili deli) objekte tih tipova.

Sintaksa. povratni_tip operator@ (args)

{ iskazi }

Ovde @ zamenjuje bilo koji operator u jeziku C++. Ne možete izmišljati sopstvene operatore kao što su ##%@$!!

Primer. Sledeći kod definiše kako se sabiranje izvodi na dva objekta klase Ctacka:

Page 89: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

452

CTacka operator+ (CTacka tacl, CTacka tac2) { CTacka nova_tacka; nova_tacka.x = tacl.x + tac2.y; nova_tacka.y = tacl.y + tac2.y; return nova_tacka; }

U ovom primeru se pretpostavlja da klasa CTacka ima dva člana: x i y. Funkcija operatora može biti definisana bilo kao funkcija članica, bilo kao globalna funkcija. Ako je definisana kao funkcija članica, onda se prvi operand zadaje implicitno. Prethodni primer je mogao biti napisan kao funkcija članica klase CTacka:

CTacka CTacka::operator+ (CTacka tac2) { CTacka nova_tacka; nova_tacka.x = x + tac2.xy; nova_tacka.y = y + tac2.y; return nova_tacka; }

Prednost pisanja funkcije operatora kao globalne funkcije leži u tome što se svaki tip - uključujući i prosti - može koristiti za svaki operand.

Primer. Klasa Counter. 1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsVal; } 13: void SetItsVal(USHORT x) {itsVal = x; } 14: 15: private: l6: USHORT itsVal; 17: 18: }; 19: 20: Counter::Counter(): itsVal(0) 21: {} 22: 23: int main() 24: { 25: Counter i; 26: cout << "Vrednost od i je " << i.GetItsVal() << endl; 27: return 0; 28: }

Kao što se vidi, ovo je prilično beskorisna klasa. Definisana je u linijama 7-18. Njena jedina promenljiva članica je promenljiva itsVal tipa USHORT. Podrazumevani konstruktor, koji je deklarisan u liniji 10 i čija je implementacija u liniji 20, inicijalizuje promenljivu članicu itsVal na vrednost 0.

Za razliku od člana itsVal, Counter objekat se ne može inkrementirati, dekrementirati, sabrati, dodeliti, ili se na neki drugi način njime manipulisati.

Page 90: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

453

Preklapanje operatora vraća funkcionalnost, koja je bila oduzeta od ove klase. Na primer, postoje dva načina da se doda sposobnost za inkrementiranje Counter objekta. Prvi je da se napiše metod za inkrementiranje, kao što je prikazano u narednom primeru. 1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsVal; } 13: void SetItsVal(USHORT x) {itsVal = x; } 14: void Increment() {++itsVal;} 15: 16: private: l7: USHORT itsVal; 18: 19: }; 20: 21: Counter::Counter(): itsVal(0) 22: { } 23: 24: int main() 25: { 26: Counter i; 27: cout << "Vrednost od i je " << i.GetItsVal() << endl; 28: i.Increment(); 29: cout << "Vrednost od i je " << i.GetItsVal() << endl; 30: return 0; 31: }

Preklapanje prefiksnog operatora Prefiksni operatori se mogu preklopiti deklarisanjem funkcija izrazom oblika:

returnType Operator op (parameters)

Operator ++ se može preklopiti pomoću sledeće sintakse: 1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsVal; } 13: void SetItsVal(USHORT x) {itsVal = x; } 14: void Increment() { ++itsVal; } 15: void operator++() { ++itsVal; } 16: 17: private: l8: USHORT itsVal; 19: 20: };

Page 91: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

454

21: 22: Counter::Counter(): itsVal(0) 23: {} 24: 25: int main() 26: { 27: Counter i; 28: cout << "Vrednost od i je " << i.GetItsVal() << endl; 29: i.Increment(); 30: cout << "Vrednost od i je " << i.GetItsVal() << endl; 31: ++i; 32: cout << "Vrednost od i je " << i.GetItsVal() << endl; 33: return 0; 34: }

Vrednost od i je 0 Vrednost od i je 1 Vrednost od i je 2

U liniji 15 preklopljen je operator++, a upotrebljen je u liniji 31. U ovom trenutku ćete možda razmotriti stavljanje dodatnih mogućnosti, za koje je prvenstveno Counter i bila kreirana, kao što je detektovanje slučaja kada Counter prekorači svoju maksimalnu veličinu.

Ipak, postoji značajan defekt u načinu na koji je inkrement operator napisan. Ako želite da stavite Counter na desnu stranu dodele, to neće uspeti. Na primer: Counter a = ++i;

Ovaj kod namerava da kreira novi objekat a klase Counter, a onda da mu dodeli vrednost objekta i posle njegovog inkrementiranja. Ugrađeni konstruktor kopije će rešiti ovu dodelu, ali tekući inkrement operator ne vraća Counter objekat. On vraća void. Ne možete dodeliti void objekat Counter objektu.

Vraćanje tipova u preklopljenim operatorskim funkcijama Jasno, ono što želite je da vratite Counter objekat tako da on može biti dodeljen drugom Counter

objektu. Koji objekat bi trebalo da bude vraćen? Jedan pristup bi bio da kreirate privremeni objekat i njega da vratite.

Primer. Vraćanje privremenog objekta. 1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsVal; } 13: void SetItsVal(USHORT x) {itsVal = x; } 14: void Increment() {++itsVal;} 15: Counter operator++(); 16: 17: private: l8: USHORT itsVal; 19: 20: }; 21: 22: Counter::Counter():itsVal(0) 23: {} 24: 25: Counter Counter::operator++()

Page 92: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

455

26: { 27: ++itsVal; 28: Counter temp; 29: temp.SetItsVal(itsVal); 30: return(temp); 31: } 32: 33: int main() 34: { 35: Counter i; 36: cout << "Vrednost od i je " << i.GetItsVal() << endl; 37: i.Increment(); 38: cout << "Vrednost od i je " << i.GetItsVal() << endl; 39: ++i; 40: cout << "Vrednost od i je " << i.GetItsVal() << endl; 41: Counter a = ++i; 42: cout << "Vrednost od a: " << a.GetItsVal() << endl; 43: cout << "Vrednost za i: " << i.GetItsVal() <<endl; 44: return 0; 45: }

Izlaz:

Vrednost od i je 0 Vrednost od i je 1 Vrednost od i je 2 Vrednost od a: 3 Vrednost za i: 3

U ovoj verziji operator++ je deklarisan u liniji 15 da vraća Counter objekat. U liniji 29 kreira se privremena promenljiva, temp, a njena vrednost se postavlja da odgovara vrednosti u tekućem objektu. Ta privremena promenljiva se vraća i odmah dodeljuje objektu a u liniji 42.

Vraćanje bezimenih promenljivih Nema potrebe imenovati privremeni objekat kreiran u liniji 28. Kada bi Counter imao konstruktor

koji prihvata vrednost, mogli biste da vratite rezultat tog konstruktora kao povratnu vrednost operatora inkrementiranja.

1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: Counter(USHORT val); 12: ~Counter(){} 13: USHORT GetItsVal()const { return itsVal; } 14: void SetItsVal(USHORT x) {itsVal = x; } 15: void Increment() {++itsVal;} 16: Counter operator++(); 17: 18: private: 19: USHORT itsVal; 20: 21: }; 22: 23: Counter::Counter(): itsVal(0) 24: {}

Page 93: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

456

25: 26: Counter::Counter(USHORT val): itsVal(val) 27: {} 28: 29: Counter Counter::operator++() 30: { 31: ++itsVal; 32: return Counter(itsVal); 33: } 34: 35: int main() 36: { 37: Counter i; 38: cout << "Vrednost od i je " << i.GetItsVal() << endl; 39: i.Increment(); 40: cout << "Vrednost od i je " << i.GetItsVal() << endl; 41: ++i; 42: cout << "Vrednost od i je " << i.GetItsVal() << endl; 43: Counter a = ++i; 44: cout << "Vrednost od a: " << a.GetItsVal() << endl; 45: cout << "Vrednost za i: " << i.GetItsVal() <<endl; 46: return 0; 47: }

Kada je potrebna struktura podataka za koju nisu bitni detalji implementacije, već operacije koje se nad njom vrše, sve ukazuje na klasu. Klasa upravo predstavlja apstraktni tip podataka za koji su definisani podaci.

U jeziku C++, operatori za korisničke tipove su specijalne funkcije koje nose ime operator@ gde je @ neki operator ugrađen u jezik.

Primer. Pretpostavimo da su nam u programu potrebni kompleksni brojevi i operacije nad njima. Treba nam struktura podataka koja će, pomoću osnovnih tipova, predstaviti strukturu kompleksnog broja, a takođe i funkcije koje će realizovati operacije nad kompleksnim brojevima. #include<iostream.h> #include<math.h> class complex { public: complex(double re, double im); /* konstruktor */ void ispis() { cout<<real; if(imag>0)cout<<"+I*"; else if(imag<0) cout<<"-I*"; if(imag!=0)cout<<fabs(imag)<<endl; } friend complex operator+(complex, complex); /* operator + */ friend complex operator-(complex, complex); /* operator - */ private: double real, imag; }; complex:: complex(double r, double i): real(r), imag(i) {} complex operator+(complex c1, complex c2) { complex temp(0,0); /* privremena promenljiva tipa complex*/ temp.real=c1.real+c2.real; temp.imag=c1.imag+c2.imag; return temp;

Page 94: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

457

} complex operator- (complex c1, complex c2) { return complex(c1.real-c2.real, c1.imag-c2.imag); } void main() { complex c1(1.2,0.3),c2(-2.1,2.98),c=c1+c2; cout<<"c1+c2: "; c.ispis(); c=c1-c2; cout<<"c1-c2: "; c.ispis(); }

Operatorske funkcije se mogu koristiti u izrazima kao i operatori nad ugrađenim tipovima. Izraz t1@t2 se tumači kao t1.operator@(t2) ili operator@(t1,t2).

2. (C++) Kreirati klasu fraction za rad sa racionalnim brojevima. Klasa bi trebalo da ima sledeće članove:

Privatni članovi celi brojevi brojilac i imenilac, fraction koja skraćuje razlomak; funkciju koja računa najveći zajednički delilac dva pozitivna cela broja;

Javni članovi konstruktor koji generiše razlomak brojilac/1; konstruktor koji inicijalizuje brojilac i imenilac na vrednosti svojih parametara; konstruktor bez parametara; operatorske funkcije koje realizuju 4 osnovne aritmetičke operacije;

#include<iostream.h> class razlomak { int brojilac, imenilac; int nzd(int a, int b); public: razlomak(int b, int i) : brojilac(b),imenilac(i){} //Konstruktor razlomak (int b):imenilac(1) // Konstruktor { brojilac=b; } razlomak(); // Konstruktor void ispis() // Ispis razlomka { cout<<brojilac<<'/'<<imenilac<<endl; } void skrati() { if(imenilac<0) { brojilac=-brojilac; imenilac=-imenilac; } int n=nzd(abs(imenilac), abs(brojilac)); brojilac/=n; imenilac/=n; } razlomak operator+(const razlomak &r); razlomak operator-(const razlomak &r); razlomak operator*(const razlomak &r); razlomak operator/(const razlomak &r); }; int razlomak::nzd(int a, int b) { int r;

Page 95: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

458

while(a%b) { r=a%b; a=b; b=r; } return b; } razlomak razlomak::operator+(const razlomak &r) { razlomak temp(brojilac*r.imenilac+imenilac*r.brojilac, imenilac*r.imenilac); temp.skrati(); return temp; } razlomak razlomak::operator-(const razlomak &r) { razlomak temp(brojilac*r.imenilac-imenilac*r.brojilac, imenilac*r.imenilac); temp.skrati(); return temp; } razlomak razlomak::operator*(const razlomak &r) { razlomak temp(brojilac*r.brojilac, imenilac*r.imenilac); temp.skrati(); return temp; } razlomak razlomak::operator/(const razlomak &r) { razlomak temp(brojilac*r.imenilac, imenilac*r.brojilac); temp.skrati(); return temp; } void main() { razlomak r1(-3,5), r2(2,5); cout<<"r1: "; r1.ispis(); cout<<"r2: "; r2.ispis(); razlomak r=r1+r2; cout<<"r1+r2 = "; r.ispis(); r=r1-r2; cout<<"r1-r2 = "; r.ispis(); r=r1*r2; cout<<"r1*r2 = "; r.ispis(); r=r1/r2; cout<<"r1/r2 = "; r.ispis(); int b; cout<<"brojilac = ? "; cin>>b; razlomak r3(b); r3.ispis(); }

Ulaz: Ceo broj koji predstavlja brojilac b jednog razlomka. Izlaz: Ispis razlomaka r1=-3/5 i r2=2/5,koji su generisani konstruktorom sa dva parametra. Zatim ispisati zbir, razliku, proizvod i količnik razlomaka r1 i r2. Na kraju, ispisati razlomak koji je generisan jednoparametarskim konstruktorom čiji je stvarni parametar učitani ceo broj b. Test primer: r1: -3/5 r2: 2/5 r1+r2 = -1/5 r1-r2 = 1/1 r1*r2 = -6/25 r1/r2 = -3/2 brojilac = ? 3 3/1

Page 96: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

459

Preklapanje postiiksnog operatora Do sada ste preklapali prefiksni operator. Šta ako želite da preklopite postfiksni inkrement

operator? Ovde kompajler ima problem: kako razlikovati prefiks i postfiks? Po konvenciji, celobrojna promenljiva se obezbeđuje kao parametar deklaraciji operatora. Vrednost parametra se ignoriše; ona je samo signal da se radi o postfiksnom operatoru.

Dok prefiksni operator može jednostavno inkrementirati vrednost, a onda vratiti sam objekat, postfiksni mora vratiti vrednost koja je postojala pre nego što je inkrementirana. Da bismo ovo uradili, moramo kreirati privremeni objekat koji će čuvati originalnu vrednost; onda će se inkrementirati vrednost originalnog objekta, a onda vratiti privremeni.

Razmotrite sledeću liniju koda: a = x++;

Ako je x bilo 5, posle ovog iskaza a je 5, ali x je 6. Vratili smo vrednost u x i dodelili je a, a onda smo uvećali vrednost od x. Ako je x objekat, njegov postfiksni inkrement operator mora smestiti originalnu vrednost (5) u privremeni objekat, inkrementirati vrednost objekta x na 6, a onda vratiti taj privremeni objekat, da bi se njegova vrednost dodelila a.

Uočite da privremeni objekat moramo vratiti po vrednosti, a ne po referenci, jer će privremeni objekat izaći iz opsega odmah po povratku iz funkcije.

Primer. Prefiksni i postfiksni operatori. 1: // 2: // Klasa Counter 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsVal; } 13: void SetItsVal(USHORT x) {itsVal = x; } 14: const Counter& operator++(); // prefix 15: const Counter operator++(int); // postfix 16: 17: private: 18: USHORT itsVal; 19: }; 20: 21: Counter::Counter(): itsVal(0) 22: {} 23: 24: const Counter& Counter::operator++() 25: { 26: ++itsVal; 27: return *this; 28: } 29: 30: const Counter Counter::operator++(int) 31: { 32: Counter temp(*this); 34: ++itsVal; 35: return temp; 36: } 37: 38: int main()

Page 97: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

460

39: { 40: Counter i; 41: cout << "Vrednost od i je "<<i.GetItsVal()<<endl; 42: i++; 43: cout << "Vrednost od i je "<<i.GetItsVal()<<endl; 44: ++i; 45: cout << "Vrednost od i je "<<i.GetItsVal()<<endl; 46: Counter a = ++i; 47: cout << "Vrednost od a: " << a.GetItsVal()<<endl; 48: cout << "Vrednost za i: " << i.GetItsVal()<<endl; 49: a=i++; 50: cout << "Vrednost od a: " << a.GetItsVal(); 51: cout << "Vrednost za i: " << i.GetItsVal() <<endl; 52: return 0; 53: }

Vrednost od i je 0 Vrednost od i je 1 Vrednost od i je 2 Vrednost od a: 3 Vrednost za i: 3 Vrednost od a: 3 Vrednost za i: 4

Postfiksni operator je deklarisan u liniji 15, a implementiran u linijama 30-35. Uočite da poziv prefiksnog operatora u liniji 14 ne uključuje celobrojnu zastavicu (x), nego se koristi sa svojom normalnom sintaksom. Postfiksni operator koristi zastavicu (x) da signalizira da se radi o postfiksu, a ne prefiksu. Zastavica (x) se nikada ne koristi.

Preklapanje unarnih operatora Deklarišite preklopljeni operator kao što bi ste deklarisali funkciju. Upotrebite ključnu reč operator,

praćenu operatorom koji će biti preklopljen. Unarne operatorske funkcije ne prihvataju parametre, sa izuzetkom postfiksnog inkrementa i dekrementa, koji prihvataju celobrojnu promenljivu kao zastavicu.

Primer 1: const Counter& Counter::operator++ ();

Primer 2: Counter Counter::bperator-(int);

Operator sabiranja Inkrement operator je unarni operator. On operiše samo nad jednim objektom. Operator sabiranja

(+) je binarni operator, i on koristi dva objekta. Kako da implementirate preklapanje operatora + za Counter?

Cilj je da se deklarišu dve Counter promenljive, a da se onda saberu, kao u ovom primeru: Counter varOne, varTwo, varThree; VarThree = VarOne + VarTwo;

Mogli biste početi pisanjem funkcije, Add(), koja bi prihvatala Counter kao svoj argument, sabiranjem vrednosti, a onda vratiti Counter sa rezultatom. Sledeći primer ilustruje ovaj pristup.

Primer. Funkcija Add(). 1: // 2: // Funkcija za sabiranje 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter();

Page 98: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

461

11: Counter(USHORT intialVa1ue); 12: ~Counter(){} 13: USHORT GetItsVal()const { return itsVal; } 14: void SetItsVal(USHORT x) {itsVal = x; } 15: Counter Add(const Counter &); 16: 17: private: 18: USHORT itsVal; 19: 20: }; 21: 22: Counter::Counter(USHORT initialValue): 23: itsVal(initialValue) 24: {} 25: 26: Counter::Counter(): 27: itsVal(0) 28: {} 29: 30: Counter Counter::Add(const Counter & rhs) 31: { 32: return Counter(itsVal + rhs.GetItsVal()); 33: } 34: 35: int main() 36: { 37: Counter varOne(2), varTwo(4), varThree; 38: varThree = varOne.Add(varTwo); 39: cout << "varOne: " << varOne.GetItsVal() << endl; 40: cout << "varTwo: " << varTwo.GetItsVal() << endl; 41: cout << "varThree: " << varThree.GetItsVal() << endl; 42: 43: return 0; 44: }

varOne: 2 varTvro: 4 varThree: 6

Funkcija Add() je deklarisana u liniji 15, Ona prihvata konstantnu Counter referencu - ona je broj koji treba da se doda tekućem objektu. Ona vraća Counter objekat - rezultat koji treba dodeliti levoj strani iskaza dodele, kao što je prikazano u liniji 38. To znači da je varOne objekat, varTwo je parametar za funkciju Add(), a rezultat se dodeljuje objektu varThree.

Da bi se kreirao objekat varThree, bez potrebe da se inicijalizuje nekom vrednošću, zahteva se podrazumevani konstruktor. Podrazumevani konstruktor inicijalizuje podatak itsVal na 0, kao što je prikazano u linijama 26-28. Kako je potrebno da varOne i varTwo budu inicijalizovani na ne-nula vrednost, bio je kreiran drugi konstruktor, kao što je prikazano u linijama 22-24. Drugo rešenje ovog problema je obezbeđivanje podrazumevane vrednosti 0 konstruktoru, koji je deklarisan u liniji 11.

Preklapanje operatora + Sama funkcija Add() je prikazana u linijama 30-33. Ona radi, ali njena upotreba je neprirodna.

Preklapanje operatora + bi učinilo upotrebu klase Counter prirodnijom.

Primer. Operator +. 1: // 2: // Funkcija za sabiranje 3: 4: typedef unsigned short USHORT; 5: #include <iostream.h> 6:

Page 99: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

462

7: class Counter 8: { 9: public: 10: Counter(); 11: Counter(USHORT intialVa1ue); 12: ~Counter(){} 13: USHORT GetItsVal()const { return itsVal; } 14: void SetItsVal(USHORT x) {itsVal = x; } 15: Counter operator+ (const Counter &); 16: 17: private: 18: USHORT itsVal; 19: 20: }; 21: 22: Counter::Counter(USHORT initialValue): 23: itsVal(initialValue) 24: {} 25: 26: Counter::Counter(): 27: itsVal(0) 28: {} 29: 30: Counter Counter::operator+ (const Counter & rhs) 31: { 32: return Counter(itsVal + rhs.GetItsVal()); 33: } 34: 35: int main() 36: { 37: Counter varOne(2), varTwo(4), varThree; 38: varThree = varOne + varTwo; 39: cout << "varOne: " << varOne.GetItsVal() << endl; 40: cout << "varTwo: " << varTwo.GetItsVal() << endl; 41: cout << "varThree: " << varThree.GetItsVal() << endl; 42: 43: return 0; 44: }

U ovom listingu, operator+ se deklariše u liniji 15, a definiše u linijama 28-31. Uporedite ovo sa deklaracijom i definicijom funkcije Add() u prethodnom listingu; one su skoro identične. Ipak, sintaksa njihove upotrebe se prilično razlikuje.

Prirodnije je reći ovo: varThree = varOne + varTwo;

nego reći: varThree = varOne.Add(varTwo);

Nije velika promena, ali je dovoljna da program bude lakši za korišćenje i razumevanje.

Tehnike korišćene za preklapanje operatora ++ se mogu primeniti na druge unarne operotore, kao što je operator-.

Binarni operatori se kreiraju kao unarni, osim što oni prihvataju parametar. Parametar je konstantna referenca na objekat istog tipa.

Counter Counter::operator+ (const Counter & rhs); Counter Counter::operator- (const Counter & rhs);

Page 100: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

463

9.13.3. Bočni efekti i veze između operatora

Bočni efekti koji postoje kod operatora za ugrađene tipove nikad se ne podrazumevaju za definisane operatore: ++ ne mora da menja stanje objekta, niti da znači sabiranje sa 1. Ovo važi i za – i sve operatore dodele (=, +=, -=, *= itd.).

Operator = (i ostali operatori dodele) ne mora da menja stanje objekta. Ipak, ovakve upotrebe treba strogo izbegavati.

Veze koje postoje između operatora za ugrađene tipove ne podrazumevaju se za redefinisane operatore. Na primer, a+=b ne mora automatski da znači a=a+b, ako je definisan operator +, već operator += mora posebno da se definiše.

Preporučuje se da operatori koje definiše korisnik imaju očekivano značenje, radi čitljivosti programa. Na primer, ako su definisani i operator += i operator +, dobro je da a+=b koristi isti efekat kao i a=a+b.

Operatorske funkcije kao članice i globalne funkcije Operatorske funkcije mogu da budu članice klasa ili globalne funkcije. Ako je @ neki binarni

operator (na primer +), on može da se realizuje kao funkcija članica klase X na sledeći način: p operator@ (X)

ili kao prijateljska globalna funkcija na sledeći način: p operator@ (X,X)

Unarni i binarni operatori Mnogi operatori jezika C++ (kao i jezika C) mogu da budu i unarni i binarni. Unarni operator ima samo jedan operand, pa se može realizovati kao operatorska funkcija

članica bez argumenata (prvi operand je objekat čija je funkcija članica pozvana): tip operator@ ()

ili kao globalna funkcija sa jednim argumentom: tip operator@ (X x)

Binarni operator ima dva operanda, pa se može realizovati kao funkcija članica sa jednim argumentom (prvi operand je objekat čija je funkcija članica pozvana):

tip operator@ (X xdesni)

ili kao globalna funkcija sa dva argumenta: tip operator@ (X xlevi, X xdesni).

Primer. class complex {

public: complex (double re=0, double im=0) : real (r), imag (i) {} friend complex operator+ (complex, complex); complex operator! () //unarni operator!, konjugovani broj { return complex (real, -imag);} private: double real, imag;

};

Page 101: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

464

9.13. Osnovni standardni ulazno/izlazni tokovi

9.13.1. Klase istream i ostream

Kao i jezik C, ni C++ ne sadrži ulazno/izlazne (U/I) operacije, već se one realizuju standardnim bibliotekama. Ipak, C++ sadrži standardne U/I biblioteke realizovane u duhu jezika C++.

Na raspolaganju su i stare C biblioteke sa funkcijama scanf i printf, ali njihovo korišćenje nije u duhu jezika C++.

Biblioteka čije se deklaracije nalaze u zaglavlju < iostream.h > sadrži dve osnovne klase, istream i ostream (ulazni i izlazni tok). Svakom primerku klasa ifstream i ofstream, koje su redom izvedene iz navedenih klasa, može da se pridruži jedna datoteka za ulaz/izlaz, tako da se datotekama pristupa isključivo preko ovakvih objekata, odnosno funkcija članica ili prijatelja ovih klasa. Time je podržan princip enkapsulacije.

U ovoj biblioteci definisana su i dva korisniku dostupna (globalna) statička objekta:

1. Objekat cin klase istream koji je pridružen standardnom ulaznom uređaju (obično tastatura); 2. Objekat cout klase ostream koji je pridružen standardnom izlaznom uređaju (obično ekran).

Klasa istream je preklopila operator >> za sve ugrađene tipove, koji služi za izlaz podataka: istream& operator >> (istream &is, tip &t);

gde je tip neki ugrađeni tip objekta koji se učitava.

Klasa ostream je preklopila operator << za sve ugrađene tipove, koji služi za izlaz podataka:

ostream& operator<< (ostream &os, tip x);

gde je tip neki ugrađeni tip objekta koji se ispisuje.

Ove funkcije vraćaju reference, tako da se može vršiti U/I u istoj naredbi. Osim toga, ovi operatori su asocijativni sleva u desno, tako da se podaci ispisuju prirodnim redosledom.

Izrazom oblika Cin >> i >> j

Učitavaju se podaci iz ulaznog toka i dodeljuju se promenljivima i i j.

Ove operatore treba koristiti za uobičajene, jednostavne U/I operacije: #include <iostream.h> //obavezno ako se želi U/I void main () { int i ; cin >> i; //učitava se i cout << ”i= ” << i << ’\n’; //ispisuje se na primer: i=5 // i prelazi u novi red }

9.13.2. Ulazno/izlazne operacije za korisničke tipove

Korisnik može da definiše značenje operatora >> i << za svoje tipove. To se radi definisanjem prijateljskih funkcija korisnikove klase, jer je prvi operand tipa istream& odnosno ostream&.

Primer za klasu complex: #include <iostream.h>

class complex { public: //... friend ostream& operator << (ostream&, const complex&); //... };

Page 102: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

465

/...

ostream& operator << (ostream &os, const complex &c) { return os <<”(“ << c.real << ”;” << c.imag << ”)”; }

void main() { complex c(0.5,0.1); cout << ”c=” << c << ”\n”; //ispisuje se:c=(0.5, 0.1) }

9.14. Nizovi objekata Kompajleru saopštavate tip objekta i broj objekata za koji želite da alocirate prostor. Kompajler zna

koliko prostora je neophodno za svaki objekat, na osnovu deklaracije klase. Klasa mora da ima podrazumevani konstruktor, koji ne uzima elemente, tako da objekti mogu da budu kreirani kada se definiše niz. Pristup podacima članovima jednog niza objekata je proces iz dva koraka. Identifikacija člana niza se vrši korišćenjem indeks operatora ([ ]) i zatim se dodaje operator (.), da bi se pristupilo određenoj promenljivoj članu.

Primer. Kreiranje niza objekata. . 1: // 2: 3: #include <iostream.h> 4: 5: class CAT 6: { 7: public: 8: CAT() { starost = 1; tezina = 5; } 9: ~CAT() {} 10: int DajStarost() const { return starost; } 11: int DajTezinu() const { return tezina; } 12: void PostaviStarost(int godine) { starost = godine; } 13: 14: private: 15: int starost; 16: int tezina; 17: }; 18: 19: int main() 20: { 21: CAT Litter[5]; 22: int i; 23: for (i = 0; i < 5; i++) 24: Litter[i].PostaviStarost(2*i +1); 25: 26: for (i = 0; i < 5; i++) 27: { 28: cout << "Cat No. " << i+1 << ": "; 29: cout << Litter[i].DajStarost() << endl; 30: } 31: return 0; 32: }

U linijama 5-17 deklarisana je CAT klasa, koja mora da ima podrazumevani konstruktor, tako da CAT objekti mogu da budu kreirani u nizu. Primetićete da, ako kreirate bilo koji drugi konstruktor, podrazumevani konstruktor koji kreira kompajler neće biti uključen; moraćete da kreirate sopstveni.

Prva for petlja (linije 23 i 24) postavlja godine za svaku od pet mačaka u nizu. Druga for petlja (linije 26 i 27) pristupa svim članovima niza i poziva DajStarost(). Svaki pojedinčni DajStarost()

Page 103: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

466

metod je pozvan tako što je pristupljeno odgovarajućem članu niza Litter[i], koji sledi (.) i funkcija član.

Posle deklaracije niza, kompajler odvaja memoriju za sve objekte, čak i ako ih nikada nećete koristiti. Ovo nije problem sa nizovima u kojima znate koliko objekata će biti potrebno. Međutim, kada nemate predstavu koliko objekata će biti potrebno, neophodno je da koristite naprednije strukture podataka.

9.14.1. Nizovi pointera

Nizovi o kojima smo do sada pričali sve svoje članove smeštaju na "stek". Stek memorija je, obično, veoma limitirana, dok je slobodna memorija mnogo veća. Moguće je deklarisati sve objekte u slobodnoj memoriji i zatim smestiti samo pointere na objekte u niz. Ovim se dramatično redukuje količina iskorišćene stek memorije. U sledećem primeru je na drugi način kreiran niz iz ranijeg primera, tako da su svi objekti smešteni u slobodnu memoriju. Kao indikacija o veličini memorije koju ovaj metod omogućava, niz je proširen sa 5 na 500.

Primer. Čuvanje niza na slobodnom skladištu. 1: // 2: 3: #include <iostream.h> 4: 5: class CAT 6: { 7: public: 8: CAT() { starost = 1; tezina = 5; } 9: ~CAT() {} 10: int DajStarost() const { return starost; } 11: int DajTezinu() const { return tezina; } 12: void PostaviStarost(int godine) { starost = godine; } 13: 14: private: 15: int starost; 16: int tezina; 17: }; 18: 19: int main() 20: { 21: CAT *Litter[500]; 22: int i; 23: CAT *pCat; 24: for (i = 0; i < 500; i++) 25: { 26: pCat = new CAT; 27: pCat->PostaviStarost(2*i +1); 28: Litter[i] = pCat; 29: } 29: 30: 31: for (i = 0; i < 500; i++) 32: { 33: cout << "Cat No. " << i+1 << ": "; 34: cout << Litter[i]->DajStarost() << endl; 35: } 36: return 0; 37: }

Objekat CAT, koji je deklarisan u linijama 5-17, identičan je sa CAT objektom deklarisanim u prethodnom listingu. Međutim, niz deklarisan u liniji 21 sadrži 500 pointera na CAT objekte.

Page 104: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

467

U inicijalnoj petlji (linije 24-29) 500 novih CAT objekata je kreirano u slobodnom prostoru i svaki od njih ima postavljene godine. Na kraju, pointer je dodat nizu.

Pošto je niz deklarisan da koristi pointere, pointer, umesto dereferencirane vrednosti u pointeru, dodat je u niz.

Druga petlja (linije 31 i 32) prikazuje svaku od ovih vrednosti. Pointeru se pristupa korišćenjem Litter[i]. Ta adresa je zatim iskorišćena za pristup metodu DajStarost().

U tom primeru niz Letter i svi njegovi pointeri su smešteni na stek, ali 500 CAT kreiranih objekata je smešteno u slobodan prostor.

9.14.2. Deklarisanje nizova u slobodnom prostoru

Takođe je moguće cele nizove smestiti u slobodan prostor, koji se, takođe, zove i heap. Ovo možete učiniti tako što ćete pozvati New, uz pomoć subscript operatora, Rezultat je pointer na zonu u slobodnom prostoru, koja sadrži niz. Na primer,

CAT *Family = new CAT[500];

deklariše Family kao pointer na prvi član niza od 500 mačaka. Drugim rečima, Family ukazuje na - ili ima adresu od - Family[0].

Prednost korišćenja Family na ovaj način je u mogućnosti da koristite aritmetiku pointera za pristup svakom članu Family. Na primer, možete napisati

CAT *Family = new CAT[500]; CAT *pCat = Family; // pCat pokazuje na Family[O] pCat->PostaviStarost(10); // postavtja Family[0] na 10 pCat++; // prelazi na Family[l] pCat->SetAge(20); // postavlja Family(l] na 20

Ovim ćete deklarisati novi niz od 500 CAT-ova i pointer koji ukazuje na početak niza. Korišćenjem tog pointera prva CAT-ova PostaviStarost() funkcija se poziva sa vrednošću 10. Pointer se, zatim, inkrementira, kako bi ukazao na sledeći CAT i poziva se drugi PostaviStarost metod.

Pointer na niz i niz pointera Pogledajte sledeće tri deklaracije: 1: Cat FamilyOne[500] 2: CAT *FamilyTwo[500]; 3: CAT *FamilyThree=newCAT[500];

FamilyOne je niz od 500 CAT-ova. FamilyTwo je niz od 500 pointera na CAT-ove i FamiyThree je pointer na niz od 500 CAT-ova.

U trećem slučaju FamiyThree je pointer na niz. Adresa u FamilyThree je adresa prve stavke u tom nizu. To je isti slučaj i sa FamilyOne.

9.15. Uvod u objektno modelovanje

9.15.1. Apstraktni tipovi podataka

Apstraktni tipovi podataka predstavljaju realizacije struktura podataka sa pridruženim protokolima (operacijama i definisanim načinom i redosledom pozivanja tih operacija). Na primer, red je struktura elemenata koja ima operacije stavljanja i uzimanja elemenata u strukturu, pri čemu se elementi uzimaju po istom redosledu po kom su stavljeni.

Kada se realizuju strukture podataka (apsraktni tipovi podataka), najčešće nije bitno koji je tip elementa strukture, važan je samo skup operacija. Načini realizacija tih operacija ne zavise od tipa elementa, već samo od tipa strukture.

Page 105: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

Za realizaciju apstraktnih tipova podataka kod kojih tip nije bitan, u jeziku C++ postoje šabloni. šablon klase predstavlja definiciju čitavog skupa klasa koje se razlikuju samo po tipu elementa, i eventualno, po dimenzijama. šabloni klasa se ponekad nazivaju i generičkim klasama.

Konkretna klasa generisana iz šablona dobija se navođenjem stvarnog tipa elementa.

Formalni argumenti šablona zadaju se u zaglavlju šablona.

Template Namena. Pravi opštu klasu ili funkciju koristeći parametarski tip koji se kasnije popunjava. Možete, na primer, napraviti klasu kolekcije (kao što je stek) i onda je primeniti na tipove int, long, float, ...

Sintaksa. Sledećom sintaksom se deklariše šablon (engl. template): template<class T> deklaracija

gde deklaracija jeste deklaracija klase ili definicija funkcije. Argument T može imati bilo koje ispravno ime; predstavlja tip koji treba kasnije da bude popunjen.

Pošto je šablon definisan, možete ga koristiti za pravljenje deklaracija na osnovu odredenog tipa. Šablon primenjujete tako što popunite argument T:

naziv_sablona<tip>

Šabloni mogu biti i složeniji, s nekoliko argumenata, gde class T čini jedan ili više tih argumenata:

template<argumenti> deklaracije

Ovaj šablon primenjujete popunjavajući sve argumente: naziv_šablona<vrednosti_argunienata>

Primer 1: jednostavan šablon klase.

Evo sintakse za šablon klase koji ima samo jedan argument: template <class T> class naziv_sablona { deklaracije };

Parametarski tip T se koristi i u deklaracijama. Gde god se u deklaracijama pojavi T, zameniće ga pravi tip (kao što su int ili double ili naziv klase) kada se koristi šablon. Sledeća šema prikazuje kako radi zamena sablona.

Zamena jednostavnog šablona. Evo jednostavnog primera šablona. Šablon koji se zove par će za bilo koji zadati tip T definisati

nov tip sastavljen od dva člana tipa T. template <class T> class par { T a, b; };

Da biste koristili ovaj šablon, treba da navedete tip kao par<T>, u kome je T stvarni tip: par<int> dzins; // dzins sadrži dva int-a par<double> rukavice; // rukavice sadrže dva double-a

Page 106: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

par<CStr> naocare; // naocare sadrže dva znakovna niza

Sledeća šema pokazuje kako se tipom int zamenjuje parametar šablona u slučaju par<int>.

Zato se naznačavanje tipa par<int> pretvara u klasu koja ima dva člana:

class par<int> { int a, b; }

Analogno, par<doub1e> i par<CStr> se prave na sličan način. Ne možete bukvalno da vidite ove zamene, ali evo kako C++ tumači značenje tipova par<double> i par<CStr>.

class par<double> { double a, b; }

class par<CStr> { CStr a, b; };

Primer 2. Jednostavan šablon funkcije.

Još jedna primena šablona je sa funkcijama. Sintaksa je slična kao za šablone klasa. Jednostavan sablon fuiikcije ima sledeći oblik:

template <class T> tip ime (args) { iskazi }

Šablon funkcije nije ništa više do sintaksa template<class T> posle koje se piše definicija funkcije. Parametar T, koji predstavlja tip koji će kasnije biti naveden, može se pojaviti u kodu definicije funkcije koliko god puta želite. Evo primera jednostavnog šablona funkcije zameni_vrednosti.

template <class T> void zameni_vrednosti (T &a, T &b) { T privremeno; privremeno = a; a = b; b = privremeno; };

Ovde je tip T korišćen u listi argumenata; zato ovaj šablon vrši operaciju s bilo koje dve promenljive tipa T. Ovaj tip se koristi za pravljenje još jedne promenljive privremeno. Evo primera korišćenja ovakvog šablona;

#include <iostream.h> //... int x = 2.0, y = 10.0; cout << "x = " << x << endl cout « "y = " << y << endl zameni_vrednosti<int>(x, y) // Zameni x i y! cout << "x = " << x << endl cout << "y = " << y << endl

Ključni red ovog primera je; zameni_vrednosti<int>(x, y) // Zameni x i y!

Upotreba zameni_vrednosti<int> izaziva pravljenje skrivene definicije funkcije. Funkcija se ponaša kao da ste ušli u sledeće iskaze. (Napravio sam ovu definiciju funkcije zamenjujući svako pojavljivanje T sa int što, u suštini, i radi prevodilac.)

void zameni_vrednosti<int>(int &a, int &b) { int privremeno;

Page 107: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

470

privremeno = a; a = b; b = privremeno; };

Napomena. U ovom primeru se koristi adresni operator (&) za deklarisanje dva referencirajuća elementa. Obratite pažnju i na to da ovaj šablon tačno funkcioniše samo za one tipove za koje je definisana dodela (=).

Primer 3. Opšti stek.

Sledeći primer, iako je minimalna realizacija, pokazuje uopštenu kolekciju koja može biti korisna. Primetićete kako se parametarski tip T pojavljuje četiri puta u deklaraciji.

template <class T> class stack { T *stackp; int size; int index; public: T pop(void) { return stackp[--index]; } void push(T item) { stackp[index++] = item; } stack(int sz) { stackp = new T[size = sz]; index = 0; } ~stack() { delete [] stackp; } };

Ovakav šablon je loš u proveri grešaka. Taj nedostatak je obrađen u sledećem delu. Ovaj konstruktor morate koristiti u pravljenju promenljivih, jer je definisan samo jedan konstruktor. Na primer:

stack<int> stvari(30); stack<CStr> znakovni_nizovi (20)

Ovim se pravi stvari kao stek do 30 celobrojnih vrednosti i znakovni_nizovi kao stek do 20 objekata CStr. Čim su ovi stekovi definisani, možete stavljati elemente na stek (engl. push) i uzimati ih sa steka (engl. pop). Na primer:

znakovni_nizovi.push("Znakovni niz A"); znakovni_nizovi.push("Znakovni niz B"); cout << znakovni_nizovi.pop(); // Ispisuje "znakovni_niz B" cout << znakovni_nizovi.pop(); // Ispisuje "znakovni_niz A"

Navođenjem tipa stack<CStr> pravi se sledeća klasa. Napravio sam ovaj kod zamenom svakog T sa CStr što, u suštini, i radi prevodilac kada tumači značenje stack<CStr>.

class stack<CStr> { CStr *stackp; int size; int index; public: CStr pop(void) {return stackp[--index]; } void push(CStr item) { stackp[index++] = item; } stack(int sz) { stackp = new CStr[size = sz]; index =0; } ~stack() {delete [] stackp; } };

Ovaj uopšteni tip funkcioniše jer su operatori dodele (=) i operacije kopiranja definisane za klasu CStr. Ne bi bilo ispravno, na primer, koristiti ga za nizove.

Primer 4. stek klasa sa šablonima funkcije

U predhodnom delu smo imali definicije funkcija u deklaracijama klase, ali funkciju možete definisati i izvan klase dajući joj sopstveni šablon.

template <class T> T stack<T>::pop(void)

Page 108: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

471

{ //... }

Ovakav pristup omogućava bolje pisanje realizacija funkcija pop i push. U sledećoj verziji je poboljšano proveravanje grešaka.

template <class T> class stack { T *stackp; int size; int index; public: T pop(void) ; void push(T item); stack(int sz) { stackp = new T[size = sz] ; index = 0; } ~stack() {delete [] stackp; } // Šablon za definisanje funkcije pop. // Proveri index: ako je stek prazan, vrati objekat dummy. // Tip T mora imati podrazumevani konstruktor! // template <class T> T stack<T>::pop(void) { if(index > 0) return stackp[--index]; else { T dummy; return dummy; } } // Šablon za definisanje funkcije push. // Proveri index: ako je stek pun, vrati 0 (neuspeh). // U ostalim slučajevima vrati 1. // template <class T> int stack<T>::push(T item) { if (index < size) stackp[index++] = item; else return 0; return 1; }

Sledećom definicijom promenljive pravi se stek znakovni_nizovi, maksimalne veličine 50; stack<CStr> znakovni nizovi(50);

Ovim iskazom se pravi instanca tipa stack<CStr>, zbog čega prevodilac pravi taj tip. Kada je tip napravljen, prevodilac deklariše dve funkcije članice – pop i push – koje imaju opseg stack<CStr>. Evo prototipova koji se dobijaju;

CStr stack<CStr>::pop(void); int stack<CStr>::push(CStr item);

Navedene deklaracije primenjuju šablone definicija funkcija za stack<CStr>::pop i stack<CStr>: :push, zbog čega prevodilac pravi odgovarajući kod.

Poslednji primer pokazuje kako u šablonu može biti više argumenata. Ovakvim pristupom se, umesto pomoću konstruktora, veličina određuje pomoću argumenata šablona.

template <c1ass T, int sz> class stack { T arr[sz]; int index; public: T pop (void) ;

Page 109: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

472

int push(T item); stack () {index = 0;} // Šablon za definisanje funkcije pop. // Proveri index: ako je stek prazan, vrati objekat dummy. // Tip T mora imati podrazumevani konstruktor! // template <class T, int sz> T stack<T, sz>::pop(void) { if (index > 0) return arr[--index]; else {T dummy; return dummy; } } // Šablon za definisanje funkcije push. // Proveri index: ako je stek pun, vrati 0 (neuspeh). // U ostalim slučajevima vrati 1. // template <class T, int sz> int stack<T, sz>::push(T item) { if (index < sz) arr[index++] = item; else return 0; return 1; }

Da biste koristili ove šablone, u deklaraciji treba da navedete i tip i veličinu. Evo nekih primera: stack<int, 10> deset_brojeva; stack<float, 20> realni_brojevi; stack<CStr", 50> znakovni_nizov1;

Prva deklaracija pravi stek deset_brojeva, koji ima maksimalnu veličinu 10. Kada prevodilac pravi ovaj tip, deklarišu se dve funkcije:

stack<int, 10>::push stack<int, 10>::pop

Na kraju, ove deklaracije čine da prevodilac napravi odgovarajuće definicije funkcija.

Primer. template <class T> class Queue { public: Queue (); } Queue (); void put (const T&); T get();

//... };

Konkretna generisana klasa dobija se samo navođenjem imena šablona, uz definisanje stvarnih argumenata šablona. Stvarni argumenti šablona su tipovi i, eventualno, celobrojne dimenzije. Konkretna klasa se generiše na mestu navođenja, u fazi prevođenja. Na primer, red događaja može se kreirati na sledeći način:

class Event ; Queue <Event*> que; que.put(e); if (que.get ()->isUrgent())...

Generisanje je samo stvar automatskog generisanja parametrizovanih koda istog oblika, a nema nikakve veze sa izvršavanjem. Generisane klase nemaju nikakve posebne međusobne veze.

Page 110: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

473

9.15.2. Projektovanje apstraktnih tipova podataka

Apstraktni tipovi podataka (struktura podataka) su veoma često korišćeni elementi svakog programa. Projektovanje biblioteke klasa koje se realizuju standardne strukture podataka veoma je delikatan posao. Ovde će biti prikazana konstrukcija dve česte linearne strukture podataka:

1. Kolekcija je linearna neuređena struktura elemenata koja ima samo operacije sastavljanja elemenata i izbacivanja datog elementa iz strukture. Redosled elemenata nije bitan, a elementi se mogu i ponavljati.

2. Red je linearna uređena struktura elemenata. Elementi su uređeni po redosledu stavljanja. Operacija uzimanja vraća element koji je prvi stavljen u strukturu.

Važan koncept pridružen strukturama je pojam iteratora. Iterator je objekat pridružen linearnoj strukturi koji služi za pristup redom elementima strukture. Iterator ima operacije za postavljanje na početak strukture, za pomeranje na sledeći element strukture, za pristup do tekućeg elementa na koji ukazuje i operaciju za ispitivanje da li je došao do kraja strukture. Za svaku strukturu može se napraviti proizvoljno mnogo objekata-iteratora i svaki od njih pamti svoju poziciju.

Suština koncepta iteratora je da obezbedi sekvencijalni pristup do elemenata agregatne strukture, bez izlaganja njene interne realizacije. S druge strane, loše je opterećivati sam interfejs date strukture operacijama koje obezbeđuju takav pristup. Zato se obezbeđuje posebna klasa iteratora pridružena datoj strukturi, koja je odgovorna za obilazak strukture, poznaje njenu internu predstavu, i za koju se onda može napraviti proizvoljan broj instanci koje nezavisno iteriraju. Ovakvo rešenje predstavlja projektni obrazac (design pattern) Iterator (ili Cursor).

Pri realizaciji biblioteke klasa za strukture podataka, bitno je razlikovati protokol strukture koji definiše njenu semantiku, od njene implementacije.

Protokol strukture određuje značenje njenih operacija, potreban način ili redosled pozivanja itd.

Implementacija se odnosi na način smeštanja elemenata u memoriju, organizaciju njihove veze itd. Važan aspekt implementacije je da li je ona ograničena ili nije. Ograničena realizacija se oslanja na fiksno dimenzionisani niz elemenata, dok se neograničena realizacija odnosi na dinamičku strukturu (najčešće listu).

Na primer, protokol reda izgleda otprilike ovako: temlate <class T> class Queue { public: virtual IteratorQueue<T>* createIterator () const=0; virtual void put (const T&) = 0; virtual T get ()=0; virtual void clear ()=0; virtual const T& first () const=0; virtual int isEmpty () const=0; virtual int isFull () const=0; virtual int isLength () const=0; virtual int location (const T&) const=0; virtual

Da bi se korisniku obezbedile obe realizacije (ograničena i neograničena), postoje dve klase izvedene iz navedene apstraktne klase koja definiše interfejs reda. Jedna od njih podrazumeva ograničenu realizaciju, a druga neograničenu.

Na primer: template <class T, int N> class QueueB : public Queue<T> { public:

QueueB () {} QueueB (const Queue<T>&);

Page 111: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

474

virtual - QueueB () {} Queue<T>& operator= (const Queue<T>&) ; virtual Iterator Queue<T>* createIterator() = const; virtual void put (const T& t); virtual T get () ; virtual void clear () ; virtual const T& first () const; virtual int isEmpty () const; virtual int isFull () const; virtual int isLength () const; virtual int location (const T&) const; }

Treba obratiti pažnju na način kreiranja iteratora. Korisniku je dovoljan samo opšti, zajednički interfejs iteratora. Korisnik ne treba ništa da zna o specifičnostima realizacije iteratora i njegovoj vezi sa konkretnom izvedenom klasom reda. Zato je definisana osnovna apstraktna klasa iteratora, iz koje su izvedene klase za iteratore vezane za dve posebne realizacije reda:

template <class T> class IteratorQueue { public: virtual ~IteratorQueue () {} virtual void reset () = 0; virtual int next () = 0; virtual int isDone () const = 0; virtual const T* currentItem () const = 0; };

Izvedene klase reda praviće posebne, njima specifične iteratore koji se uklapaju u zajednički interfejs iteratora:

Template <class T, int N> IteratorQueue<T>* QueueB<T, N>: : createIterator ( ) const { return new IteratorQueueB<T, N> (this); }

Suština ovog rešenja je da se korisnici ovog podsistema oslanjaju samo na apstraktne interfejse Queue i IteratorQueue, odnosno da ne znaju za specifičnosti konkretnih implementacija. Čak i samo pravljenje iteratora obavlja se preko apstraktnog interfejsa, tj. operacije createIterator(), koju definišu konkretne izvedene klase QueueB i QueueU, tako što prave konkretne specifične iteratore. Ovakav pristup znatno povećava fleksibilnost softvera.

Ovo rešenje, tj operacija createIterator() predstavlja projektni obrazac Factory Method, koji se sastoji u obezbeđivanju interfejsa za pravljenje objekta, pri čemu konkretne izvedene klase odlučuju koji konkretni objekat da naprave.

Relacije između opisanih klasa prikazane su na sledećem dijagramu:

Page 112: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

Sama realizacija ograničene i neograničene strukture oslanja se na dve klase koje imaju sledeće interfejse:

template <class T> class Unbounded {public : Unbounded(); Unbounded (const.Unbounded<T>&) ; ~Unbounded(); Unbounded <T>& operator = (const Unbounded<T>&); void append (const T&); void insert (const T&, int at=0); void remove ( const T&); void remove (int at=0); void clear (); int isEmpty ( ) const; int isFull ( ) const; int length ( ) const; const T& first ( ) const; const T& last ( ) const; const T& itemAt (int at) const; T& itemAt (int at); int location (const T&) const; }; template <class T, int N> class Bounded { public: Bounded(); Bounded (const Bounded<T,N>&); ~Bounded(); Bounded<T,N>& operator=(const Bounded<T,N>&); void append (const T&); void insert (const T&, int at=0); void remove ( const T&); void remove (int at=0); void clear(); int isEmpty() const; int isFull() const;

Page 113: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

476

int length() const; const T& first() const; const T& last() const; const T& itemAt(int at) const; T& itemAt (int at); int location (const T&) const; };

Definisana struktura može se koristiti kao u sledećem primeru: class Event { //... };

typedef QueueB<Event*, MAXEV> EventQueue; typedef IteratorQueue<Event*> Iterator; //... EventQueue que; que.put (e);

Iterator* it=que.createIterator (); For (; !it->isDone(); it->next () ) (*it->currentItem ())->handle (); delete it;

Promena orijentacije na ograničeni red veoma je jednostavna. Ako se želi neograničeni red, dovoljno je promeniti samo:

typedef QueueU<Event*> EvenrQueue;

9.15.3. Modelovanje strukture-klase i osnovne relacije između klasa

Klasa je osnovna jedinica strukturnog modela sistema. Klasa je veoma retko izolovana. Ona dobija smisao samo zu druge klase sa kojima je u relaciji. Osnovne relacije između klasa su: asocijacija, zavisnost i nasleđivanje.

Klasa, atributi i operacije Klasom se modeluje apstrakcija. Klasa je opis skupa objekata koji dele iste atribute, operacije,

relacije i semantiku.

Atribut je imenovano svojstvo entiteta. Njime se opisuje opseg vrednosti koje instance tog svojstva mogu da imaju.

Operacija je implementacija usluge koja se može zatražiti od bilo kog objekta klase da bi se uticalo na ponašanje.

Klasa se prikazuje pravougaonim simbolom u kome mogu postojati posebni odeljci za ime klase, atribute i operacije:

CLASSA name: String age: int=0 ClassA( ) setName(newName: String): void

Asocijacija Asocijacija (pridruživanje) je relacija između klasa čiji su objekti na neki način strukturno

povezani. Ta veza između objekata klasa postoji određeno duže vreme, a ne samo tokom trajanja izvršavanja operacije jednog objekta koju poziva drugi objekat. Instanca operacije naziva se vezom i postoji između objekata datih klasa.

Page 114: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

477

Asocijacija se predstavlja punom linijom koja povezuje dve klase. Asocijacija može imati ime koje opisuje njeno značenje. Svaka strana u asocijaciji ima svoju ulogu koja se može naznačiti na strani date klase. Na svakoj strani asocijacije može se definisati kardinalnost pomoću sledećih oznaka:

1 tačno jedan * proizvoljno mnogo 1..* jedan i više

0..1 nula ili jedan 3..7 zadati opseg i slično.

Druga posebna karakteristika svake strane asocijacije je navigabilnost: sposobnost da se sa te strane (od objekta sa jedne strane veze) dospe do druge strane (do objekta sa druge strane veze). Prema ovom svojstvu, asocijacija može biti simetrična ili asimetrična.

Zavisnost Relacija zavisnosti postoji ako klasa A na neki način koristi usluge klase B. To može biti na primer

odnos klijent-server (klasa A poziva funkcije klase B) ili odnos instancijalizacije (klasa A pravi objekte klase B)

Za realizaciju ove relacije između klase A i B potrebno je da interfejs ili implementacija klase A “zna” za definiciju klase B. Tako je klasa A zavisna od klase B. Oznaka: Značenje relacije može da se navede kao stereotip relacije na dijagramu, na primer <<call>> ili <<create>>.

Ako klasa Client koristi usluge klase Supplier tako što poziva operacije objekata ove klase (odnos klijent-server), onda ona tipično vidi ove objekte kao argumente svojih operacija. U ovom slučaju, za implementaciju na jeziku C++, interfejsu klase Client nije potrebna definicija klase Supplier, već samo njenoj implementaciji:

class Supplier;

class Client { public; //... void aFunction (Supplier*); }; // Implementacija: void Client :: aFunction (Supplier* s) š //... s->doSomething(); }

Pri implementaciji na jeziku C++, ako je potrebno dobiti notaciju prenosa po vrednosti, a zadržati navedenu pogodnost slabije zavisnosti između modula, onda se argument prenosi preko reference na konstantu:

void Client :: aFunction (const Supplier& s) { s.doSomething(); }

Ako klasa Client instancijalizuje klasu Supplier, onda je realizacija nalik na: Supplier* Client :: createSupplier (/*some arguments*/) { return new Supplier (/*some arguments*/); }

Nasleđivanje (generalizacija/specijalizacija)

Supplier Client

Page 115: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

478

Nasleđivanje predstavlja relaciju generalizacije odnosno specijalizacije, zavisno u kom smeru se posmatra. Oznaka:

Derived

Relacija generalizacije/specijalizacije predstavlja relaciju koja ima dve važne semantičke manifestacije: (a) Nasleđivanje: nasleđena klasa implicitno poseduje sve atribute, operacije i asocijacije

osnovne klase (važi i tranzitivnost). (b) Supstitucija: objekat (instanca) izvedene klase može se naći svugde gde se očekuje objekat

osnovne klase (važi i tranzitivnost). Za strukturni aspekt sistema ovo pravilo ima sledeću bitnu manifestaciju: ako u nekoj asocijaciji učestvuje osnovna klasa, onda u nekoj vezi kao instanci te asocijacije mogu učestvovati objekti svih klasa izvedenih iz te klase.

Realizacija: class Derived : public Base //…

Zbog ovako definisane semantike osnovnih relacija između klasa (prvenstveno asocijacije i nasleđivanja), softverski sistemi (aplikacije) koji su modelovani objektno imaju jedno opšte svojstvo: njihova struktura u vreme izvršavanja može se apstraktno posmatrati kao tipizirani graf, jer se sastoji iz objekata (instanci klasa) povezanih vezama (instancama asocijacija). Dakle, objekti predstavljaju čvorove, a veze grane jednog grafa. Pritom, objekti kao čvorovi grafa imaju svoje tipove (to su klase čije su ovo instance), kao i veze koje su instance odgovarajućih asocijacija. Na stranama svake veze koja je instanca neke asocijacije nalaze se instance onih klasa koje povezuje ta asocijacija, ili klasa izvedenih iz njih (uključujući i tranzitivnost nasleđivanja).

9.16. Primeri Objektno – orijentisanih programa u C++: Primer 1. Projektovati klasu koja realizuje polinom sa realnim koeficijentima i realnim argumentom. U glavnom programu demonstrirati mogućnosti klase. // Modul pol.h #ifndef _pol_h #define _pol_h class iostream; class ostream; class Polinom { public: Polinom () : n(-1), a(0) {} Polinom (const double* coef, int numOfCoef); Polinom (const Polinom& p); ~polinom (); Polinom& operator= (const Polinom&); int getPower () const {return n;}; friend Polinom operator+ (const Polinom& p1, const Polinom& p2); friend Polinom operator- (const Polinom& p1, const Polinom& p2); friend istream& operator>> (istream& is, Polinom& p); friend ostream& operator<< (ostream& os, const Polinom& p); //Vraca vrednost polinoma u tacki x, P(x) double operator() (double x) const;; protected:

Base

Page 116: Objektno orijentisano programiranje

Miloš Milenov Programski jezici

479

//Brise ceo polinom void release (); private: int n; //Red polinoma double* a; //Niz koeficijenata polinoma }; #endif //Modul pol.cpp #include <iostream.h> #include "pol.h" void main () { double a1[3]={2.1,3.7,1.12}; double a2[4]={-2.1,1.3,-1.12,1.5}; Polinom p1(a1,2),p2(a2,3); cout<<p1; cout<<p2; int i=p1.getPower (); cout<< i <<"\n"; double s=p2(3); cout <<"\nVrednost polinoma p2(3)=" <<s<<"\n"; Polinom q=p1+p2; i=q.getPower(); cout<<q; q=p1-p2; cout<<q; }

Primer 2. Projektovati klasu koja realizuje matricu proizvoljnih dimenzija. Dimenzije matrice se zadaju pri njihovom nastanku, u vreme izvršavanja programa, što znači da je matrica dinamička. Smatrati da su elementi matrice celi brojevi.

// Modul matrix.h #include <stdlib.h> #include <iostream.h> #ifndef _Matrix #define _Matrix class istream; class ostream; typedef int Type; class Matrix {public: Matrix (int m, int n); Matrix (const Matrix& mat); ~Matrix (); Matrix& operator = (const Matrix&); friend int operator == (const Matrix&, const Matrix&); friend int operator != (const Matrix&, const Matrix&); Type& operator () (int i, int j); friend Matrix operator + (const Matrix&); friend Matrix operator - (const Matrix&); friend Matrix operator + (const Matrix&, const Matrix&); friend Matrix operator - (const Matrix&, const Matrix&); friend Matrix operator * (const Matrix&, const Matrix&); friend Matrix operator * (const Matrix&, const Type&); friend Matrix operator * (const Type&, const Matrix&);


Recommended