Top Banner
NASLEĐIVANJE Nasleđivanje je veza subordinacije. Uspostavlja se između dva pojma u smislu da je jedan opštiji (generalizacija-specijalizacija). Nasleđivanje=izvođenje=inheritance primeri: Svaki budilnik je sat, ali nije svaki sat budilnik; klasa funkcija nasleđuje klasu relacija jer je svaka funkcija relacija Pod nasleđivanjem podrazumevamo preuzimanje kompletnog sadržaja druge klase uz mogudnost dodavanja članova i modifikacije metoda; Nasleđivanje omoguduje višekratnu upotrebu Klasa A je direktan predak pa se naziva roditelj, a klasa B je direktan potomak pa se naziva naslednik Ili klasu A možemo zvati bazna klasa, a B je tada izvedena klasa Ili klasu A možemo zvati natklasa, tada je B potklasa opšte osobine nasleđivanja: 1. za izvođenje/realizaciju nasleđivanja se ne koristi izvorni kod natklase 2. mogude je proširivanje dodavanjem novih članova 3. mogude su modifikacije metoda (redefinisanje) – vrši se ponovnim pisanjem metode u potklasi pod istim imenom (koje je u natklasi) 4. tranzitivnost (ako C nasleđuje B, a B nasleđuje A, onda i C nasleđuje A) 5. postoji i višestruko nasleđivanje (klasa može da ima više roditeljskih klasa, ali je ono retko i postoji samo u C++) DEMETRIN ZAKON Metode date klase ne smeju da zavise od strukture ma koje druge klase osim roditeljske. A B
13

C++

Dec 27, 2015

Download

Documents

AA RR

tt
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: C++

NASLEĐIVANJE

Nasleđivanje je veza subordinacije. Uspostavlja se između dva pojma u smislu da je jedan opštiji (generalizacija-specijalizacija).

Nasleđivanje=izvođenje=inheritance

primeri: Svaki budilnik je sat, ali nije svaki sat budilnik; klasa funkcija nasleđuje klasu relacija jer je svaka funkcija relacija

Pod nasleđivanjem podrazumevamo preuzimanje kompletnog sadržaja druge klase uz mogudnost dodavanja članova i modifikacije metoda; Nasleđivanje omoguduje višekratnu upotrebu

Klasa A je direktan predak pa se naziva roditelj, a klasa B je direktan potomak pa se naziva naslednik

Ili klasu A možemo zvati bazna klasa, a B je tada izvedena klasa

Ili klasu A možemo zvati natklasa, tada je B potklasa

opšte osobine nasleđivanja: 1. za izvođenje/realizaciju nasleđivanja se ne koristi izvorni kod natklase 2. mogude je proširivanje dodavanjem novih članova 3. mogude su modifikacije metoda (redefinisanje) – vrši se ponovnim

pisanjem metode u potklasi pod istim imenom (koje je u natklasi) 4. tranzitivnost (ako C nasleđuje B, a B nasleđuje A, onda i C nasleđuje A) 5. postoji i višestruko nasleđivanje (klasa može da ima više roditeljskih klasa,

ali je ono retko i postoji samo u C++)

DEMETRIN ZAKON

Metode date klase ne smeju da zavise od strukture ma koje druge klase osim roditeljske.

A

B

Page 2: C++

REALIZACIJA NASLEĐIVANJA U C++

class B : ⍺ A {

//dodata polja

//dodate metode

//redefinisane metode

};

⍺ može da bude private, public ili protected

private – dostupno samo u toj i ni u jednoj drugoj klasi (primer gde demo sigurno staviti private, a ne protected je pri nasleđivanju steka iz liste jer neke metode ne smeju biti dozvoljene u steku, a postoje u listi)

protected – nedostupno klijentu, ali je dostupno klasi koja je nasleđuje; pošto private delu nema pristup ni jedna klasa bolje je pisati protected u klasama za koje postoji verovatnoda da de biti nasleđene

public – otvoreno i za klijenta i za potomka

efektivna kontrola pristupa:

vrsta nasleđivanja

član bazne klase

public protected private

public public protected

nedostupno protected protected protected

private private private

šta se ne nasleđuje: 1. osnovni operator dodele = definisan je u svakoj klasi; ukoliko je u

baznoj klasi bio preklopljen, mora se preklopiti ponovo u izvedenoj klasi inače de se vratiti na svoju osnovnu funkciju

