740 likes | 1.07k Views
Objektno programiranje (C++). Pravila ocjenjivanja. Održat će se dva kolokvija. Svaki nosi po 30 bodova. Za prolaz treba imati 25 bodova. Treba predati rješenja dviju domaćih zadaća. Svaka zadaća nosi 20 bodova. Minimum za prolaz je 15 bodova.
E N D
Pravila ocjenjivanja Održat će se dva kolokvija. Svaki nosi po 30 bodova. Za prolaz treba imati 25 bodova. Treba predati rješenja dviju domaćih zadaća. Svaka zadaća nosi 20 bodova. Minimum za prolaz je 15 bodova. Popravni kolokvij (maksimum je 45, treba skupiti 20 bodova).
Gradivo Ocjene: 45-59 bodova: dovoljan (2) 60-74 boda: dobar (3) 75-85 bodova: vrlo dobar (4) 86-100 bodova: izvrstan (5)
Ciljevi kolegija: Uvod u objektno orijentirano i generičko programiranje kroz programski jezik C++ Detaljna prezentacija C++-a Pripadne standardne biblioteke: STL, Boost Napredne tehnike programiranja, softverski predlošci
Gradivo Klase Kontrola kopiranja Operatori Spremnici Iznimke Imenici Nasljeđivanje Polimorfizam RTTI Predlošci
Nastavni materijali Stranica kolegija: http://web.math.hr/nastava/opepp Vježbe: http://web.math.hr/~vpetrice/opepp Preporučuju se i slideovi kolegija Računarski praktikum 4 (Botinčan, Petričević, Puljić).
Literatura Osnovna: Stanley B. Lippman, JoséeLajoie, Barbara E. Moo: C++ Primer, FourthEdition, AddisonWesley Professional, 2005. Julijan Šribar, Boris Motik: Demistificirani C++, 2. izdanje, Element, Zagreb, 2006. BruceEckel: Thinkingin C++, 1. i 2. dio (besplatna e-knjiga) :http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.html
Literatura Napredna: BjarneStroustrup: The C++ ProgrammingLanguage, AddisonWesley, 2000. Scott Meyers: Effective C++, Third Edition, Addison-Wesley, 2006. Scott Meyers: More Effective C++, Addison-Wesley, 1996. HerbSutter: Exceptional C++, AddisonWesley, 2000. Stephen C. Dewhurst: C++ CommonKnowledge, AddisonWesley, 2005. ErichGamma, et. all: DesignPatterns: ElementsofReusableObject-OrientedSoftware (Addison-Wesley Professional ComputingSeries).
Povijest Programski jezik C++ nastao je razvojem jezika C i zadržava visoku kompatibilnost s njime. Kreator jezika je Bjarne Stroustrup (http://www.research.att.com/~bs/). Razvoj je započeo 1979. g.; ime C++ skovano je 1983. godine, a prva komercijalna implementacija pojavila se 1985. Između 1985. i 1989. jezik je doživio veće inovacije: zaštićeni članovi, parametrizirane klase, višestruko nasljeđivanje itd. Jezik je 1997. godine definiran standardnom koji nosi ime ISO/IEC 14882:2003. Planira se novi standard C++0x.
Objektno orijentirano programiranje U centru interesa softverskog inženjerstva su složene aplikacije. Problemi: Održivost -- mogućnost lakog održavanja koda. Proširivost -- mogućnost lakog dodavanja nove funkcionalnosti.
Tehnike programiranja Proceduralna: Razlaganje programa na manje cjeline -- implementacija pomoću funkcija procedura. Nemogućnost definiranja apstrakcije koncepata iz aplikacijske domene. Ne nudi podršku za postizanje održivosti i proširivosti. "Svemoguća" main funkcija.
Tehnike programiranja Objektno orijentirana: Klase predstavljaju koncepte iz aplikacijske domene. Program se sastoji od objekta (instance klasa) i njihove komunikacije. Objekti imaju punu odgovornost za svoje ponašanje.
Osnovni elementi OO tehnike: Apstrakcija -- Koncepti se reprezentiraju neposredno u programu, a izvedbeni detalji su skriveni iza sučelja (eng. interface) koje reprezentira koncept. Enkapsulacija -- Sposobnost osiguravanja da se apstrakcija koristi prema svojim specifikacijama. Enkapsulacijom se sprečava narušavanje apstrakcije, odnosno prodiranje implementacijskih odluka izvan granica apstrakcije. Polimorfizam -- Skrivanje različitih implementacija iza istog sučelja. Osigurava široku primjenjivost koda i reducira zavisnost o implementaciji. Nasljeđivanje -- Konstrukcija novih apstrakcija polazeći od već postojećih.
Generičko programiranje Pojam generičkog programiranja odnosi se na generalizaciju softverskih komponenti kako bi se lako mogle koristiti u različitim situacijama. Osnovni elementi generičkog programiranja u C++-u su parametrizirane klase i funkcije.
Reference Referenca na neki objekt je novo ime za taj objekt. Osnovna uloga reference je omogućiti prijenos parametara po referenci, dakle prijenos u kojem nema kopiranja parametra, već funkcija dobiva referencu na objekt. Referenca spada u tzv. složeni tip(engl. compoundtype), tip koji se gradi pomoću drugih tipova. Tu spadaju još pokazivači (na neki tip) i polja (nekog tipa). Budući da referenca uvijek mora referirati na neki objekt, slijedi pravilo: Prilikom deklaracije referenca mora biti inicijalizirana. int n=100; int &rn = n; float℞ // Greška pri kompilaciji. Neinicijalizirana referenca
Reference Na jedan objekt možemo imati više referenci, a kako je svaka referenca samo novo ime za objekt, svaka promjena objekta putem jedne reference vidljiva je i kroz sve druge. int n = 100; int &rn1 = n; int &rn2 = n; std::cout << “n= "<< n << std::endl; //100 std::cout << “rn1= "<< rn1 <<“, rn2=“<<rn2<< std::endl; //100,100 rn1++; std::cout << “n= "<< n << std::endl; //101 std::cout << “rn1= "<< rn1 <<“, rn2=“<<rn2<< std::endl; //101, 101
Reference Referenca može biti konstantna i tada može referirati na konstantan objekt. Obična referenca ne može referirati na konstantu: char &ra = 'a'; // greška pri kompilaciji. // Nekonstantna referenca, konstantan objekt. constchar &rb = 'b'; // o.k. Konstantna referenca nam garantira da kroz nju ne možemo promijeniti objekt na koji referira. Referenca je dakle isto što i pokazivač koji se automatski dereferencira. Za razliku od pokazivača, referenca koja referira na jedan objekt nikad ne može referirati na neki drugi objekt.
Reference Korištenjem referenci donekle ograničavamo prirodan način korištenja implicitnih konverzija. Na primjer, sljedeći kod javlja grešku pri kompilaciji. double x = 2.71; int &rx = x; S druge strane, sljedeći javlja samo upozorenje o konverziji double u int (jer je naravno moguć gubitak podataka): double x = 2.71; constint &rx = x;
Reference Kada prevoditelj treba referencu na jedan tip inicijalizirati objektom nekog drugog, kompatibilnog, tipa, on kreira privremeni objekt istog tipa kao i referenca, vrši konverziju i inicijalizira referencu tim privremenim objektom. Kod koji prevoditelj generira izgleda, dakle, ovako: double x = 2.71; inttmp = x; constint &rx = tmp; Nekonstantna referenca nekog tipa može biti inicijalizirana jedino objektom egzaktno tog istog tipa.
Reference Referencu možemo definirati i na pokazivač. int n = 100; int *pn = &n; int *&rpn = pn; // Referenca na pokazivač std::cout << "*rpn = "<< *rpn << std::endl; Nije dozvoljeno deklarirati pokazivač na referencu, a isto tako ni referencu na referencu. Polje referenci ne postoji. double &x[8]; // Polje referenci nije dozvoljeno – neispravno. Razlog je taj što svaka referenca mora biti inicijalizirana nekim objektom, što pri definiciji polja nije moguće.
Reference voidf(int *p) { std::cout<<*p<<std::endl; p++; } void g(int * & p) { std::cout<<*p<<std::endl; p++; } int a=3; int *ptr=&a; std::cout<<ptr<<std::endl; f(ptr); std::cout<<ptr<<std::endl; g(ptr); std::cout<<ptr<<std::endl;
Reference U C++ u koristimo reference kad ne želimo prijenos parametara po vrijednosti. Ako je formalni argument funkcije deklariran kao referenca nekog tipa, onda prilikom poziva funkcije neće doći do kopiranja stvarnog argumenta u formalni, već će formalni argument (koji je referenca) biti inicijaliziran stvarnim. Formalni argument tako postaje alias za stvarni argument i kroz njega možemo dohvatiti stvarni argument. Na taj se način postiže prijenos parametara po referenci. voidswap(int &x, int &y) { inttmp = x; x = y; y = tmp; } Prijenos po referenci vršimo kad želimo mijenjati stvarni argument ili kad je argument suviše velik za kopiranje.
Reference U drugom slučaju, kad samo želimo izbjeći kopiranje stvarnog argumenta, formalni argument treba deklarirati kao konstantnu referencu. S druge strane, kako je moguće konstantnu referencu inicijalizirati nekonstantnim objektom (const je samo obećanje da referenca neće služiti za mijenjanje objekta), funkcija koja deklarira konstantnu referencu može uvijek uzeti nekonstantan argument (koji, naravno, ne može u svom tijelu promijeniti).
Reference voidprint(std::string& text) { std::cout << text << std::endl; } std::string a("..."); print(a); // o.k. print("..."); // greška Bolje je dakle pisati: void print(const std::string& text) { std::cout << text << std::endl; }
Reference Ako je argument funkcije deklariran kao nekonstantna referenca, onda pri pozivu funkcije nije dozvoljena konverzija tipova za taj argument. Formalni i stvarni argument moraju biti istog tipa. Kad je referenca konstantna, onda je, kao što smo vidjeli, konverzija dozvoljena. Treba imati pri tome na umu da će prevoditelj generirati privremeni objekt i da će formalni argument referirati na njega.
Reference Kako se polja ne mogu kopirati, ne možemo napisati funkciju koja ima parametar tipa polje. Kad koristimo polje u izrazima, ono se automatski konvertira u pokazivač na prvi član polja. Sljedeće deklaracije su ekvivalentne. Interpretiraju se kao funkcija koja kao parametar prima pokazivač tipa int*. voidprint( int * ){ …..} voidprint( int[ ] ){ …. } voidprint( int[100] ){ …… }
Reference Funkcija koja uzima polje kao argument dobiva pokazivač na prvi element polja. Takva funkcija može uzeti polje bilo koje dimenzije. S druge strane, moguće je deklarirati funkciju koja uzima referencu na polje: intcount(int (&arr)[4]) { int x = 0; for(int i=0; i< 4; ++i) x += arr[i]; return x; } Funkcija count će uzimati samo polja dimenzije 4, jer nema implicitnih konverzija između polja različite veličine. Referenca na polje je dakle manje fleksibilna od pokazivača na polje, pa se stoga rijetko koristi kao parametar funkcije.
Reference Ako funkcija vraća referencu, tada ne dolazi do kopiranja vrijednosti, već samo do kopiranja reference. Za velike objekte to može biti velika ušteda. Referenca kao povratna vrijednost ima još i tu prednost da predstavlja vrijednost koja se može naći na lijevoj strani znaka jednakosti (lvalue). char& get(std::string& s, unsigned i) { return s[i]; } std::string s1("abc"); get(s1,2)='z';
Reference Moramo paziti da nikad ne vratimo referencu na lokalnu varijablu jer će ona nakon vraćanja reference na nju biti uništena. Isto naravno vrijedi i za pokazivač na lokalnu varijablu. // Neispravan kod std::string& ccn(conststd::string& s1, conststd::string& s2){ std::stringtmp = s1+s2; returntmp;// greška } Funkcija ne smije nikada vratiti referencu ili pokazivač na lokalnu varijablu.
Pokazivači na funkcije Kao i ostali pokazivači, i pokazivač na funkciju pokazuje na točno određeni tip funkcije. Pritom se uzima u obzir i povratni tip i lista parametara. Primjer: Pokazivač na funkciju koja prima const string referencu i int, a vraća int, deklariramo na sljedeći način: int (*fptr)(const string &, int ); Napomena: zagrade oko *fptr su nužne jer je int *fptr(const string &, int ); deklaracija funkcije koja ima jednaku listu parametara i vraća pokazivač na int.
Pokazivači na funkcije Zbog kompliciranosti sintakse, preporučljivo je uvesti novo ime za pokazivač na funkciju: typedef int (*fptrType)(conststring &, int); Ukoliko koristimo ime funkcije za poziv funkcije, ona se i tretira kao funkcija. U ostalim slučajevima se automatski tretira kao pokazivač na funkciju pripadnog tipa.
Pokazivači na funkcije Neka je deklarirana odgovarajuća funkcija: int f(const string &, int ); Pokazivač na funkciju može se inicijalizirati samo s funkcijom ili pokazivačem na funkciju jednakog tipa ili s konstantnim izrazom jednakim nula. Ne postoji konverzija između pokazivača na funkcije različitih tipova (moraju se slagati i lista parametara i povratni tip). fptrType ptr1=f; ptr1=&f; fptrType ptr2=0; ptr2=ptr1;
Pokazivači na funkcije Možemo uvesti novo ime i za sam tip funkcije. typedefint (*fptrType)(const string &, int ); typedefintfType(const string &, int ); int f(const string &, int ); fptrType fptr1=f; fType *fptr2=fptr1;
Pokazivači na funkcije Pokazivač na funkciju koji nije inicijaliziran ili ima vrijednost nula ne može se koristiti za poziv funkcije. Pri pozivu funkcije možemo koristiti i pokazivač na funkciju i dereferencirani pokazivač. int f(conststring &s, int a){ cout << s; return ++a; } fptrTypefptr=&f; int a=(*fptr)("Hello",1); int b=fptr("Hello",1);
Pokazivači na funkcije Pokazivač na funkciju može biti i parametar neke funkcije. Pritom su ekvivalentne sljedeće deklaracije: typedefint (*fptrType)(const string &, int ); void Test(int ,int(const string &, int )); void Test(int ,int (*) (const string &, int )); void Test( int , fptrType );
Pokazivači na funkcije Funkcija može i vratiti pokazivač na funkciju. Pritom povratna vrijednost mora biti baš pokazivač, ne može biti sama funkcija. Preporučljivo je koristiti typedef za tip pokazivača. Ekvivalentno je: int (*f( int ))(const string &, int ); fptrType f( int ); Nije dozvoljeno deklarirati funkciju tipa fTypeff( int );
Pokazivači na funkcije ¸Moguće je definirati i polje pokazivača na funkcije. void f1( int i) { cout<<"Funkciji f1 proslijedili ste broj "<<i; } void f2( int i ) { cout<<"Funkciji f2 proslijedili ste broj "<<i; } void f3( int i ){ cout<<"Funkciji f3 proslijedili ste broj "<<i; } void (*fp[3])( int )={ f1,f2,f3 }; fp[1](13);
Pokazivači na funkcije Zadatak: Definirane su funkcije bool ascending ( int a, int b ) { return a<b ; } bool descending ( int a, int b ) { return a>b ; } Napišite definiciju funkcije deklariranu s void Sort ( int [ ] , const int , bool(*) (int, int)); koja sortira polje int-ova u rastućem ili padajućem poretku. Napišite main koji testira funkciju.
Konverzije Izrazi mogu biti sastavljeni od operanada različitih tipova ukoliko se svi mogu konvertirati u odgovarajući zajednički tip. Prevoditelj će prije izvršenja operacije izvršiti konverziju operanada u taj zajednički tip. Budući da se radi o konverzijama koje se rade bez eksplicitnog zahtjeva od programera govorimo o implicitnim konverzijama.
Konverzije Implicitne konverzije se dešavaju u ovim situacijama: Kod aritmetičkih, relacijskih i logičkih izraza. Kod testiranja u if, while, for i do while naredbama dolazi do konverzije u tip bool. Kod izraza pridruživanja (=) dolazi do konverzije u tip varijable na lijevoj strani. Ako pri tome dolazi do gubitka preciznosti, konverzija je svejedno legalna, jedino što će prevoditelj dati upozorenje. Kod poziva funkcije, ako stvarni i formalni argumenti nisu istog tipa.
Konverzije Najčešće su aritmetičke konverzije. Osnovno pravilo je da dolazi do konverzije u najširi tip u izrazu s ciljem da se sačuva preciznost rezultata. Integralna promocija: svi se integralni tipovi manji od int (char, signedchar, unsignedchar, short, unsignedshort) pretvaraju u int, ako je to moguće, a ako ne, onda u unsigned int. Kada se tip bool pretvara u int, onda se true pretvara u 1, a false u nulu.
Konverzije Ostale konverzije: U većini izraza polje se konvertira u pokazivač na prvi član polja. To se ne dešava kod primjene adresnog operatorai sizeofoperatora te kad se poljem inicijalizira referenca na polje. U izrazima testiranja pokazivač se pretvara u bool tip: null-pokazivač se konvertira u false, svaki drugi u true. Svaki pokazivač se može konvertirati u void *. Nula (0) se može konvertirati u pokazivački tip. Enumeracija se konvertira u integralni tip koji je strojno zavisan. Nekonstantan objekt se može konvertirati u konstantan. Pokazivač na konstantan tip može se inicijalizirati adresom nekonstantnog objekta. Napomena: klase definiraju svoje vlastite konverzije.
Konverzije Od prevoditelja možemo eksplicitno zahtijevati da napravi konverziju tipova, ukoliko je takva konverzija dozvoljena. U C-u bismo to učinili izrazom (T) izraz; gdje je T tip u koji konvertiramo izraz. Jednako je dozvoljen izraz oblika T(izraz). Primjer: izbjegavanje cjelobrojnog dijeljenja. int x = 3; int y =4; double z=3.24; z = x/y; // cjelobrojno dijeljenje z = double(x)/y; // realno dijeljenje ili z = (double) x/y; // realno dijeljenje
Konverzije Ista je sintaksa dozvoljena i C++-u, no ovdje se umjesto jednoga uvodi četiri specijalizirana operatora konverzije: static_cast<T>(izraz); const_cast<T>(izraz); dynamic_cast<T>(izraz); reinterpret_cast<T>(izraz); Prednost specijaliziranih operatora je bolja dijagnostika grešaka, a ekspresivnija sintaksa omogućava lakše uočavanje eksplicitnih konverzija u kodu.
Konverzije static_cast<T> služi za sve one konverzije koje prevoditelj radi implicitno. Većinu tih konverzija on može učiniti i u suprotnom smjeru. Na primjer, svaki se pokazivač implicitno može konvertirati u pokazivač na void, ali obratna konverzija se ne dešava automatski. Moguće ju je tražiti eksplicitno: void *pv = &x; int *pi; pi = pv; // Greška pri kompilaciji pi = static_cast<int *>(pv); // o.k. Ovaj je kod ispravan samo ako je varijabla x kojom smo incijalizirali pokazivač pi tipa int. Prevoditelj, općenito, nije u stanju detektirati stvarni tip varijable čija je adresa uzeta. Stoga se greške u uporabi eksplicitnih konverzija pokazuju za vrijeme izvršavanja programa.
Konverzije Pomoću static_cast<T> operatora ne možemo pretvoriti konstantan objekt u nekonstantan. U tu svhu služi const_cast<T>. const_cast<T>(izraz) uklanja konstantnost izraza. Pretpostavimo, na primjer, da imamo funkciju koja uzima nekonstantnu referencu na tip, ali ne mijenja stvarni argument. Tu funkciju ne možemo pozvati s konstantnim stvarnim argumentom. void f(int& i) { // ... } // .... constint x = 3; f(x); // Greška pri kompilaciji f(const_cast<int &>(x)); // o.k const_cast<T> se može primijeniti samo na pokazivačima i referencama.
Konverzije dynamic_cast<T>(izraz) se koristi za konverziju pokazivača ili reference na baznu klasu u pokazivač ili referencu na izvedenu klasu. reinterpret_cast<T>(izraz) vrši konverzije zavisne o implementaciji kao što je konverzija pokazivača u int. Radi se o reinterpretaciji niza bitova koja ovisi o sustavu na kojem se vrši i stoga nije prenosiva s računala na računalo. Rijetko se koristi, a ima svoje mjesto u sistemskom programiranju. Eksplicitne konverzije su opasne jer dovode do grešaka za vrijeme izvršavanja koje nije moguće otkriti za vrijeme kompilacije. One često indiciraju grešku u dizajnu programa i najčašće se mogu izbjeći. Stoga ih treba koristiti u najmanjoj mogućoj mjeri.
Preopterećenje funkcija Dvije funkcije u istom dosegu su preopterećene ako imaju isto ime,ali različitu listu parametara. U jeziku C funkcija je na jedinstven način identificirana svojim imenom pa stoga različite funkcije moraju imati različita imena. Primjer: double sin(double) i floatsinf(float). U C++ jeziku funkciju identificira njena signatura (potpis) koja se sastoji od imena funkcije te liste njezinih parametara. Nije moguće preopteretiti dvije funkcije samo na osnovu različitog povratnog tipa, odnosno ne mogu postojati dvije funkcije istog imena, broja i tipa parametara, koje bi se razlikovale samo po tipu povratne vrijednosti.
Preopterećenje funkcija U nekim se situacijama parametri, koji se čine različitim, ne tretiraju kao različiti i prevoditelj javlja grešku redefiniranja funkcije. To su sljedeće situacije:
Preopterećenje funkcija Typedef ne uvodi novi tip već samo predstavlja novo ime za stari tip i stoga ne može biti osnova za razlikovanje: void f(double y) { cout << "f(double)"<<endl; } typedefdouble R; void f(R y) { cout << "f(R)"<<endl; } // Greška, redefinicija