Transcript Izuzeci

Objektno programiranje (C++)
Pravila ocjenjivanja
1.
Održat će se dva kolokvija. Svaki nosi po 30 bodova. Za
prolaz treba imati 25 bodova.
2.
Treba predati rješenja dviju domaćih zadaća. Svaka
zadaća nosi 20 bodova. Minimum za prolaz je 15
bodova.
3.
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ée Lajoie, Barbara E. Moo: C++
Primer, Fourth Edition, Addison Wesley Professional, 2005.
• Julijan Šribar, Boris Motik: Demistificirani C++, 2. izdanje,
Element, Zagreb, 2006.
• Bruce Eckel: Thinking in C++, 1. i 2. dio (besplatna eknjiga) :
http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.html
Literatura
Napredna:
• Bjarne Stroustrup: The C++ Programming Language,
Addison Wesley, 2000.
• Scott Meyers: Effective C++, Third Edition, Addison-Wesley,
2006.
• Scott Meyers: More Effective C++, Addison-Wesley, 1996.
• Herb Sutter: Exceptional C++, Addison Wesley, 2000.
• Stephen C. Dewhurst: C++ Common Knowledge, Addison
Wesley, 2005.
• Erich Gamma, et. all: Design Patterns: Elements of Reusable
Object-Oriented Software (Addison-Wesley Professional
Computing Series).
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. compound type), 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.
const char &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;
const int &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;
int tmp = x;
const int &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
void f(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.
void swap(int &x, int &y) {
int tmp = 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
void print(std::string& text) {
std::cout << text << std::endl;
}
std::string a("...");
print(a);
print("...");
// o.k.
// 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*.
void print( int * ){ …..}
void print( int[ ] ){ …. }
void print( 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:
int count(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(const std::string& s1, const std::string& s2){
std::string tmp = s1+s2;
return tmp;
// 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)(const string &, 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.
typedef int (*fptrType)(const string &, int );
typedef int fType(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(const string &s, int a){
cout << s;
return ++a;
}
fptrType fptr=&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:
typedef int (*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
fType ff( 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, signed
char, unsigned char, short, unsigned short) 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 operatora i sizeof operatora 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) { // ... } // ....
const int 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 float sinf(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;
}
typedef double R;
void f(R y) {
cout << "f(R)"<<endl;
}
// Greška, redefinicija
Preopterećenje funkcija
Defaultni argument ne smanjuje broj argumenata funkcije pa ne može
biti osnova za razlikovanje:
void f(int x, int y) {
cout << "f(int,int)"<<endl;
}
void f(int x, int y=0) {
cout << "f(int,int=0)"<<endl;
}
// Greška, redefinicija
Preopterećenje funkcija
Kada je const irelevantan, ne može služiti za razlikovanje tipova. U ovom
slučaju const je irelevantan jer funkcija dobiva kopiju argumenta i nikako
ne može promijeniti original.
void f(int x) {
cout << "f(int)"<<endl;
}
void f(const int x) {
cout << "f(const int)"<<endl;
}
// Greška, redefinicija
Konstantni i nekonstantni argument mogu služiti za razlikovanje samo
kod referenci i pokazivača.
Preopterećenje funkcija
Prvi korak: nalaženje kandidata. Prevoditelj nalazi sve funkcije
danog imena koje su vidljive na mjestu poziva.
Drugi korak: selektiranje među kandidatima onih funkcija koje
imaju korektan broj parametara i svi parametri se mogu
konvertirati u odgovarajući tip. Time dolazimo do skupa dobrih
kandidata (engl. viable functions), tj. onih koji mogu biti pozvani
sa zadanim parametrima. Prilikom uspoređivanja broja
argumenata uzimaju se u obzir i eventualni defaultni argumenti.
Ako je skup dobrih kandidata prazan imamo grešku pri
kompilaciji.
Preopterećenje funkcija
Treći korak: određivanje najboljeg kandidata. Princip nalaženja
je sljedeći: kandidat kod kojeg imamo egzaktno podudaranje
argumenta je bolji od kandidata kod kojeg moramo vršiti
konverziju argumenta. Kako ima više vrsta konverzija one se
rangiraju prema kvaliteti u svrhu definiranja uređaja među
kandidatima.
Za funkcije s više argumenata najbolji kandidat je onaj koji nije
lošiji od drugih niti po jednom argumentu, a u jednom je bolji
od svih. Ako na kraju postupka ima više najboljih kandidata,
poziv je dvosmislen (engl. ambiguous) i prevoditelj javlja grešku.
Preopterećenje funkcija
Rang lista konverzija prema kvaliteti je sljedeća:
Egzaktno podudaranje
• Integralna promocija
• Standardna konverzija
• Konverzija definirana u klasi
•
Preopterećenje funkcija
Primjer:
void f(int x, int y) {
cout << "f(int,int)"<<endl;
}
void f(double x, double y) {
cout << "f(double, double)"<<endl;
}
f(1.2, 2);
Preopterećenje funkcija
Primjer:
void f(int x, int y) {
cout << "f(int,int)"<<endl;
}
void f(double x, double y) {
cout << "f(double, double)"<<endl;
}
f(1.2, 2);
Poziv je dvosmislen. Obje funkcije su dobri kandidati.
Preopterećenje funkcija
Primjer:
void f()
{ cout << "f()"<<endl; }
void f(int x)
{ cout << "f(int)"<<endl; }
void f(int x, int y)
{ cout << "f(int,int)"<<endl; }
void f(double x, double y=0.0) {cout << "f(double, double=0)"<<endl;}
f(1.2);
Preopterećenje funkcija
Primjer:
void f()
{ cout << "f()"<<endl; }
void f(int x)
{ cout << "f(int)"<<endl; }
void f(int x, int y)
{ cout << "f(int,int)"<<endl; }
void f(double x, double y=0.0) {cout << "f(double, double=0)"<<endl;}
f(1.2);
Bit će pozvana funkcija f(double, double) jer ne traži niti jednu konverziju.
Preopterećenje funkcija
Često se funkcija preopterećuje na osnovu toga što jedna verzija uzima
konstantnu referencu, a druga nekonstantnu. To je legalno jer prevoditelj
uvijek može odrediti koju funkciju pozvati.
void f(int & x) {
cout << "f(int &)"<<endl;
}
void f(const int & x) {
cout << "f(const int &)"<<endl;
}
Preopterećenje funkcija
Ako funkcija dobiva referencu na const objekt, onda je poziv korektan
jedino ako funkcija definira argument kao konstantnu referencu; verzija
funkcije koja deklarira argument kao nekonstantnu referencu ne
garantira da neće promijeniti objekt.
Ako pak imamo referencu na nekonstantan objekt, onda su obje
funkcije dobri kandidati i obje mogu biti pozvane. Ali, funkcija s
nekonstantnim argumentom ne traži konverziju argumenta, dok kod
funkcije s konstantnim parametrom treba prvo konvertirati referencu
na nekonstantan objekt u referencu na konstantan objekt. Prema tome,
verzija s nekonstantnom referencom je bolja te se poziva.
Preopterećenje funkcija
Posve je analogna situacija s pokazivačima na konstantan i nekonstantan
objekt.
void f(int * x) {
cout << "f(int *)"<<endl;
}
void f(const int * x) {
cout << "f(const int *)"<<endl;
}
Preopterećenje funkcija
Napomena: Kada funkcija uzima referencu na neki tip, a dobije
referencu na neki drugi tip koji je s njim vezan, onda prevoditelj radi
konverziju tako što kreira privremeni objekt i predaje funkciji
konstantnu referencu na taj objekt. U tom se dakle slučaju poziva verzija
funkcije s konstantnom referencom i ako takva nije definirana dolazi do
greške pri kompilaciji.
void f(int &a) {std::cout<<"f(int &)";}
void f(const int &a){std::cout<<"f(const int &)";}
….
double d=0.0
f(d);
Preopterećenje funkcija
Ne možemo preopteretiti funkciju obzirom na to uzimaju li pokazivač
ili konstantan pokazivač (ne pokazivač na const), jer funkcija ionako
dobiva kopiju pokazivača.
void f(int * x) {
cout << "f(int *)"<<endl;
}
void f(int * const x) {
cout << "f(int * const)"<<endl;
}
// Greška, redefinicija
Preopterećenje funkcija
class Klasa{
int i;
public:
Klasa (int a):i(a){ };
};
//void f(int i) { std::cout<<“ f( int ) ”;}
void f(double i) { std::cout<<“ f( double ) ";}
void f(Klasa i) { std::cout<<“ f( Klasa )";}
int main() {
Klasa k(2);
f(2);
f(1.2);
f(k);
return 0;
}
Preopterećenje funkcija
class Klasa{
int i;
public:
Klasa (int a):i(a){ };
};
//void f(int i) { std::cout<<“ f( int ) ”;}
//void f(double i) { std::cout<<“ f( double ) ";}
void f(Klasa i) { std::cout<<“ f( Klasa )";}
int main() {
Klasa k(2);
f(2);
f(1.2);
f(k);
return 0;
}
Preopterećenje funkcija
class Klasa{
int i;
public:
Klasa (int a):i(a){ };
};
void f(int i) { std::cout<<“ f( int ) ”;}
void f(double i) { std::cout<<“ f( double ) ";}
//void f(Klasa i) { std::cout<<“ f( Klasa )";}
int main() {
Klasa k(2);
f(2);
f(1.2);
f(k);
return 0;
}
Preopterećenje funkcija
void g( int i ) { cout<<"int"; }
void g( long i ) { cout<<"long"; }
void g( float i ) { cout<<"float"; }
void g( double i ){ cout<<"double"; }
int main()
{
short a=1;
g(a);
return 0;
}
Preopterećenje funkcija
void g( int i ) { cout<<"int"; }
void g( long i ) { cout<<"long"; }
void g( float i ) { cout<<"float"; }
void g( double i ){ cout<<"double"; }
int main()
{
short a=1;
g(a);
return 0;
}
Poziva se g(int ) jer integralna promocija ima prednost.
Preopterećenje funkcija
void g( long i ) { cout<<"long"; }
void g( float i ) { cout<<"float"; }
void g( double i ){ cout<<"double"; }
int main()
{
short a=1;
g(a);
return 0;
}
Preopterećenje funkcija
void g( long i ) { cout<<"long"; }
void g( float i ) { cout<<"float"; }
void g( double i ){ cout<<"double"; }
int main()
{
short a=1;
g(a);
return 0;
}
Poziv funkcije je dvosmislen.