2. prijateljstvo; ako je funkcija bila prijateljska za baznu klasu, nede biti prijateljska i za izvedenu

3. konstruktori i destruktor; imana se poklapaju sa imenom klase, a bazna i izvedena klasa nemaju isto ime

redefinisanje Neka je B izvedena klasa. I neka je metoda met() u njoj redefinisana. Poziv B.met() pozvade redefinisanu metodu met(). Mogud je i poziv neredefinisane verzije metode met(). S obzirom da redefinisana metoda najčešde radi sve isto što i njena prvobitna verzija + još nešto, često unutar izvedene klase prilikom redefinisanja prvo pozivamo verziju metode iz bazne klase sa A::met().

A

B

Page 3: C++

KONSTRUISANJE I DESTRUKCIJA U AMBIJENTU NASLEĐIVANJA

Prilikom pisanja klase ne znamo ništa o precima, osim ponešto o roditeljskoj klasi iz koje nasleđujemo. Potrebno je prilikom pravljenja klase obezbediti poziv konstruktora njoj roditeljske klase, pa tek onda obezbediti konstrukciju dela koji je dodat. Konstruktor klase X počinje pozivom konstruktora klase An, tako da se omogudi izvršenje prvo konstruktora najstarijeg pretka i tako redom do konstruktora klase koju pravimo. Druga mogudnost je da ne napišemo poziv konstruktora natklase, ali de automatski biti pozvan podrazumevani konstruktor.

Destrukcija. Destruktori se pozivaju obrnutim redosledom od konstruktora. Destruktor klase X uništava samo dodati deo, potom poziva destruktor roditeljske klase da uništi svoj deo i tako redom do destruktora bazne klase.

INKLUZIONI POLIMORFIZAM

Svaki objekat klase može se ponašati kao objekat klase koja joj je predak. Primer: svaki objekat klase budilnik može se ponašiti kao objekat klase sat.

Inkluzioni polimorfizam se javlja u kontekstu pridruživanja, a pridruživanje se javlja u 3 slučaja: dodela, zamena parametara argumentima i formiranje rezultata funkcije (kada je objekat potomak, a rezultat predak).

Potomak može da se pridrruži pretku, tj. potomak može da se ponaša kao predak, ali obrnuto ne važi.

Ako za svaki objekat s klase S postoji objekat k klase K takav da se proizvoljni program definisan nad k ponaša isto kada se k zameni sa s tada je S klasa izvedena iz K.

A1

A2

An

X

linija izvršavanja konstruktora

linija poziva konstruktora; linija izvršavanja destruktora

Page 4: C++

primeri:

u klasi predak imamo metodu m(1), a u potomku je ona redefinisana pa imamo verziju m(2). Imamo objekte i pokazivače.

Predak pr, *pPr

Potomak po, *pPo

*direktna dodela

pr=po (retko)

*pPr=*pPo

levi objekat dovodi se u isto stanje kao i desni; osobine koje ima desni, a nema levi se jednostavno ne prepisuju.

pPr=pPo (dešava se pPr=(Predak *)pPo)

pPrm( )

Očekivali bismo da de biti pozvana redefinisana metoda m(2), međutim bide pozvana m(1) što de izazvati problem koji se rešava uvođenjem virtuelnih funkcija. Problem može nastati na primer jer potomak ima naka dodatna polja koja nede biti pravilno određena pozivom metode iz pretka jer predak ta polja ne sadrži.

do dodele ga smatram budilnikom, nakon dodele smatram ga samo satom, ali on i dalje ima sve mehanizme budilnika.

*zamena parametara argumentima

void f (Predak); //Ukoliko kao parametar f-je stoji objekat tipa Predak ili referenca

f(po); // tipa Predak, tada toj funkciji moze biti prosledjen obj. Potomak

void g (Predak &); // pri cemu ce se taj objekat ponasati kao objekat klase Predak.

g(po);

*formiranje rezultata funkcije

