Objektno orijentirano programiranje http www oss unist hrncaklovioop
Objektno orijentirano programiranje http: //www. oss. unist. hr/~ncaklovi/oop Nenad Čaklović nenad. caklovic@oss. unist. hr
1. predavanje • odlike objektnog programiranja • uvod u programski jezik C++ • razlike C i C++
Objektno programiranje • problemi proceduralnog programiranja: – teško održavanje velikih projekata – osjetljivost na promjene u implementaciji – nemogućnost dodavanja novih tipova i redefinicije operatora • odlike objektno orijentiranog programiranja: – – – • apstrakcija podataka (data abstraction) enkapsulacija (encapsulation) skrivanje podataka (data hiding) polimorfizam (polymorphism) dinamičko povezivanje (dynamic binding) nasljeđivanje (inheritance) pravila “čistog” objektnog programiranja – – – sve je objekt program je hrpa objekata koji komuniciraju porukama svaki objekt ima svoje podatke sastavljene od drugih objekata svaki objekt ima tip svi objekti istog tipa mogu primati iste poruke
Programski jezik C++ • • • 1979. Bjarne Stroustrup u Bellu (sadašnji AT&T) - “C s klasama” 1983. C++, dodatna proširenja 1989. osnovan ANSI komitet za standardizaciju C++ na osnovu ARM-a (Annotated C++ Reference Manual) 1998. usvojen ISO standard još uvijek traje petogodišnji moratorij na promjene standarda • literatura: – Bjarne Stroustrup: The C++ Programming Language • usenet: – comp. lang. c++ – comp. std. c++ • C++ se može koristiti i samo kao bolji C
void • ne mora se pisati void kao argument funkcije C C++ int main(void){ return 0; } int main(){ }
komentari • jednolinijski komentari, počinju sa // C C++ /* returns square */ int f(int n){ /* int i, s=0; for(i=0; i<n; ++i) s += n; return s; */ return n*n; /* easier */ } // returns square int f(int n){ /* int s=0; for(int i=0; i<n; ++i) s += n; return s; */ return n*n; // much easier }
deklaracija varijabli • varijable se mogu deklarirati bilo gdje unutar funkcije C C++ int f(char* s, int n){ int i, c = 0; char* p = s; if(!p) return 0; for(i=0; i<n; ++i) if(p[i] == 'a') ++c; return c; } int f(char* s, int n){ char* p = s; if(!p) return 0; int c = 0; for(int i=0; i<n; ++i) if(p[i] == 'a') ++c; return c; }
bool • novi tip, može imati vrijednosti true i false C C++ int f(int n) { if(n < 0) return 1; return 0; } bool f(int n) { if(n < 0) return true; return false; } int main(void) { int x = -3; int b = f(x); if(b) x = abs(x) return 0; } int main() { int x = -3; bool b = f(x); if(b) x = abs(x); }
operator : : • omogućava pristup skrivenim globalnim varijablama C C++ int n; int main(void) { int n; for(n=0; n<10; ++n) n++; return 0; } int main() { for(int n=0; n<10; ++n) : : n++; }
pretpostavljene vrijednosti argumenata • zadnji argumenti funkcije mogu imati pretpostavljenu (default) vrijednost C C++ int f(int n, int b) { return n % b == 0; } bool f(int n, int b = 2) { return n % b == 0; } int main(void) { f(10, 5); f(9, 3); f(8, 2); return 0; } int main() { f(10, 5); f(9, 3); f(8); }
overloading funkcija • mogu postojati funkcije istog imena, različitih tipova argumenata C C++ #include <stdio. h> #include <cstdio> void print_int(int n) { printf(“%dn”, n); } void print_double(double n) { printf(“%lfn”, n); } void print(double n) { printf(“%lfn”, n); } int main(void) { print_int(10); print_double(3. 14); return 0; } int main() { print(10); print(3. 14); }
reference • T& je referenca na tip T C C++ void swap(int* a, int* b) { int t; if(!a || !b) return; t = *a; *a = *b; *b = t; } void swap(int& a, int& b) { int t = a; a = b; b = t; } int main(void) { int x=5, y=7; swap(&x, &y); return 0; } int main() { int x=5, y=7; swap(x, y); }
reference • referenca mora biti inicijalizirana double d = 1. 25; double& r = d; • referenca se ne može promijeniti, sve operacije na referenci se odnose na varijablu na koju referenca pokazuje int n = 1; int& r = n; r = 3; // mijenja vrijednost varijable n • preko konstantne reference ne može se mijenjati ni varijabla na koju referenca pokazuje int n = 1; const int& r = n; r = 3; // compile error
“Hello world!” #include <iostream> int main() { std: : cout << "Hello world!" << std: : endl; }
2. predavanje • objekti • klase
Objekti • objekt: podaci + operacije nad podacima
objekt // date. h struct int int }; void date { year; month; day; set_date(struct date*, int y, int m, int d); current_date(struct date*); print_date(const struct date*); next_date(struct date*);
C implementacija // date. c #include <stdio. h> #include "date. h" void set_date(struct date* dt, int y, int m, int d){ dt->year = y; dt->month = m; dt->day = d; } void current_date(struct date* dt){ dt->year = 2002; dt->month = 4; dt->day = 19; } void print_date(const struct date* dt){ printf("%02 d. %d. ", dt->day, dt->month, dt->year); } void next_date(struct date* dt){ // oversimplified dt->day++; if(dt->day > 30){ dt->day -= 30; dt->month++; } if(dt->month > 12){ dt->month -= 12; dt->year++; } }
funkcije članovi // date. h revisioned struct int int void void date { year; month; day; }; set(int y, int m, int d); get(int* y, int* m, int* d) const; current(); print() const; next();
C++ implementacija // date. cpp #include <iostream> #include "date. h" void date: : set(int y, int m, int d){ year = y; month = m; day = d; } void date: : get(int* py, int* pm, int* pd) const { *py = year; *pm = month; *pd = day; } void date: : print() const { std: : cout << day << '. ' << month << '. ' << year << '. '; } void date: : next(){ // oversimplified day++; if(day > 30){ day -= 30; month++; } if(month > 12){ month -= 12; year++; } }
upotreba objekata • upotreba strukture iz C i C++ C C++ int main(void) { struct date d; current_date(&d); next_date(&d); print_date(&d); return 0; } int main() { date d; d. current(); d. next(); d. print(); }
class // date. h rerevisioned struct date { private: int year; int month; int day; public: void set(int y, int m, int d); void get(int* y, int* m, int* d) const; void current(); void print() const; void next(); }; class date { int year; int month; int day; public: void set(int y, int m, int d); void get(int* y, int* m, int* d) const; void current(); void print() const; void next(); };
private, public • samo objekt smije pristupati svojim privatnim članovima class X { int n; bool test(); public: void work(X& other); }; int main(){ X x 1, x 2; int n = x 1. n; // error if(x 1. test()) // error return 1; x 1. work(x 2); // OK return 0; }
private, public • samo objekt i drugi objekti istog tipa smiju pristupati privatnim članovima objekta class X { int n; bool test(); public: void work(X& other); }; // all OK void X: : work(X& other) { if(!test()) return; if(other. test()) n = other. n; }
const funkcije • const funkcija član ne može mijenjati objekt class integer { int n; public: int read() const; void write(int i); }; // 1 st version, all OK integer: : read() const { return n; } void integer: : write(int i) { n = i; } // 2 nd version, illegal integer: : read() const { if(n < 0) n = 0; // error! return n; }
const funkcije • na konstantnim referencama se mogu zvati samo konstantne funkcije void f(integer& x, const integer& y) { int a = x. read(); // OK x. write(5); // OK int b = y. read(); // OK y. write(8); // error! };
inline funkcije • definicija funkcije člana se može pisati u tijelu klase class integer { int n; public: int read() const { return n; } void write(int i) { n = i; } }; class integer { int n; public: int read() const; void write(int i); }; inline integer: : read() const { return n; } inline void integer: : write(int i) { n = i; }
this • za svaku klasu T postoji skriveni const pointer: T* const this class integer { int n; public: int read() const { return n; } void write(int i) { n = i; } }; class integer { int n; public: int read() const { return this->n; } void write(int i) { this->n = i; } };
this • koristi se i kada iz funkcije člana treba poslati pointer ili referencu na objekt void print 1(date* dt){ int d, m, y; dt->get(&d, &m, &y); std: : cout << d << '. ' << m << '. ' << y << '. '; } void print 2(const date& dt){ int d, m, y; dt. get(&d, &m, &y); std: : cout << d << '. ' << m << '. ' << y << '. '; } void date: : print() const { : : print 1(this); : : print 2(*this); }
3. predavanje • stvaranje objekata • inicijalizacija objekata
dinamička alokacija u C++ • operatori new, delete i delete[] int* pn = new int; float* pf = new float; date* pd = new date; delete pn; delete pf; delete pd; pn = 0; pf = 0; pd = 0; int* pn = new int[15]; date* pd = new date[3]; delete[] pn; pn = 0; delete[] pd; pd = 0;
konstruktor, destruktor • posebne funkcije članovi, nemaju tip povratne vrijednosti • konstruktor: – za klasu T naziv funkcije je T() – poziva se pri stvaranju objekta • destruktor – za klasu T naziv funkcije je ~T() – poziva se pri uništenju objekta
konstruktor, destruktor class screen { int width, height; public: screen(); ~screen(); bool draw(const char* txt); }; screen: : screen() { width = 1024; height = 768; : : fill_screen(1); } screen: : ~screen(){ : : fill_screen(0); } int main(){ screen s; s. draw("Hello!"); } int main(){ screen* ps = new screen; ps->draw("Hello!"); delete ps; }
konstruktor sa argumentima class screen { int width, height; public: screen(int width, int height); ~screen(); bool draw(const char* txt); }; screen: : screen(int w, int h) { width = w; height = h; } screen: : screen(int width, int height) { this->width = width; this->height = height; } screen: : screen(int w, int h) : width(w), height(h) { } screen: : screen(int width, int height) : width(width), height(height) { }
konstruktor sa argumentima int main(){ screen s(1024, 768); s. draw("Hello again!"); } • i built-in tipovi imaju konstruktor int width(1024), height(768); double r(7. 24); bool f(false); int* pn = new int(42); unsigned long* pl = new unsigned long(666);
primjer: čitanje iz datoteke (C) int main(void){ char head[40], data[256]; FILE* fp = fopen("a. bmp", "r"); if(!fp) return 1; if(!fread(head, 40, fp)){ fclose(fp); return 2; } if(!fread(data, 256, fp)){ fclose(fp); return 3; } show_picture(data); fclose(fp); return 0; }
primjer: čitanje iz datoteke (C++) int main(){ char head[40], data[256]; file f("a. bmp"); if(!f. open()) return 1; if(!f. read(head, 40)) return 2; if(!f. read(data, 256)) return 3; : : show_picture(data); return 0; } class file { FILE* fp; public: file(const char*); ~file(); bool open(); int read(void*, int); }; file: : file(const char* name){ fp = : : fopen(name, "r"); } file: : ~file() { if(fp) : : fclose(fp); }. . .
default konstruktor • konstruktor koji se može pozvati bez argumenata • kada klasa nema konstruktora, generira se default konstruktor • kada pravimo polja objekata, objekt mora imati default konstruktor class point { int x, y; public: point() : x(0), y(0) {} }; int main(){ point pts[8]; return 0; } class point { int x, y; public: point(int x=0, int y=0) : x(x), y(y) {} }; int main(){ point* p = new point[8]; … delete[] p; }
copy konstruktor • konstruktor koji kao prvi argument prima referencu na drugi objekt istog tipa class point { int x, y; public: point(int x, int y); point(const point& other); }; point: : point(int ix, int iy) : x(ix), y(iy) {} point: : point(const point& other) : x(other. x), y(other. y) {} int main(){ point p 1(1, 0); point p 2(p 1); }
inicijalizacija • članovi se inicijaliziraju redoslijedom kojim su navedeni u tijelu klase class point { int x, y; public: point(int x, int y); }; point: : point(int ix, int iy) : y(iy), x(ix) { }
class objekti kao članovi • prvo se pozivaju konstruktori članova, a zatim konstruktor objekta class interval { point start; point end; public: interval(int x 1, int y 1, int x 2, int y 2); interval(const point& p 1, const point& p 2); }; interval: : interval(int x 1, int y 1, int x 2, int y 2) : start(x 1, y 1), end(x 2, y 2) { } interval: : interval (const point& p 1, const point& p 2) : start(p 1), end(p 2) { }
4. predavanje • nasljeđivanje
statički članovi (static members) • dijele ih svi objekti istog tipa • ekvivalent globalnih varijabli/funkcija u C-u // track. h class track { static int numobj; // static member public: track(); ~track(); static void print_name(); // static member function }; // track. cpp int track: : numobj = 0; track: : track() { ++numobj; } track: : ~track() { --numobj; } void track: : print_name(){ std: : cout << "track"; }
operator: : • može se pisati pristupu članovima objekta • obavezan pristupu statičkim članovima class point { int x, y; public: double length(); }; double point: : length(){ return sqrt(x*x + y*y); } int main(){ point p; double n 1 = p. point: : length(); // isto kao p. length() point* pp = &p; double n 2 = pp->point: : length(); // isto kao pp->length() }
namespace • grupiranje globalnih simbola (funkcija, tipova i varijabli) // oop. h namespace oop { class point { public: int x, y; }; double distance(const point& p 1, const point& p 2); } // oop. cpp double oop: : distance(const point& p 1, const point& p 2){ int a(p 1. x-p 2. x), b(p 1. y-p 2. y); return sqrt(a*a + b*b); }
namespace • korištenje simbola - operator: : ili 'using namespace' int main(){ oop: : point p 1, p 2; double len = oop: : distance(p 1, p 2); } int main(){ using namespace oop; point p 1, p 2; double len = distance(p 1, p 2); }
std namespace • std: : cin, std: : cout, std: : string #include <iostream> #include <string> int main(){ using namespace std; string s; cin >> s; cout << s << " ima " << s. length() << " znakova. " << endl; char c; cin >> c; int pos = s. find(c); if(pos >= 0) cout << "slovo " << c << " se prvi puta pojavljuje na " << pos << ". mjestu" << endl; }
nasljeđivanje class person { string name; date birth; string address; . . . }; class student { string name; date birth; string address; short school. Year; float average. Mark; . . . }; class student { person p; short school. Year; float average. Mark; . . . }; class student : person { short school. Year; float average. Mark; . . . };
bazna klasa, izvedena klasa • izvođenje ili nasljeđivanje person student • - bazna klasa (base class, superclass) - izvedena klasa (derived class, subclass) built-in tip ne može biti bazna klasa
poziv funkcija bazne klase • funkcije članovi bazne klase se pozivaju preko operatora: : void person: : print(){ std: : cout << name << birth << address; } void student: : report(){ person: : print(); std: : cout << school. Year << average. Mark; }
protected • objekti izvedenih klasa smiju pristupati protected članovima class person { protected: string name; date birth; string address; }; class student : public person { short school. Year; float average. Mark; public: bool check_status(){ return school. Year < 2 && birth. year < 1980; } };
upcasting • objekt izvedene klase se može tretirati kao objekt bazne klase preko reference ili pointera int main(){ person p; student s; person* p 1 = &p; // ili student* s 1 = &s; // ili person& p 1 = p; student& s 1 = s; person* p 2 = &s; // OK, svaki 'student' je 'person' person& p 3 = s; // OK student* s 2 = &p; // error! -> p nije student& s 3 = p; // error! student* s 4 = p 2; // error! (iako p 2 pokazuje na studenta) }
private, protected izvođenje • privatno izvođenje skriva članove bazne klase • protected izvođenje skriva članove bazne klase, ali ne od izvedenih klasa class A { public: int i; }; class B : protected A {}; class B : private A {}; class C : public B { public: void f() { i = 0; } }; int main(){ B b; b. i = 1; // error! } int main(){ C c; c. i = 1; // error! }
inicijalizacija 1. konstruktor bazne klase 2. konstruktori članova 3. konstruktor objekta person: : person(const string& n, int y, int m, int d, const string& a) : name(n), date(y, m, d), address(a) {} student: : student(const string& n, int y, int m, int d, const string& a, short sy) : person(n, y, m, d, a), school. Year(sy) {} • destruktori se pozivaju obrnutim redom
skrivanje funkcija • istoimena funkcija u izvedenoj klasi skriva funkciju u baznoj klasi class person { public: void print(); }; class student : public person { public: void print(); }; int main(){ student s; s. print(); s. person: : print(); }
virtualne funkcije • virtualna funkcija se može pregaziti (override) u izvedenoj klasi • poziv funkcije je poziv u zadnjoj izvedenoj klasi koja je pregazila funkciju (osim u konstruktoru i destruktoru) class instrument { public: virtual void play(){} }; int main (){ instrument i; piano p; drum d; class drum : public instrument { public: void play(){ cout << "dum, dum"; } }; class piano : public instrument { public: void play(){ cout << "pling"; } }; instrument* pi = &i; pi->play(); // - } pi = &p; pi->play(); // pling pi = &d; pi->play(); // dum, dum
5. predavanje • virtualne funkcije • višestruko nasljeđivanje
mehanizam virtualnih funkcija • veličina objekta naraste za 4 byte-a čim ima barem jednu virtualnu funkciju • pointer (vptr) na tablicu virtualnih funkcija (vtable) class virt 0 { int n; public: void f(){} }; int main (){ using namespace std; virt 0 v 0; cout << sizeof(v 0); // 4 class virt 1 { int n; public: virtual void f(){} }; class virt 2 { int n; public: virtual void f(){} virtual void g(){} }; virt 1 v 1; cout << sizeof(v 1); // 8 virt 2 v 2; cout << sizeof(v 2); // 8 }
mehanizam virtualnih funkcija class virt 2 { int n; public: virtual void f(){} virtual void g(){} }; int main(){ virt 2 a, b; } a vptr n vtable b vptr n • tablica virtualnih funkcija vrijedi za sve objekte klase • tablica virtualnih funkcija se inicijalizira u konstruktoru • sve funkcije osim konstruktora mogu biti virtualne f() g()
virtualni destruktor • obavezan ako će se uništavanje objekata vršiti preko pointera na baznu klasu int main (){ instrument* pins[] = { new instrument, new piano, new drum, }; for(int i=0; i<3; ++i) pins[i]->play(); for(int j=0; j<3; ++j) delete pins[j]; } class instrument { public: virtual void play() {} virtual ~instrument() {} };
čisto virtualne funkcije (pure virtual functions) • čisto virtualne funkcije se označavaju sa =0 u deklaraciji class instrument { public: virtual void play() = 0; }; • čisto virtualne funkcije mogu imati definiciju class instrument { public: virtual void play() = 0; }; void instrument: : play(){ cout << "moj zvuk: "; } class piano : public instrument { public: virtual void play(); }; void piano: : play(){ instrument: : play(); cout << "pling" << endl; }
apstraktna klasa (abstract class) • klasa sa bar jednom čisto virtualnom funkcijom • nije moguće napraviti objekt apstraktne klase, koriste se pointeri i reference na njih • čisto virtualni destruktor mora imati definiciju class instrument { public: virtual void play() = 0; virtual void tune() = 0; virtual ~instrument() = 0; }; instrument: : ~instrument() {} apstraktna klasa samo sa čisto virtualnim funkcijama = čisto apstraktna (pure abstract) klasa = interface
višestruko nasljeđivanje (multiple inheritance) • izvedena klasa ima više baznih klasa class phone { public: virtual void call(int); virtual void hangup(); virtual void redial(); }; class device { public: virtual void on(); virtual void off(); }; class moveable { protected: point GPScoord; public: virtual point coords(); }; class mobile_phone : public phone, public device, public moveable { }; int main(){ mobile_phone mp; mp. on(); mp. call(238729); point p = mp. coords(); }
inicijalizacija 1. konstruktori baznih klasa, redom kojim su bazne klase navedene 2. konstruktori članova 3. konstruktor objekta A B class A { public: A(){ cout << "A"; } ~A(){ cout << "~A"; } }; class B { public: B(){ cout << "B"; } ~B(){ cout << "~B"; } }; class M { public: M(){ cout << "M"; } ~M(){ cout << "~M"; } }; C class C : public A, public B { M m; public: C(){ cout << "C"; } ~C(){ cout << "~C"; } }; int main(){ C c; cout << endl; } ABMC ~C~M~B~A
višestruko pojavljivanje baze • ista bazna klasa se pojavljuje više puta u hijerarhiji • više instanci bazne klase je sadržano u instanci izvedene klase class auto { protected: engine e; }; class auto 1 auto 2 auto 3 auto 4 : : public class perfect_auto : public auto 1, public auto 2, public auto 4 { }; /* has 3 engines ! */ auto {. . . }; // // ima ima servo klimu ABS zr. jastuke A A B B C
virtualno izvođenje (virtual inheritance) • omogućava da se ista bazna klasa pojavi više puta u hijerarhiji, a da je u izvedenoj klasi sadržana samo jednom • konstruktor virtualne baze se mora pozvati u najizvedenijoj klasi class person { protected: string name; public: person(const string& name) : name(name) {} }; class student : virtual public person {. . . }; class employee : virtual public person {. . . }; class emp_student : public student, public employee { public: emp_student(const string& name) : person(name) {} }; person student employee emp_student
6. predavanje • operatori
friend • klasa može omogućiti nekoj funkciji pristup njenim private članovima • friend se može napisati bilo gdje u tijelu klase class Number { int n; public: Number(int n) : n(n) {} friend Number sum(const Number& a, const Number& b); }; Number sum(const Number& a, const Number& b){ return Number(a. n + b. n); } int main(){ Number n 1(3), n 2(4); Number n 3 = sum(n 1, n 2); }
friend • friend se može pisati i za funkcije članove class A { int n; friend void B: : f(A&); }; class B { void f(A& a) { ++a. n; } }; • pokrata za sve funkcije članove neke klase: friend class A { int n; friend class B; }; class B { void f(A& a) { ++a. n; } void g(A& a) { --a. n; } }; • friend nije tranzitivan i ne nasljeđuje se izvođenjem class A { friend class B; }; class B { friend class C; }; class C { }; // C nije friend od A class D : public B {}; // D nije friend od A
operatori • funkcija čije je ime "operator@" class Number { int n; public: Number(int n) : n(n) {} friend Number operator+(const Number& a, const Number& b); }; Number operator+(const Number& a, const Number& b){ return Number(a. n + b. n); } int main(){ Number n 1(3), n 2(4); Number n 3 = n 1 + n 2; }
operatori • može se pisati i kao funkcija član class Number { int n; public: Number(int n) : n(n) {} Number operator+(const Number& b); }; Number: : operator+(const Number& other){ return Number(n + other. n); } int main(){ Number n 1(3), n 2(4); Number n 3 = n 1 + n 2; }
preopterećenje operatora (operator overloading) • operatori koji se mogu preopteretiti + * ! = < > &= |= << >> && • važna pravila: || ++ -1. ne mogu se definirati novi delete / % ^ & | ~ += -= *= /= %= ^= >>= <<= == != <= >= -> [] () new ->* , operatori 2. ne može se mijenjati prioritet operatora 3. ne mogu se mijenjati sintaksna pravila • binarni operatori: 1. globalna funkcija sa 2 argumenta (barem jedan od argumenata mora biti userdefined tip!) ili 2. funkcija član sa jednim argumentom • unarni operatori: 1. globalna funkcija sa 1 argumentom ili 2. funkcija član bez argumenata • operatori koji se moraju pisati kao članovi: = [] () ->
primjeri preopterećenja operatora class Byte { char n; public: Byte(unsigned char n=0) : n(n) {} Byte operator+() const { return Byte(n); } Byte operator-() const { return Byte(-n); } Byte& operator+=(const Byte& other) { n += other. n; return *this; } friend Byte operator+(const Byte& b 1, const Byte& b 2); friend Byte operator-(const Byte& b 1, const Byte& b 2); }; Byte operator+(const Byte& b 1, const Byte& b 2){ return Byte(b 1. n + b 2. n); } Byte operator-(const Byte& b 1, const Byte& b 2){ return Byte(b 1. n - b 2. n); }
inkrement i dekrement • da bi se razlikovali prefiksna i postfiksna verzija uvodi se dodatni argument class Number { int n; public: Number(int n) : n(n) {} Number operator++() { return Number(++n); } Number operator++(int) { return Number(n++); } friend Number operator--(Number&); friend Number operator--(Number&, int); }; Number operator--(Number& a) { return --a. n; } Number operator--(Number& a, int) { return a. n--; }
operatori <<, >> • za unos i ispis objekata klase class point { int x, y; friend istream& operator>>(istream&, point&); friend ostream& operator<< (ostream& os, const point& a); }; istream& operator>>(istream& is, point& p){ is >> p. x >> p. y; return is; } ostream& operator<< (ostream& os, const point& p){ os << '(' << p. x << ', ' << p. y << ')'; return os; } int main(){ point p; cin >> p; cout << p; }
operatori implicitne pretvorbe (conversion operators) • funkcija čije je ime "operator tip", nema tip povratne vrijednosti class Number { int n; public: Number(int n=0) : n(n) {} operator int() { return n; } }; int main(){ Number a; int b = 2 + a; // 2 + a. operator int() }
operator= • ako nije napisan, generira ga compiler (slično ako copy konstruktor) class Number{ int n; public: Number(int n=0) : n(n) {} Number(const Number& other); Number& operator=(const Number& other); }; int main(){ Number a, b; Number c = a; // poziva copy konstruktor! b = a; // poziva operator= } Number& Number: : operator=(const Number& other) { n = other. n; return *this; } Number: : Number(const Number& other) { *this = other; } // koristi op=
privremeni objekti (temporary objects) • nastaju prvi svakom slanju objekata u funkciju i vraćanju objekata iz funkcije class A { int n; public: A(int n) : n(n) { cout << "A"; } A(const A& other) { n = other. n; cout << "A"; } ~A() {cout << "~A"; } }; A f(A a){ return a; // poziva copy konstruktor! (return type = objekt) } int main(){ A a(3); f(a); // poziva copy konstruktor! (argument funkcije = objekt) }
operator[ ] • kao indeks (argument) može primiti bilo koji tip • može služiti za stvaranje asocijativnih polja int main(){ assoc a; string s; while(true) { cin >> s; if(s. length()<=1) break; a[s]++; } cout << a; } struct snpair { string s; int count; snpair() : count(0) {} };
operator[ ] class assoc { snpair arr[256]; int last; public: assoc() : last(0) {} int& operator[](const string& s); friend ostream& operator<<(ostream& os, const assoc&); }; int& assoc: : operator[](const string& s){ for(int i=0; i<last; ++i) if(arr[i]. s == s) return arr[i]. count; arr[last]. s = s; return arr[last++]. count; } ostream& operator<< (ostream& os, const assoc& a){ for(int i=0; i<256; ++i) if(a. arr[i]. count) os << a. arr[i]. s << " : " << a. arr[i]. count << endl; return os; }
operator() • ako funkcija ima statičke parametre, može se napisati kao klasa (functional object) class translate { int cx, cy; public: translate(int cx, int cy) : cx(cx), cy(cy) {} point& operator()(point& p){ p. x += cx; p. y += cy; return p; } }; // translate all points for vector (3, 4) int main(){ translate t(3, 4); point p; while(1){ cin >> p; t(p); cout << p; } }
7. predavanje • template
template klase class Int. Array { int* p; public: Int. Array(int size) : p(new int[size]){ } ~Int. Array() { delete[] p; } int& operator[](int i) { return p[i]; } }; class Double. Array { double* p; public: Double. Array(int size) : p(new double[size]){ } ~Double. Array() { delete[] p; } double& operator[](int i) { return p[i]; } }; int main(){ Int. Array ar 1(10); Double. Array ar 2(20); }
template klase • klasa parametrizirana tipom (ili tipovima) template<typename T> class Array { T* p; public: Array(int size) : p(new T[size]){ } ~Array() { delete[] p; } T& operator[](int i) { return p[i]; } }; int main(){ Array<double> ar 1(10); Array<int> ar 2(10); }
template klase - definicija članova • non-inline definiciju funkcija članova prethodi template<typename T> class Array { T* p; public: Array(int size); ~Array(); T& operator[](int i); }; template<typename T> Array<T>: : Array(int size) : p(new T[size]){ } //template<typename T> Array<T>: : Array<T>(int size) : p(new T[size]){ } template<typename T> Array<T>: : ~Array() { delete[] p; } template<typename T> T& Array<T>: : operator[](int i) { return p[i]; }
template funkcije • funkcija parametrizirana tipom (ili tipovima) void swap(int& a, int& b){ int t = a; a = b; b = t; } void swap(double& a, double& b){ double t = a; a = b; b = t; } void swap(Number& a, Number& b){ Number t = a; a = b; b = t; } template<typename T> void swap(T& a, T& b){ T t = a; a = b; b = t; } int main(){ int n 1, n 2; swap(n 1, n 2); swap<int>(n 1, n 2); }
template funkcije članovi (template member functions) • funkcija član može biti template u template klasi ili običnoj klasi • ne može biti virtualna class A { public: template<typename T> T fun(T a) { return a*a; } template<typename T> class A { T m; public: A(T t) : m(t) {} }; int main(){ A a; int n = a. fun(5); } template<typename ARG> void f(ARG a) { cout << m*a; } }; int main(){ A<unsigned char> a(2); a. f(5); }
generičko programiranje • mogući parametri template-a: 1. konstantni izraz 2. objekt ili funkcija 3. pointer na člana • template<typename T> vrijedi do kraja klase/funkcije • template može imati više parametara, zadnji mogu imati default vrijednosti i ne moraju se navesti (kao kod default vrijednosti argumenata funkcija) template<typename T, int SIZE=64> class array {. . . }; int main(){ array<int> ar 1; array<unsigned long, 32> ar 2; } • template parametri funkcija se ne moraju navesti, osim za return type template<typename T> T fun(int a){ return a*a; } int main(){ fun<double>(3); }
instanciranje (template instantiation) • compiler obavlja male sintaksne provjere odmah, kod za template klasu/funkciju nastaje tek pri prvom spominjaju - instanciranje template <typename T> class Array { T* p; public: Array(int size); ~Array(); T& operator[](int i); }; template class Array<bool>; // eksplicitno! typedef Array<int> Int. Array; int main(){ Array<float*> ar 1(24); // implicitno Int. Array ar 2(32); // implicitno }
primjer • funkcija sort template<typename T> void sort(vector<T>& v){ const size_t n = v. size(); for(int gap=n/2; 0<gap; gap/=2) for(int i=gap; i<n; ++i) for(int j=i-gap; 0<=j; j-=gap) if(v[j+gap] < v[j]) swap(v[j], v[j+gap]); } • zahtjeva operator< na tipu T
primjer • funkcija sort template<typename T> bool less(T a, T b) { return a < b; } template<typename T> void sort(vector<T>& v){ const size_t n = v. size(); for(int gap=n/2; 0<gap; gap/=2) for(int i=gap; i<n; ++i) for(int j=i-gap; 0<=j; j-=gap) if(less(v[j+gap], v[j])) swap(v[j], v[j+gap]); } // da bi sort stringova radio pravilno template<> bool less(const char* a, const char* b) { return strcmp(a, b) < 0; }
specijalizacija (specialization) • alternativna implementacija za pojedini tip template<> class Array<bool> { char* p; public: Array(int size) : p(new char[(size-1)/8+1]){ } ~Array() { delete[] p; } bool operator[](int i) { return p[i/8] & (1<<(i%8)); } }; int main(){ Array<int> ar 1(30); // koristi originalni template Array<bool> ar 2(64); // koristi specijalizirani template }
parcijalna specijalizacija (partial specialization) • specijalizacija, ali ne po svim parametrima template <typename T, typename U, typename V> class A {}; template <typename T, typename V> class A<T, int, V> {}; template <typename V> class A<double, long, V> {}; template <typename U> class A<int, U, int*> {};
eksplicitna konverzija (cast) u C++ • static_cast - za uobičajene (C-ovske) pretvorbe i upcasting int n=5; double d = static_cast<double>(n); Derived pd; Base* pb = static_cast<Base*>(&pd); • dynamic_cast - za downcast, vraća NULL ako ne uspije • klasa mora imati virtualne funkcije, potreban RTTI (run-time type information) Base b; Derived d; Base* pb = static_cast<Base*>(&d); Derived* pd = dynamic_cast<Derived*>(pb); Derived* pd 2 = dynamic_cast<Derived*>(&b); // pd 2 je NULL!
eksplicitna konverzija (cast) u C++ • const_cast - za skidanje i stavljanje konstantnosti void f(const A& a){ A& ra = const_cast<A&>(a); ra. change(); A* pa = const_cast<A*>(&a); pa->change(); } • reinterpret_cast - za kompletnu promjenu tipa, opasno void f(long lp){ A* pa = reinterpret_cast<A*>(lp); } int main(){ A a; long xxx = reinterpret_cast<long>(&a); f(xxx); f(1); // argh!! }
8. predavanje • iznimke
iznimka (exception) • kako signalizirati pogrešku iz operatora[] ili konstruktora? • funkcija baca (throw) iznimku • funkcija pozivatelj hvata (catch) iznimku class X {}; double square(double v) { if(v < 0) throw X(); . . . return s; } int main(){ double d; cin >> d; try { double sq = square(d); cout << "square(" << d << ") = " << sq << endl; }catch(X){ cout << "invalid input" << endl; } }
iznimka je objekt • objekt koji se baca može nositi poruku o greški class X { public: string err; X(const char* s) : err(s) {} }; double square(double v) { if(v < 0) throw X("argument manji od nule"); . . . } int main(){ double d; cin >> d; try { double sq = square(d); cout << "square(" << d << ") = " << sq << endl; }catch(X x){ cout << "error: " << x. err << endl; } }
višestruki catch blokovi • catch blokovi se provjeravaju redom koji su napisani class XNeg {}; class XSmall {}; double square(double v) { if(v < 0) throw XNeg(); if(v < 0. 01) throw XSmall(); . . . } int main(){ double d; cin >> d; try { double sq = square(d); cout << "square(" << d << ") = " << sq << endl; }catch(XNeg x){ cout << "negativan broj" << endl; }catch(XSmall x){ cout << "premali broj" << endl; } }
hijerarhija iznimki • obično se koristi hijerarhija klasa iznimki class Math. Err { public: virtual void Show. Error() = 0; }; class Zero. Divide : public Math. Err { public: void Show. Error() { cout << "dijeljenje s nulom"; } }; class Overflow : public Math. Err { public: void Show. Error() { cout << "overflow"; } }; class Underflow : public Math. Err { public: void Show. Error() { cout << "underflow"; } };
catch by reference • catch bazne klase, obavezno reference da bi radile virtualne funkcije int main(){ double d; cin >> d; try { double sq = square(d); cout << "square(" << d << ") = " << sq << endl; }catch(Math. Err& x){ x. Show. Error(); }catch(. . . ){ cout << "nepoznata iznimka" << endl; } } • catch(. . . ) hvata sve ostale iznimke • u slučaju da nitko ne uhvati iznimku zove se std: : uncaught_exception() • pri bacanju iznimke zovu se destruktori svih objekata čija je inicijalizacija završena • ne smije se throw-ati iz destruktora
deklaracija funkcija • za svaku funkciju može se specificirati koje iznimke baca double f(double v) throw (X 1, X 2); // baca samo X 1, X 2 i izvedene void f(int n) throw(); // ne baca nista! int g(); // moze baciti bilo sto! class Int. Array { public: . . . int& operator[](int index) throw (Range. Error); }; • ako funkcija baci neku drugu iznimku, zove se std: : unexpected() • ako virtualna funkcija specificira set iznimki, može se pregaziti sa funkcijom koja baca isti set ili podskup
9. predavanje • standardna biblioteka
standardna biblioteka (standard library) • sve je unutar std namespace-a, u setu header-a • #include <header> je obavezno zbog mogućih optimizacija compilera • uglavnom template-i, dozvoljeno je izvođenje i specijalizacija • podjela prema tipu (u zagradi su headeri): – – – – – containeri (vector, list, deque, queue, stack, map, set, bitset) opći servisi (utility, functional, memory, ctime) iteratori (iterator) algoritmi (algorithm, . . . ) dijagnostika (exception, stdexcept, cassert, . . . ) stringovi (string, cctype, cwtpe, . . . ) ulaz/izlaz (iosfwd, iostream, ios, streambuf, istream, ostream, iomanip, fstream, . . . ) lokalizacija (locale, . . . ) podrška jeziku (limits, new, typeinfo, . . . ) numerika (complex, valarray, numeric, . . . ) • Standard Template Library (STL) - containeri, algoritmi
standardni containeri • osobine: – – – • type safe homogeni (svi elementi istog tipa) neintruzivni (elementi ne moraju biti izvedeni iz bazne klase) alokator kao dodatni template argument (za rad s memorijom) imaju "jednak" interface (standardni nazivi i semantika) vrste: – sekvencijalni - vector, deque, list – asocijativni (pristup po ključu) - map, set, multimap, multiset – posebni • adapteri (sequence adapters) - stack, queue, priority_queue • nepravi containeri - bitset, valarray, string • sadržavaju (preko typedef-a): – value_type, allocator_type, size_type, difference_type – iterator, const_iterator, reverse_iterator, const_reverse_iterator – pointer, const_pointer, reference, const_reference • osnovne funkcije za početak i kraj sekvence: – begin(), end(), rbegin(), rend() - vraćaju iteratore
iterator • za pristup standardnim containerima koriste se iteratori • omogućavaju pristup različitim containerima na isti način template <typename C> C: : value_type sum(const C& c){ C: : value_type s = 0; for(C: : const_iterator it = c. begin(); it != c. end(); ++it) s += *it; return s; }
vector • funkcije za pristup elementima (vraćaju referencu): – operator[](size_type index), at(size_type index) – front(), back() • at() provjerava indeks i baca out_of_range iznimku • podržava funkcionalnost stack-a: – push_back(const value_type& elem), pop_back() int main(){ vector<int> v; v. push_back(2); v. push_back(7); typedef vector<int>: : const_iterator Iter; for(Iter it = v. begin(); it != v. end(); ++it) cout << *it << endl; // isto kao: for(int i=0; i<v. size(); ++i) cout << v[i] << endl; }
vector • za dodavanje u sredinu (vraća iterator): – insert(iterator pos, const value_type& elem) • za brisanje iz sredine (vraća iterator): – erase(iterator pos) • skupe operacije (zahtjevaju kopiranje preostalih elemenata) • za brisanje svih elemenata: – clear() • funkcije vezane za veličinu: – size_type size() - broj elemenata u vectoru – bool empty() – void resize(size_type n, value_type val = value_type()) dodane elemente inicijalizira sa val – void reserve(size_type n) - alocira memoriju za n elemenata – size_type capacity() - za koliko elemenata je alocirana memorija
ostali sekvencijalni containeri • deque (double-ended queue) • kao vector, dodatno podržava : – push_front(const value_type& elem), pop_front() • list • ima front operacije, nema direktnog pristupa elementu (operator[ ]) • koristi dvosmjerne iteratore pa je obično dvostruko povezana lista • dodatna funkcionalnost: – splice(iterator pos, list& x) - prebaci sve element iz x ispred pos – sort() - sortiraj listu (koristi operator<) – merge(list& x) - spoji listu x (x ostaje prazan)
adapteri • koriste sekvencijalne containere, ali imaju svoj interface • ne koriste iteratore • stack • po defaultu koristi deque (kao protected member), ali ima svoje funkcije : – push(), pop(), top() • queue • po defaultu koristi deque (kao protected member), ali ima svoje funkcije : – push(), pop(), front(), back() • priority_queue • slično kao queue (po defaultu koristi vector), ali elementi imaju prioritet kojim dolaze na top() - po defaultu operator<
map • drži parove ključ-vrijednost (value_type je std: : pair<>), ključ mora biti jedinstven • operator[](const key_type& key) - dodaje novi element ako ne nađe postojeći • sortira elemente (po defaultu operator<) da bi iteriranje išlo redom int main(){ map<string, int> m; while(true) { string s; cin >> s; if(s. length()<=1) break; m[s]++; } typedef map<string, int>: : const_iterator Iter; for(Iter it = m. begin(); it != m. end(); ++it) cout << it->first << " : " << it->second << endl; } • dodavanje preko insert(): string name("ivan"); int mark=5; m. insert(make_pair(name, mark));
ostali asocijativni containeri • multimap • dozvoljava duplicirane ključeve • zbog toga, dodatno podržava: – lower_bound(), upper_bound(), equal_range() • set • drži samo ključeve, moraju biti jedinstveni • multiset • dozvoljava duplicirane ključeve
10. predavanje • standardna biblioteka - algoritmi
copy • kopiranje preko iteratora vector<int> v(10); deque<double> d(v. size()); copy(v. begin(), v. end(), d. begin()); • definicija funkcije template<class In. It, class Out. It> inline Out. It copy(In. It p, In. It e, Out. It x){ for (; p != e; ++p, ++x) *x = *p; return x; }
copy - posebne primjene • standarni iterator za ispis - ostream_iterator<int> os(cout, "n"); // n - separator vector<int> v(10); copy(v. begin(), v. end(), os); • standarni iterator za unos - istream_iterator<int> in(cin), ends; vector<int> v; copy(in, ends, back_inserter(v)); • ends - iterator sa default konstruktorom služi kao oznaka kraja • back_inserter() - funkcija koja vraća back_insert_iterator • back_insert_iterator - iterator koji radi push_back() u operator=()
fill, generate - punjenje containera • punjenje konstantom typedef vector<int>: : iterator Iter; vector<int> v(10); for(Iter it = v. begin(); it != v. end(); ++it) *it = -1; // isto kao: fill(v. begin(), v. end(), -1); • punjenje funkcijom typedef vector<int>: : iterator Iter; vector<int> v(10); for(Iter it = v. begin(); it != v. end(); ++it) *it = rand(); // isto kao: generate(v. begin(), v. end(), rand);
funkcijski objekt • klasa koja ima operator() • za generate() - operator() nema argumenata, vraća vrijednost class incr { int n; public: incr(int initval=0) : n(initval) {} int operator()() { return n++; } }; int main(){ vector<int> v(10); generate(v. begin(), v. end(), incr()); }
for_each, transform • operator() prima element class avg { int n; double sum; public: avg() : n(0), sum(0) {} void operator()(int elem) { ++n; sum += elem; } operator double() { return sum/n; } }; int main(){ istream_iterator<int> in(cin), ends; vector<int> v; copy(in, ends, back_inserter(v)); cout << "prosjek: " << for_each(v. begin(), v. end(), avg()); } • vraćaju funkciju (funkcijski objekt) • za transform() - operator() vraća novu vrijednost
count, find, replace, remove • broj elemenata jednak nekoj vrijednosti cout << "broj nula je: " << count(v. begin(), v. end(), 0); • prvi element koji je jednak nekoj vrijednosti Iter it = find(v. begin(), v. end(), 42); if(it != v. end()). . . // nasao • u stringu zamijeni sva slova 'a' sa 'e' string s; cin >> s; replace(s. begin(), s. end(), 'a', 'e'); • izbaci sva slova 'a' string s; cin >> s; s. erase(remove(s. begin(), s. end(), 'a'), s. end()); • remove() ne briše nego vraća iterator - obavezan poziv erase()
predikat - count_if, find_if, . . . • predikat - operator() prima element, vraća bool • primjer: broj elemenata u vektoru manjih od 100 class less { int limit; public: less(int limit) : limit(limit) {} bool operator()(int n) { return n < limit; } }; . . . int cnt = count_if(v. begin(), v. end(), less(100)); • koristeći standardni objekt count_if(v. begin(), v. end(), bind 2 nd(less<int>(), 100));
sort • koristi operator< ili binarni predikat • binarni predikat - operator() prima dva elementa, vraća bool struct student { string ime, prezime; int ocjena; }; struct comp { bool operator() (const student& s 1, const student& s 2){ return s 1. ocjena < s 2. ocjena; } }; int main(){ vector<student> v; //. . . napuni container sort(v. begin(), v. end(), comp()); }
podjela algoritama (potpuni spisak) • • nemodificirajući na sekvenci: for_each, find_if, find_first_of, adjacent_find, count_if, mismatch, equal, search, find_end, search_n modificirajući na sekvenci: transform, copy_backward, swap, iter_swap, swap_ranges, replace_if, replace_copy_if, fill_n, generate_n, remove_if, remove_copy_if, unique_copy, reverse_copy, rotate_copy sortirane sekvence: sort, stable_sort, partial_sort_copy, nth_element, lower_bound, upper_bound, equal_range, binary_search, merge, inplace_merge, partition, stable_partition setovi: includes, set_union, set_intersection, set_difference, set_symmetric_difference operacije na heap-u: make_heap, push_heap, pop_heap, sort_heap min/max: min, max, min_element, max_element, lexicographical_compare permutacije: next_permutation, prev_permutation numerički: acccumulate, inner_product, partial_sum, adjacent_difference
- Slides: 122