Predak h(....) { Potomak w; //objekat potomak, a rezultat predak ..... return w;}

Predak

Potomak

pr

po

pPr pPo

Page 5: C++

VIRTUELNE FUNKCIJE (METODE)

Imamo klasu A i iz nje izvedenu klasu B. U klasi A je prvi programer napravio metode m i k, gde se k poziva iz m. Drugi programer koji je pravio klasu B reši da mu metoda m odgovara, ali metodu k redefiniše. Kada neki tredi programer napiše B b; b.m(); očekuje da de ovo raditi, odnosno da de biti pozvana redefinisana verzija metode k. Međutim, u mašinskom kodu metode m nalazi se poziv neredefinisanog k, te ovo nede raditi.

Rešenje je da se svakoj klasi doda V-tabela u kojoj se nalaze adrese metoda koje podležu redefinisanju (to obavlja prevodilac). Tada se u mašinski kod od metode m ubacuje pregledač V-tabele pa do greške nede dodi. Metode iz V-tabele su virtuelne i tu se nalaze i podaci o veličini objekata.

virtual tip ime (par) { . . . }

Preovladavaju virtuelne metode, a metoda se proglašava virtuelnom dodavanjem reči virtual. Proglašavanje metode virtuelnom ne može da dovede do greške, pa kad god postoji šansa da de metoda u nekoj nasleđenoj klasi biti redefinisana, treba je proglasiti virtuelnom. Prilikom redefinisanja reč virtual se ne piše, a parametri se moraju poklopiti sa parametrima originalne metode. Kada imamo virtuelne metode i destruktor treba da bude virtuelan.

Prilikom poziva pky( ) ne zna se koja verzija y( ) je pozvana jer se ne zna na koji objekat (iz koje klase) pokazuje pk. Tek kada je metoda ved pozvana saznaje se koja verzija je pozvana. To se zove kasno ili dinamičko povezivanje.

A

B

m k

k &k

&k

v.a

v.b

Page 6: C++

APSTRAKTNE METODE I KLASE

Trudimo se kad imamo klase koje se poklapaju po nekim osobinama, da im napravimo zajedničkog pretka (klasa Figura); Kada pravimo zajedničkog pretka u njemu treba da se nalaze sve zajedničke osobine potklasa, ako neke od metoda ne možemo da napišemo, realizujemo ih kao apstraktne.

Apstraktna metoda je metoda koja nema realizaciju (primer: obim figure); Ona nikada nije pozvana i uvek je virtuelna i mora biti redefinisana (dobija semantiku redefinisanjem). Pravilnije je redi operacionalizacija umesto redefinisanje jer ne možemo redefinisati nešto što nije definisano. Klasa sa bar jednom apstraktnom metodom je apstraktna klasa.

class Figura {

public:

virtual double obim( )=0;

};

Ne može:

Figura f;

f.obim( );

Ovaj problem je rešen tako što apstraktna klasa ne može da se instancira.

Može:

Figura *pF;

pF=new Krug(5);

pf=new Kvadrat(10);

VIŠESTRUKO NASLEĐIVANJE

Podrazumeva nasleđivanje od više natklasa. Postoji samo u C++. U drugim programskim jezicima ne može se u potpunosti realizovati (samo u nekoj meri preko kompozicije). Razlog ove nemogudnosti jeste nepostojanje inkluzionog polimorfizma. Ovakvo nasleđivanje može da izazove velike probleme i manje je važno od jednostrukog nasleđivanja.

Svrha i primena:

X de višestruko naslediti klasu A i klasu B ako se svaki objekat klase X u svakom trenutku ponaša i kao objekat klase A i kao objekat klase B. X nasleđuje sve od A i sve od B. (radio sat u svakom trenutku možemo koristiti ili samo kao sat ili samo kao radio; ulazno izlaznu datoteku možemo u svakom trenutku koristiti ili samo kao ulaznu ili samo kao izlaznu). Česta je pogrešna

Pravougaonik

Kvadrat

Krug Trougao

Figura

Page 7: C++

upotreba (pegaza ne možemo posmatrati samo kao konja ili samo kao pticu, on ni u jednom trenutku nije ni samo konj ni samo ptica).

class X : ⍺A, βB {

. . .

};

⍺ i β mogu biti private, protected ili public

Tehnički problemi:

1. Problem istoimenih metoda

u A i u B postoji metoda m( ), klijent odabira koju želi da iskoristi sa A::m ili B::m

2. Problem ponovljenog nasleđivanja

pojava duplog sadržaja u X rešava se upotrebom virtuelnog nasleđivanja (ali je to daleko od idealnog rešenja)

class P : virtual ⍺U { . . . }

class Q : virtual βU { . . . }

sada se isti sadržaj koji potiče iz U nede pojaviti duplo u klasi X, problem je što se potreba za tim ne zna u trenutku pravljenja klasa P i Q

A B

X

A B

X

Q P

U

Page 8: C++

GENERIČKE KLASE

Generičke klase su jedan od oblika parametarskog polimorfizma. Kod generičkih klasa u trenutku definisanja se ne zna koji su parametri, ali se prilikom instanciranja zadaju konkretne klase/tipovi. Kontejnerske klase su najčešde generičke. Omogudava jednostavno korišdenje na primer liste u jednom trenutku sa podacima tipa int u slededem sa podacima tipa double.Generičnost se postiže uz pomod šablona ili template-a.

2 načina prikaza:

Na slici levo imamo listu parametara i stereotip koji govori koji su konkretni tipovi. Na slici desno navode se konkretni tipovi.

Kada imamo template naredba class se proširuje.

template <class P1, . . . ,class PN>

class A {

. . .

};

Umesto class kod parametara možemo pisati typename. Generičke parametre koristimo kao da znamo koji su pri definisanju polja, parametara i rezultata metoda. (primer: P1 x,y; {...x+y...}; iako se ne zna kog su tipa x i y ovakav zapis se koristi, a ako se desi da se prilikom instanciranja x i y ne mogu sabrati, greška de biti javljena na vreme). Puna identifikacija generičke klase nije samo ime ved i lista parametara.

primer: tip A<P1, . . . ,PN>::imeMetode(par){ . . .}

Da bi se izvršilo instanciranje, moraju se navesti konkretni generički argumenti. Kompletan opis generičke klase mora da se nađe u zaglavlju. Generičke klase se ne mogu prevesti u napred. Generički argument može da bude klasa ali i tip podataka.

primer:

A<A1, ... ,AN> inst1

A<B1, ... ,BN> inst2

kon_klasa

<<bind>><A1, . . . ,AN>

gen_klasa

P1, . . . ,PN

GEN_KLASA <A1, . . . ,AN>

Page 9: C++

GENERIČKI STEK

Ukoliko želimo da stek ima od slučaja do slučaja različitu količinu elemenata definišemo samo tip konstante i prilikom instanciranja steka mi definišemo njegovu veličinu. Objekti koju su instancirani u mainu sa različitim parametrima nisu međusobno kompaktibilni jer nisu istog tipa. C++ dozvoljava da generičke klase nasleđuju i da budu nasleđene, ali se to ne radi često. template <class T, int capacity> class Stack { private: int t; T s[capacity]; public: Stack() {t=-1;} int empty () const { return t<0; } int full () const { return t==capacity-1; } T top () const { return s[t]; } void pop () {t--;} void push (T el) {S[++t]=el;} }; Stack <int,500> iS; Stack <char,1000> cS; Stack <int,2000> iS1;

PREVENCIJA OTKAZA

greška=error (unošenje pogrešnog koda)

defekt=fault (rezultat greške programera)

otkaz=failure (kada se izvrši defektna naredba; može i ne mora biti vidljivo; netačan izlaz iako mi to ne znamo)

incident (došlo je do otkaza i to se vidi; ako uspemo da izazovemo incident zaključujemo da u programu postoji greška koju je potrebno pronadi)

testiranje programa: neka je klasa stek korektna; aktiviramo metodu pop u neregularnim uslovima (prazan stek)dolazi do greške u klijentu 4 postupka za sprečavanje:

1. prepustiti klijentu da proveri if(empty), ali nije dobro opteretiti klijenta 2. havarijski prekid programa-klijent je u potpunosti oslobođen; u kod se uključuje

provera uslova u okviru funkcije, pa ukoliko su uslovi neregularni program se prekida; ozbiljan nedostatak je da je neregularna akcija na jednom, a neregularna reakcija na potpuno drugom mestu; ovo se koristi samo u slučaju da se više ništa ne sme raditi ukoliko se pojave neregularni uslovi

3. korišdenje rezultata programa kao koda uspešnosti-kao rezultat javlja se podatak da li je metoda izvršena uspešno ili ne (izlaz se kodira kao enumeracija); klijent predvidi određenu reakciju za neuspešno izvršenu metodu; problem se javlja kod metoda koje vradaju neku vrednost, pa se rezultat mora vratiti preko liste parametara ili ako

Page 10: C++

rezultat funkcije mora da zadovolji određene uslove stavlja se da funkcija vrada neku specijalnu (nemogudu) vrednost kao znak da je došlo do greške enum ErrorCode {ok, underflow, overflow}; ErrorCode push (T el) { if (t==CAPACITY-1) return overflow; s[++t]=el; return ok; }

4. Opšte rešenje-proširenje jezika rukovanjem izuzecima

RUKOVANJE IZUZECIMA (exception handling) Izuzetak je događaj do kog dolazi da ne bi došlo do otkaza; Otkazom se ne može

rukovati, ali izuzetkom može. Mehanizam-kroz jedan kanal klijent metodi šalje parametre, kroz drugi metoda klijentu

vrada rezultat, sada se otvara i tredi kanal za slanje podataka o nastalom izuzetku. Mora postojati sistem za generisanje kao i sistem za prijem podataka o izuzetku. Ti podaci se mogu formirati na 2 načina i poslati u klijent:

*kao vrednosti (u C ali i u C++) *kao objekti (samo u C++) U objektnom programiranju se koriste oba načina, s tim da je rukovanje izuzecima kao

vrednostima jednostavnije, ali kao objektima daleko modnije. Naredba za generisanje je THROW, a za prijem TRY; Na problem se reaguje tamo gde je

nastao-u klijentu; Ovaj način prevencije otkaza rešava sve probleme ali je komplikovan. THROW throw izraz; (kada je izraz konstanta enumeracije; izuzeci kao vrednosti) throw Konstruktor(arg); (pravimo klasu izuzetaka sa metodama koje sadrže pripremljenu

reakciju; izuzeci kao objekti) TRY piše se u klijentu try { //naredbe klijenta(neke ili sve) }catch (E1 e) { . . . //E1,E2 su tipovi izuzetaka, e je ime }catch (E2 e){ //catch je exception handler; za 1 try bar 1 handler . . . } . . . catch (. . .) { //univerzalni handler . . . }

Page 11: C++

Lista catch-eva se pregleda linearno i traži se prvi rukovalac koji može da prihvati dati izuzetak; kad se taj deo izvrši izlazi se iz try; poslednji se po potrebi navodi univerzalni handler koji prihvata sve izuzetke.

RUKOVANJE IZUZECIMA KAO VREDNOSTIMA Koristi se za jednostavnije slučajeve, kada nije potrebno predvideti reakcije.

enum ErrorCode {OK, OVERFLOW, UNDERFLOW}; //ok se mora napisati čak i ako se ne koristi void push (T el) {

if (t==CAPACITY-1) throw OVERFLOW; s[++t]=el;

} T top( ) const { if (t<0) throw UNDERFLOW;

return s[t]; } try { //naredbe, pozivi, pop, push, top… }catch (ErrorCode err) {

switch (err) { case UNDERFLOW: cout<<”stack underflow”<<endl;break; case OVERFLOW: cout<<”stack overflow”<<endl;break; }

} Ako je ikako mogude pokušamo da vratimo program u stanje pre izuzetka pa da se

problem automatski ispravi.

Page 12: C++

RUKOVANJE IZUZECIMA KAO VREDNOSTIMA Pravimo klasu izuzetaka sa metodama koje sadrže pripremljenu reakciju. Pošto imamo dve klase izuzetaka koje imaju neke zajedničke osobine, mogu se smatrati naslednikom neke zajedničke klase, radi jednostavnosti glavnog programa realizujemo klasu StackException (zajednička roditeljska klasa) kao apstraktnu. class StackException { public: virtual void action( )=0; }; class StackUnderflow : public StackException { public: void action( ) { cout<<”stack underflow”<<endl; } }; class StackOverflow : public StackException { public: void action( ) { cout<<”stack overflow”<<endl; } }; T top ( ) const { if (t<0) throw StackUnderflow( ); return s[t]; } void push (T el) { if (t==CAPACITY-1) throw StackOverflow( ); s[++t]=el; } U klijentu: try { //neke naredbe }catch (StackException &e) { e.action( ); }

Page 13: C++

PROPAGACIJA IZUZETAKA Izuzetak ne sme da prođe neprimedeno! U fn nastao izuzetak. Tada svaka prethodna

funkcija može da reaguje na tri načina: 1. Ako u klijentu nema try fn-1 se prekida kada stigne izuzetak i on se prosleđuje višoj

instanci; ako ni u jednoj nema try program se zaustavlja 2. Ako u klijentu ima try onda se izvršava ono što je predviđeno rukovaocima i tada

nema propagacije 3. Ako u fn-1 ima try, kada se obrada izuzetka završi on se prosleđuje višoj instanci

try { . . . }catch (E e) { obrada throw e; }

f0

f1

fn-1

fn