PDA

Pogledaj cijelu verziju : C++ i stukturno programiranje



marijanovic
18-11-2007, 14:30
1. UVOD U C++
1.1 Strukturno programiranje i C++
U programiranju danas dominiraju dva osnovna koncepta : koncept strukturnog
programiranja i koncept objektno-orijentiranog programiranja. Pritom objektno orijentirano
programiranje predstavlja nadgradnju koncepta strukturnog programiranja, obogacujuci ga
novim mogucnostima i novim pristupom programiranju.

Tri osnovna koncepta strukturnog programiranja su :


stroga hijerarhijska struktura programa (što znaci da je upotreba naredbe GO TO
nepoželjna, ili barem svedena na minimum),

odvajanje definicije podataka od njihove obrade (odnosno, postoje odjeljci u programu za
definicije podataka i odjeljci za njihovu obradu) i


potprogrami. Logicke cjeline unutar programa izdvajaju se u potprograme s tocno
odredenom zadacom, tako da se isti programski kod može pozivati iz razlicitih dijelova
programa.
1.2 Odnos izmedu C i C++
Kao što je C strukturno-orijentirani programski jezik, tako je C++ (uz Javu, koja, medutim,
nasljeduje vecinu koncepata i sintaksu od C++) najtipicniji objektno-orijentirani programski jezik.
C++ predstavlja proširenje programskog jezika C, u kojeg uvodi koncept objektno-orijentiranog
programiranja. Jezik C ostao je i dalje ukljucen u jezik C++, kao njegov podskup. Kao takav,
C++ podržava oba programska koncepta : koncept strukturnog programiranja i koncept
objektno-orijentiranog programiranja.

1.2.1 Komentari
Osim oznaka /* i */ iz C-a koje predstavljaju pocetak, odnosno kraj bloka koji predstavlja
komentar, C++ uvodi i znak // koji oznacava red izvornog koda programa kao komentar. Na
primjer :

{
// Ovo je komentar!
}



1.2.2 Dodjela tipa podataka
U C-u se dodjela tipa vrši tako da se tip navede u zagradi ispred naziva varijable, kao u
slijedecem primjeru :

int i; float f;
f = (float) i;

C++ dopušta i drugi nacin, po uzoru na poziv funkcije :

int i; float f;
f = float (i);

1.2.3 Ulaz i izlaz
Ulaz i izlaz je u C++ riješen pomocu ulaznih i izlaznih tokova. Tokovi su klase definirane
u standardnim bibliotekama, koje omogucuju komunikaciju programa s vanjskim jedinicama (npr.
tipkovnicom i ekranom).

Ispis pomocu cout

Jedna od najocitijih razlika izmedu C i C++ je zamjena standardne biblioteke stdio
bibliotekom iostream. Biblioteka iostream zamjenjuje sve mogucnosti biblioteke stdio, odnosno,
tri programska retka iz slijedeceg primjera daju identican rezultat :

printf ("Danas je lijep dan.\n"); // C
cout << "Danas je lijep dan.\n"; // kombinirano rješenje
cout << "Danas je lijep dan." << endl; // C++

Znakovi “<<” predstavljaju operator umetanja u tok, jer se njime podatak umece u tok. S
desne strane se može naci bilo koji tip podatka: char, short, int, long int, char *, float, double,
long double, void * . Tako vrijede sljedece operacije:

cout<<9.3<<endl; //float
cout<<615 <<endl; //int
cout<<"Ovo je ispis znakovnih nizova"<<endl; //char *

Za formatiranje izlaza mogu se koristiti manipulatori, koji su definirani unutar biblioteke iomanip.

Primjer :

float pi=3.14159;
cout << setprecision(4) << pi << endl; // ispisuje se 3.141


Postoji više manipulatora: setw(int), dec, hex, oct, flush, endl...


Ucitavanje pomocu cin

Ulazom se upravlja na slican nacin kao s ispisom, s tim da se koristi ulazni tok cin i
operator izlucivanja iz toka (>>) jer se njime podatak izlucuje s izlaznog toka.

Primjer :

scanf ("%f",&pi); // C
cin >> pi; // C++


S desne strane se može naci bilo koji tip podatka: char, short, int, long int, char *, float, double,
long double, void * . Tako vrijede sljedece operacije:

int i;
lon l;
float f;
double d;

cin>>i; //int
cin>>l; //long int
cin>>f; //float
cin>>d; //double

Uspješnim ucitavanjem podatka operator izlucivanja kako rezultat vraca referencu na objekt tipa
istream. Zbog toga se upis može ulancavati pa možemo napisati:

cin>>i>>l>>f>>d;

Datotecni ulaz/izlaz

Umjesto biblioteke stdio koristi se biblioteka fstream (takoder se mogu koristiti ifstream i
ofstream). Slijedeci primjer u C-u upisuje dva reda teksta u izlaznu tekstualnu datoteku :

FILE *dat;

dat = fopen ("tekst1.txt","wt");

fprintf (dat,"%s","Prvi red teksta!\n");

fprintf (dat,"%s","Drugi red teksta!\n");

fclose(dat);

Odgovarajuci primjer u C++ :

fstream dat;
dat.open ("tekst.txt",ios::out);
dat << "Prvi red teksta!" << endl;
dat << "Drugi red teksta!" << endl;
dat.close();


U drugom primjeru definiran je datotecni objekt dat, iz klase fstream. Za otvaranje datoteke
korišten je funkcijski clan open. Datoteka je otvorena u modu out (izlazna datoteka). Tekst se u
datoteku upisuje korištenjem operatora umetanja u tok (<<). Da smo željeli citati iz datoteke
koristili bi mod ios::in.


Deklaracije varijabli

Varijable se u C++ deklariraju na isti nacin kao i u C-u, ali mogu biti deklarirane u bilo kojem
dijelu programskog koda.

Primjer:

{
int a;
... programski kod ...
float b;
... programski kod ...
char c;
... programski kod ...
}


Konstante

U C-u se za definiranje konstanti koristi pretprocesorska naredba #define. Na primjer:

#define UKUPNO 50

U tu svrhu u C++ se može koristiti i kljucna rijec
const :

const UKUPNO = 50;

Takoder, C++ dopušta da pojedini argumenti funkcija budu konstantni, time se sprecava
promjena njihove vrijednosti unutar funkcije. Primjer :

void funkcija (const int a)

{
a=10; // Greška; ne može se promijeniti konstanta
}


Preopterecenje funkcije

C++ omogucuje da više funkcija ima isti naziv, pod uvjetom da su im liste parametara razlicite.
Prema listi parametara kod poziva funkcije odreduje se koja ce funkcija biti pozvana.

Primjer :

#include <iostream.h>
void funkcija(){
cout << "Funkcija bez parametara!" << endl;
}
void funkcija(int a){
cout << "Cjelobrojni parametar a = " << a << endl;
}
void funkcija(float b, float c){
cout << "Realni parametar b = " << b << endl;
cout << "Realni parametar c = " << c << endl;
}



void main(){
funkcija();
funkcija(10);
funkcija(2.71,3.14);
}

Ispisuje se :

Funkcija bez parametara!
Cjelobrojni parametar a = 10
Realni parametar b = 2.71
Realni parametar c = 3.14

Podrazumijevani argumenti funkcija

Prilikom poziva funkcije potrebno je navesti listu argumenata koja odgovara listi argumenata
u zaglavlju funkcije. C++ dopušta da se neki od argumenata u pozivu funkcije izostave, tako
da se umjesto njih koriste podrazumijevane vrijednosti.
Primjer:


#include <iostream.h>
void funkcija(int a, int b=5){
cout << "a = " << a << endl;
cout << "b = " << b << endl << endl;
}
void main(){
funkcija (3,4);
funkcija (10);
}


Ispisuje se :

a=3
b=4
a = 10
b=5

U drugom pozivu funkcije izostavljen je drugi argument, umjesto kojeg se koristi
podrazumijevani (b=5).

Alokacija memorije

C++ zamjenjuje C-ovu funkciju za alokaciju memorije malloc i funkciju za dealokaciju
memorije free s new i delete. New i delete omogucuju alokaciju korisnicki definiranih tipova
jednako kao i preddefiniranih.

C:
int *pok;
pok = (int*)malloc(sizeof(int));
*pok = 10;
free (pok);


C++ :

int *pok;
pok = new int;
*pok = 10;
delete pok;


Deklaracije referenci

U C-u se cesto koriste pokazivaci za prosljedivanje argumenata funkcijama. U C++ može se
koristiti referentni operator (&) u listi argumenata, što cini programski kod cišcim.

C:
void zamjena (int *a, int *b){
int t = *a;
*a = *b;
*b = t;
}
void main(){
int x = 3, y = 5;
zamjena (&x, &y);


printf ("%i %i\n",x,y);
}


C++:

void zamjena (int &a, int &b){
int t = a;
a = b;
b = t;
}
void main(){
int x = 3, y = 5;
zamjena (x,y);
cout << x << y << endl;;
}


Takoder, moguce je referenci varijable pridružiti varijablu, kao u slijedecem primjeru :

int a = 0;
int &b = a; // varijable a i b zauzimaju isti memorijski prostor
b = 5;
cout << a << endl; // ispisuje se 5



2. OBJEKTI I KLASE
2.1. Uvod u objektno programiranje
Objektno orijentirano programiranje: Postupak izrade aplikacija u kojem se svojstva
apstraktnih ili stvarnih objekata modeliraju programskim klasama i objektima. U stvarnom svijetu
okruženi smo objektima (automobil, racunalo, pas, drvo, ...). Svaki objekt definiran je stanjem i
ponašanjem. Npr. za automobil stanje odreduje trenutna brzina, kolicina goriva u spremniku i sl.,
a ponašanje može biti ubrzavanje, kocenje, skretanje i sl.

Objekt u programskom okruženju je skup varijabli i pripadnih metoda. Varijable odreduju
stanje, a metode ponašanje objekta. Varijable objekta nazivaju se i varijable primjerka (instance
variables) – svaki primjerak (instanca) odredenog objekta sadrži vlastitu kopiju varijabli
primjerka. Metode mijenjaju stanje objekta, a po potrebi mogu stvarati i nove objekte.

Objekti medusobno razmjenjuju informacije i traže jedan od drugog usluge. Pritom
okolina ne mora znati ništa o unutanjem ustrojstvu objekta. Okolina komunicira sa objektom
preko javnog sucelja (engl. public interface). Nacin na koji se ostvaruje reprezentacija objekta
jest implementacija objekta (engl. implementation) i ona se najcešce skriva od okoline da bi se
osigurala konzistentnost objekta. Klasa se dakle sastoji od opisa javnog sucelja i od
implementacije. Objedinjavanje sucelja i implementacije naziva se enkapsulacija (engl.
encapsulation).

Klasa je nacrt objekta, odnosno predložak po kojem je odredeni objekt stvoren. Npr.
odredeni tip automobila definiran je jednim nacrtom (odnosno skupom nacrta). Na temelju
jednog nacrta moguce je proizvesti više primjeraka istog tipa automobila (tj. objekata), koji ce se
medusobno razlikovati po stanju i ponašanju. Podskup stanja i ponašanja (varijabli i metoda)
može biti zajednicki svim objektima odredene klase. Nazivamo ih varijablama i metodama klase.

Sintaksa za kreiranje klase u C++ je vrlo slicna sintaksi za kreiranje strukture u C-u:

Struct datum
{
int dan, mjesec, godina;
};


Ali pri kreiranju klase osim varijabli definiramo i metode kojima se objekt služi kako bi pristupio
svojim varijablama i mijenjao ih.

class datum

{

int dan; // Varijable koje se koriste za pohranu datuma

int mjesec;

int godina;

public: // Metode klase

datum(); //constructor

datum(int,int,int);

void postavidatum (int, int, int); //prototip funkcije

void pokazi(); //function prototype


Kada definiramo klasu možemo definirati i njene metode. Definiranje metode ima sljedeci format:

Povratni tip ime klase::ime metode (parametri) {
Tijelo metode
}

Slijedi definicija metoda za naš primjer:

void datum::postavidatum (int dd., int mm, int yy)

{
dan=dd;
mjesec=mm;
godina=yy;

}

void datum::pokazi (void)
{
cout<<”Dan:”<<dan<<”\n”<<”Mjesec:”<<mjesec<<”\n”<<”Godina:”<<godina;
}

2.2. Konstruktori
Konstruktor je posebna metoda, cije ime je jednako imenu klase, koja se automatski
izvršava u trenutku stvaranja objekta. Najcešce se koristi za inicijalizaciju varijabli objekta. U
pravilu se definira više konstruktora.

datum::datum ()

{
dan=1;
mjesec=1;
godina=2007;

}

U gornjem primjeru konstruktor inicijalizira datum na 1.1.2007. godine.

-Svaka klasa mora imati najmanje jedan konstruktor.
-Ime konstruktora mora biti jednako imenu klase.
-Iako nije prikazano u gornjem primjeru konstruktor može primati i parametre.

-Konstruktor ne vraca nikakvu vrijednost (cak niti void).
-Objekt se može stvoriti samo jednom kao što se i varijabla samo jednom može definirati.



2.3. Stvaranje objekta
Nakon što smo definirali klasu možemo stvoriti pojedinacne objekte tako da navedemo ime klase
kojoj objekt pripada i ime objekta:

datum danas;

-Kreirali smo objekt klase datum koji se zove danas
-Buduci da je objekt danas instanca klase datum pri njegovoj se deklaraciji automatski
poziva konstruktor koji postavlja vrijednost datuma na 1.1.2007.

-Objekt danas izgleda ovako:

danas
dan=1
mjesec=1
godina=2007
void postavidatum(int,int,int)
void pokazi()

-Kao i kod struktura možemo pristupati pojedinacnim varijablama objekta koristeci
sljedecu notaciju "."

danas.dan=15;

-No rijetko cemo direktno pristupati varijablama objekta jer tako ne koristimo prednost
objektno orijentiranog programiranja. Umjesto toga cemo pozvati metodu objekta.
Sintaksa poziva metode je:

Ime objekta.ime metode (parametri);
danas.postavidatum(15,10,2007);


-Objekt danas sada izgleda ovako:

danas
dan=15
mjesec=10
godina=2007
void postavidatum(int,int,int)
void pokazi()

-Za ispis trenutnog datuma:

danas.pokazi();


2.4. Preopterecenje konstruktora
U C++ moguce je preopterecenje konstruktora. Da bismo to pokazali dodat cemo još jedan
konstruktor postojecoj klasi.

//Stari konstruktor koji postavlja datum na predefiniranu vrijednost

datum(); //ovo je samo prototip konstruktora

//Novi konstruktor koji stvara objekt datum sa definiranim vrijednostima za dan, mjesec i //godinu

datum(int dd, int mm, int yy); //ovo je samo prototip konstruktora

datum::datum(int dd, int mm, int yy)

{

dan=dd;

mjesec=mm;

godina=yy; //ovo je definicija konstruktora
}

Primjer kako pozvati oba konstruktora:

datum petak; //ovo ce pozvati konstruktor sa predefiniranim vrijednostima
datum danas(15,11,2007); //ovo ce pozvati novi konstruktor

2.5. Destruktor
Kao što se objekt može stvoriti tako se mora moci i uništiti. C++ omogucuje eksplicitno korištenje
destruktora, tj. metode koja se poziva neposredno prije uništenja objekta.
Sintaksa za destruktor je vrlo jednostavna:


~ime klase();
U našem primjeru:

~datum();

Destruktori se koriste za cišcenje memorije nakon što su objekti uništeni.


2.6. Dodjela prava pristupa
Apstrakcija je mogucnost uklapanja tudeg programa u svoj vlastiti program bez
razumijevanja kako je program implementiran. Moramo znati što program radi, ali ne moramo
znati kako on radi. Ljudi koji rade velike programe moraju brinuti samo kako radi njihov kod i
kako koristit kod koji su napisali drugi ljudi, a ne moraju razumjeti citav program.

Ovijanje (engl. encapsulation) je mehanizam kontrole pristupa varijablama i metodama
klasa i objekata. Ona odvaja implementaciju od korisnika. Svaki pristup unutarnjoj reprezentaciji
je kroz metode klase koje predstavljaju sucelje prema korisniku. Dva su razloga zbog kojih
korisnik ne bi trebao imati pristup unutarnjoj prezentaciji objekta:

-Ovijanje sprjecava korisnika da mijenja podatke na ilegalan nacin
-Ovijanje omogucava programeru da mijenja stanje objekta u bilo koje vrijeme

C++ implementira enkapsulaciju korištenjem kljucnih rijeci public, private i protected.
Prava pristupa odreduju koji ce clanovi razreda ili klase biti dostupni izvan razreda, koji iz
naslijedenih razreda, a koji samo unutar razreda. Svaki clan klase može imati jedan od tri
moguca nacina pristupa što ce biti pokazano na primjeru:

Primjer:

class Pristup {

public:

int a, b;
void Funkcija1(int brojac);


private:

int c;

protected:

int d;
int Funkcija2();
};



Javni pristup se dodjeljuje kljucnom rijeci public. Clanovima sa javnim pristupom može
se pristupiti i izvan klase. Varijable a i b te Funkcija1() se mogu pozvati tako da se
definira objekt klase Pristup i da im se pomocu operatora . pristupi.

Privatni pristup se dodjeljuje kljucnom rijeci private. Clanovi sa privatnim pristupom nisu
dostupni vanjskom programu i klasama koji nasljeduju promatranu klasu. Tako se
varijabli c može pristupiti samo preko funkcijskih clanova klase pristup.

Zašticeni pristup se dodjeljuje kljucnom rijeci protected. Clanovi sa zašticenim pristupom
nisu dostupni vanjskom programu nego samo funkcijskim clanovima klase i klasama koji
nasljeduju promatranu klasu.

Ako se eksplicitno ne navede pravo pristupa nekom clanu klase podrazumijeva se privatno
pravo pristupa. Za ilustraciju korištenja prava pristupa imamo stvorit cemo objekt klase
Pristup i pokazati kojim se clanovima može pristupiti iz kojeg dijela programa:
Pristup x;
void Pristup::Funkcija1(int brojac) {
//sada smo unutar funkcijskog clana klase pa možemo
//pristupiti svim clanovima klase
a=brojac;
c=a+5;
Funkcija2();
}
int main() {
x.a=x.b=6; //U redu jer a i b imaju javni pristup
x.Funkcija1(1); // U redu jer Funkcija1() ima javni pristup
cout<<x.c<<endl //Nije u redu jer c ima privatni pristup i ne može
//mu se pristupiti izvana
x.Funkcija2(); //Nije u redu jer Funkcija2() ima zašticeni pristup
};

2.7. Javno sucelje
Clanovi razreda sa javnim pristupom formiraju javno sucelje objekta. Programer analizira
objekte i nacin njihovog korištenja te tako odreduje koji clanovi pripadaju javnom sucelju. On
zatim obznanjuje suradnicima što tocno trebaju pružiti objektu i što od njega mogu dobiti
nazad. Sam sadržaj objekta je crna kutija za korisnike. Implementaciju klase korisnik piše na
osnovi javnog sucelja cime sucelje postaje neovisno o implementaciji. Ako se kasnije ustvrdi
da je neka implementacija bolja objekt se jednostavno preradi, dok ostatak koda (npr. kod
drugih programera) ne treba dirati jer on vidi samo javno sucelje objekta koje se ne mijenja.
Primjer:
class Vektor {
private:
float ax, ay;
public:
void PostaviXY(float x, float y) {
ax=x;
ay=y;
}
float VratiX() { return ax; }
float VratiY() { return ay; }
void MnoziSkalarom(float skalar) {
ax*=skalar;
ay*=skalar;
}
};



Implementacija vektora je izvedena u Descartesovom koordinatnom sustavu. Javno
sucelje ne govori ništa o koordinatama nego samo omogucava da se utvrdi projekcija svakog
vektora na os x i os y neovisno u kojem je koordinatnom sustavu vektor prikazan. Mi u
implementaciji možemo promijeniti koordinatni sustav u polarni ako se pokaže da program
bolje radi, ali javno sucelje ostaje isto.

Primjer:

#include <iostream.h>
class Point {
int x;
int y;
public:
Point();
Point(int, int);
~Point();
};
Point::Point(){ //Definiranje konstruktora
x=0;
y=0;
cout<<"Poziv konstruktora bez parametara"<<endl;
cout<<"X="<<x<<"\tY="<<y<<endl;
}
Point::Point(int InitX,int InitY){ //Preoptereceni konstruktor
x=InitX;
y=InitY;
cout<<"Poziv preopterecenog konstruktora "<<endl;
cout<<"X="<<x<<"\tY="<<y<<endl;
}
Point::~Point(){ //Definiranje destruktora
cout<<"Poziv destruktora"<<endl;
}
void main(){
Point P1; //Stvaranje objekta P1 prvim konstruktorom
Point P2(10,10); //Stvaranje P2 preopterecenim konstruktorom
}


a) Napraviti metodu koja ce postavljati koordinate tocke na zadanu vrijednost i koja ce
ispisati koordinate tocke.
b) Dodati metodu koja ce racunati udaljenost izmedu dvije tocke i ispisati udaljenost na
ekran. Funkciju preopteretiti da racuna udaljenost zadane tocke od ishodišta
koordinatnog sustava.


#include <iostream.h>

#include <math.h>

class Point {

int x;

int y;

double d;

public:

Point();

Point(int, int);

~Point();

void SetXY(int,int); //Deklaracija metode SetXY

void Distance(Point,Point);

void Distance(Point);

};

Point::Point(){ //Definiranje konstruktora

SetXY(0,0);

cout<<"Poziv konstruktora bez parametara"<<endl;

}

Point::Point(int InitX,int InitY){ //Preoptereceni konstruktor

SetXY(InitX,InitY); //Poziv metode SetXY

cout<<"Poziv preopterecenog konstruktora "<<endl;

}

void Point::SetXY(int Xset,int Yset) { //Definiranje metode SetXY

x=Xset;

y=Yset;

cout<<"X="<<x<<"\tY="<<y<<endl;

}

void Point::Distance(Point P1, Point P2) { //Definiranje metode Distance

d=sqrt((P1.x-P2.x)* (P1.x-P2.x) + (P1.y-P2.y)* (P1.y-P2.y));

cout<<"Udaljenost dvaju tocaka je d="<<d<<endl;

}

void Point::Distance(Point P1) {

d=sqrt((P1.x-0)* (P1.x-0) + (P1.y-0)* (P1.y-0));

cout<<"Udaljenost od ishodista je d="<<d<<endl;

}

Point::~Point() { //Definiranje destruktora

cout<<"Poziv destruktora"<<endl;

}

void main() {

Point P1; //Stvaranje objekta P1 prvim konstruktorom

Point P2(10,10); //Stvaranje P2 preopterecenim konstruktorom

Point P3(15,18); //Stvaranje P3 preopterecenim konstruktorom

P1.SetXY(5,5);

P1.Distance(P1,P2);

P3.Distance(P3);

}


2.8. Nasljedivanje
Nasljedivanje je mehanizam pomocu kojega je na temelju postojecih klasa moguce
definirati nove i proširene klase. Ideja nasljedivanja je da se odrede slicne klase i da se pri
nasljedivanju promijene samo neka specificna svojstva, dok se ostala svojstva nasljeduju u
nepromijenjenom obliku.
Svojstva izvedene klase:
-Ona nasljeduje varijable i metode svoj nadredene klase tj. klase koju nasljeduje
-Ima svoje nove varijable i metode koje ispunjavaju njene specificne zahtjeve
-Izvedena klasa nasljeduje sva svojstva i mogucnosti nadredene klase
-Svaki konstruktor u izvedene klase trebao bi pozvati konstruktor nadredene klase
-Npr. klasa Tocka može biti nadredena klasa klasi Krug (krug se može opisati
pomocu tocke i pripadnog radijusa)
Primjer:

#include <iostream.h>
#include <math.h>
class Point {
int x;
int y;
double d;
public:
Point();
Point(int, int);
~Point();
void SetXY(int,int);
void Distance(Point,Point);
void Distance(Point);
};
Point::Point(){
SetXY(0,0);
cout<<"Poziv konstruktora bez parametara klase Point"<<endl;
}
Point::Point(int InitX,int InitY){
SetXY(InitX,InitY);
cout<<"Poziv preopterecenog konstruktora klase Point "<<endl;
}
void Point::SetXY(int Xset,int Yset) {
x=Xset;
y=Yset;
cout<<"X="<<x<<"\tY="<<y<<endl;
}
void Point::Distance(Point P1, Point P2) {
d=sqrt((P1.x-P2.x)* (P1.x-P2.x) + (P1.y-P2.y)* (P1.y-P2.y));
cout<<"Udaljenost dvaju tocaka je d="<<d<<endl;
}



void Point::Distance(Point P1) {
d=sqrt((P1.x-0)* (P1.x-0) + (P1.y-0)* (P1.y-0));
cout<<"Udaljenost od ishodista je d="<<d<<endl;
}
Point::~Point() {
cout<<"Poziv destruktora klase Point"<<endl;
}


class Circle:public Point {
int radius;
public:
Circle();
Circle(int,int,int);
~Circle();
};
Circle::Circle():Point() {
radius=10;
cout<<"Radijus r="<<radius<<endl;
cout<<"Poziv konstruktora bez parametara klase Circle "<<endl;
}
Circle::Circle(int InitX,int InitY, int InitR):Point(InitX,InitY) {
radius=InitR;
cout<<"Radijus r="<<radius<<endl;
cout<<"Poziv preopterecenog konstruktora klase Circle "<<endl;
}
Circle::~Circle() {
cout<<"Poziv destruktora klase Circle "<<endl;
}


void main() {
Circle C1; //Stvaranje objekta C1 prvim konstruktorom
Circle C2(6,6,5); //Stvaranje objekta C2 preopterecenim konstruktorom
}


Ideja nasljedivanja je da svaki objekt podredene klase sadrži varijable i metode
nadredene klase. Ako su varijable u nadredenoj klasi nalaze pod privatnim pristupom
(private) onda im se ne može pristupiti iz podredene klase. Da bi im se moglo pristupiti
varijable bi se morale nalaziti pod kljucnom rijeci protected, što bi znacilo da im metode iz
podredene klase mogu pristupiti. Ali ako želimo zadržati fleksibilnot i integritet prezentacije
podataka u osnovnoj klasi moramo ih držati kao protected. Stoga ako je moguce podredene
klase bi trebale koristiti metode sa javnim pristupom (public) u nadredenoj klasi kako bi
pristupile zašticenim podacima. Npr. klasa Circle pomocu metode SetXY koja u klasi Point
ima javni pristup postavlja koordinate središta kruga (x,y) koje u klasi Point imaju privatni
pristup.



2.9. Preopterecenje metoda nadredene klase
Ponekad je potrebno promijeniti metodu koja je naslijedena od nadredene klase jer
želimo dobiti neka nova svojstva. To se cini preopterecenjem metode koja se nalazi u
nadredenoj klasi. Potrebno je napisati sasvim novi kod za navedenu metodu. Koja ce od
dvaju metoda biti pozvana prevodilac odlucuje na osnovu objekta koji poziva metodu. Ako
objekt koji poziva metodu pripada nadredenoj klasi onda ce biti pozvana metoda iz
nadredene klase, a ako objekt pripada podredenoj klasi onda ce biti pozvana metoda iz
podklase.

Primjer:

#include <iostream.h>
class A {
public:
void print() {
cout<<”Metoda koja pripada nadredjenoj klasi”<<endl;}
};
class B: public A {
public:
void print() {
cout <<”Metoda koja pripada podredjenoj klasi ”<<endl;}
};
void main() {
A Obj1;
B Obj2;
Obj1.print(); //Ovo poziva metodu iz nadredene klase
Obj2.print(); //Ovo poziva metodu iz podredene klase
Obj2.A::print(); //Ovo poziva metodu iz nadredene klase pomocu operatora ::
}



Primjer:

#include <math.h>
#include <iostream.h>
#include<string.h>
class Datum{
public:
int dan;
int mjesec;
int godina;
void Ispis();
Datum(){dan=1; mjesec=1;godina=2007;}
Datum(int d, int m , int g){dan=d; mjesec=m; godina=g;}
};
void Datum::Ispis(){
cout<<"\n"<<endl;
cout<<"datum "<<dan << mjesec << godina<<endl;
}
class Student:public Datum{
public:
Datum D1;
Datum D2;
char ime[32];
char prezime[32];
int idn;
Student();
Student(int ID);
Student(char[], char[], int, int, int, int,int,int,int);
void Ispis();
};
Student::Student(){
strcpy(ime, "Marko");
strcpy(prezime, "Markic");
idn=1000;
}
Student::Student(int ID){
idn=ID;
}
Student::Student(char im[32], char pr[32],int IDN, int d, int m, int g, int dd,int mm, int gg): D1
(d, m,g), D2(dd,mm,gg) {
strcpy(ime, im);
strcpy(prezime, pr);
idn=IDN;
}
void Student::Ispis(){
cout<<"\n"<<endl;
cout<<"Ime "<<ime<<endl;
cout<<"Prezime "<<prezime<<endl;
cout<<"Maticni broj "<<idn<<endl;
cout<<"datum rodenja "<<D1.dan<<"."<<D1.mjesec<<"."<<D1.godina<<endl;



cout<<"datum upisa "<<D2.dan<<"."<<D2.mjesec<<"."<<D2.godina<<endl;
}


class Ispit: public Student{
public:
char imeisp[32];
int ocjena;
Datum D3;
Ispit();
Ispit(char[], int, int, int, int, int);
void Ispis();
void Polozeno();
};
Ispit::Ispit():Student(){
strcpy(imeisp, "Oop");
ocjena=5;
}
Ispit::Ispit(char isp[32], int ocj, int ID, int da, int mj, int go):Student(ID), D3(da, mj, go){
strcpy(imeisp, isp);
ocjena=ocj;
}
void Ispit::Polozeno(){
if (ocjena>1){
cout<<"\n"<<endl;
cout<<"Polozeni ispit je: "<< imeisp<<endl;
}
}
void Ispit::Ispis(){
cout<<"\n"<<endl;
cout<<"Ime ispita "<<imeisp<<endl;
cout<<"Ocjena "<<ocjena<<endl;
cout<<"Maticni broj "<<idn<<endl;
cout<<"datum polaganja "<<D3.dan<<"."<<D3.mjesec<<"."<<D3.godina<<endl;
}
main (){
Student S1("Josip", "Josipovic", 1015, 5, 4, 1987, 12, 7, 2004);
Student S2("Marko", "Markic", 1111, 6,6,1986, 1,1,2003);
S1.Ispis();
Ispit I1("Oop",0, 1015, 30,6,2007);
Ispit I2("Teorija mreza", 2, 1015, 1,9,2007);
Ispit I3("Baze podataka", 4, 1111, 12,5,2007);
I2.Polozeno();
I3.Polozeno();
I1.Ispis();
I2.Ispis();
I3.Ispis();
return 0;
}



2.10. Polimorfizam i virtualne funkcije
Nasljedivanjem klasa možemo konstruirati hijerarhijsku strukturu koja ce nam poslužiti
za lakše oblikovanje programskog koda. Zajednicka svojstva grupe klasa mogu biti sadržana
u jednoj ili više nadredenih klasa iz koje su izvedene ostale klase. Imamo li više izvedenih
klasa, možemo pojednostaviti rukovanje s njima koristeci pokazivac ili referencu na osnovnu
klasu. Pokazivac tipa osnove klase može, osim na objekte osnovne klase, pokazivati i na bilo
koji objekt izvedenih klasa, kojima nije jednak po tipu. Ovisno o vrsti objekta na koji pokazuje,
prevodilac može razlicito interpretirati upotrebu takvog pokazivaca (polimorfizam).
OsnovnaK *p;
IzvedenaK1 a;
IzvedenaK2 b;
p=&a;
p=&b;
p=new IzvedenaK3;
Izvedena klasa moci ce koristiti public i protected metode osnovne klase, ali se te metode
takoder mogu i nadopuniti ili cak nanovo definirati u izvedenoj klasi. Ispred deklaracije
metode osnovne klase koja ce biti redefinirana u nekoj izvedenoj klasi potrebno je staviti
kljucnu rijec virtual. Tada ce prevodilac pozvati istoimenu metodu izvedene klase, cak i kada
je pokazivac, koji pokazuje na objekt izvedene klase, iz osnovne klase.
class Osnovni {
public:
virtual void virt_fun(void)
{ ... }
void fun(void)
{ ... }
};
class Izvedeni {
public:
void virt_fun(void)
{ ... }
void fun(void)
{ ... }
};
...
Osnovni *p = new Izvedeni;
p->fun(); // izvrsit ce se funkcija Osnovni::fun()
p->virt_fun(); // izvrsit ce se funkcija Izvedeni::virt_fun()
...
p->Osnovni::virt_fun() // ako bas zelimo pozvati funkciju iz osnovnog razreda,
// mozemo joj pristupiti operatorom dosega ::
U tijelu redefinirane metode u izvedenoj klasi možemo takoder pozvati i istoimenu metodu
osnovne klase (npr. ako je dio posla koji obavljamo identican) sa:
void Izvedeni::virt_fun()
{ ...
Osnovni::virt_fun();
}



Primjer:

#include<iostream.h>
class A {
public:
void funkcija() {
cout<<”Poziva se A::funkcija()”<<endl;
}
};
class B : public A {
public:
virtual void funkcija() {
cout<<”Poziva se B::funkcija()”<<endl;
}
}:
class C : public B {
public:
virtual void funkcija() {
cout<<”Poziva se C::funkcija()”<<endl;
}
};



2.11. Predlošci funkcija i klasa
Predlošci omogucuju pisanje opcenitog koda koji se može primijeniti za razlicite tipove
podataka. Tako se omogucuje lakše i brže pisanje složenijih programa. Na primjer ako
napišemo algoritam za sortiranje (poput bubble sort ili quick sort) kod je isti za sve tipove
podataka, ali do sada programeri nisu bili u mogucnosti napisati funkciju koja ce vrijediti za sve
tipove podataka. Problem je u tome što C++ zahtijeva tocno odredivanje tipa podataka s kojima
radi. Programer zbog toga mora mijenjati deklaraciju funkcije i kopirati kod. Ali se tako se
povecava kod programa i ako se pojavi greška izmjena se mora napraviti u svim funkcijama.
Rješenje problema je u korištenju predložaka koji omogucuju da se isti algoritam koristi za
razlicite tipove podataka.

Postoje dvije vrste predložaka:

• Predlošci funkcija
• Predlošci klasa
2.11. 1. Predlošci funkcija
Definicija predloška funkcije zapocinje kljucnom rijeci template. Svaki predložak ima listu
formalnih parametara koja se nalazi izmedu znakova < lista parametara > . Svaki formalni
parametar se sastoji od kljucne rijeci class iza koje se navede identifikator.

Funkcija manji() koja vraca manji od dva broja može se pisati:

template <class NekiTip>
NekiTip manji (NekiTip a, NekiTip b) {

return a < b ? a : b;
}

Prilikom prevodenja predložak ce rezultirati konkretnom funkcijom za svaki tip
parametara. Pisanje tijela funkcije je isto kao i kod regularnih funkcija samo što ne navodimo
konkretan tip nego tip koji se nalazi u listi parametara. Da bi se moglo izvršiti kompajliranje
programa u kojem se koristi genericka funkcija, kompajleru mora biti na raspolaganju potpuna
definicija funkcije, a ne samo deklaracija funkcije kao što je slucaj kod upotrebe regularnih
funkcija.


Primjer:

template <class Tip>
Tip Kvadrat (Tip x) {
Tip result = x*x;
return result;
}
int main ()
{
int i=5, k;
float a=10.9, b;
k=Kvadrat<int>(i);
b=Kvadrat<float>(a);
cout << k << endl;
cout << b << endl;
return 0;
}


Ispis je:
25

118.81
U gornjem primjeru ne mora se specificirati tip podatka jer je jasno da su varijable a i b tipa float,
ali je preporuka da se eksplicitno specificira tip parametara.

Mogu se definirati i funkcije s više tipova.

Primjerice:
template <class T, class U>
T GetMin (T a, U b) {

return (a<b?a:b);
}
definira funkciju s dva argumenta kojima su tipovi T i U. Moguce je izvršiti poziv funkcije na

slijedeci nacin:
int i,j;
long l;
i = GetMin<int,long> (j,l);

ili još jednostavnije


i = GetMin (j,l);
Iako su j i l razlicitog tipa, kompajler sam vrši pretvorbe tipova.


Osim kljucne rijeci class možemo koristiti i kljucnu rijec
typename kojom eksplicitnije navodimo
da se ne radi o klasi vec
o tipu podatka. Ali ona ima i dodatnu funkciju, njome možemo unutar
predloška odrediti da nešto predstavlja tip, a ne nešto drugo.

Na primjer:
template <typename T> //T oznacava tip
void funkcija() {
T :: A * pok1; //Ove dvije deklaracije imaju
typename T :: A * pok2; //razlicito znacenje
}

Pretpostavimo da naš predložak u sebi mora imati definiran tip A. Prva deklaracija ne daje
željenu vrijednost. Ona pristupa identifikatoru A unutar tipa T i množi ga sa pokazivacem pok1
što je pogrešno. Druga deklaracija pomocu typename oznacava da se radi o tipi T::A i prilikom
instantacije se specificira stvarni tip.

Preopterecenje predložaka funkcije

Predložak funkcije može biti preopterecen proizvoljan broj puta pod uvjetom da se potpisi
funkcija razlikuju po tipu ili broju argumenata. Na primjer može se definirati funkcija zbroji:

//Zbrajanje dvaju elemenata
template <class Tip>
Tip zbroji (Tip a, Tip b);


//Zbrajanje triju elemenata
template <class Tip>
Tip zbroji (Tip a, Tip b, Tip c);


//Zbrajanje dvaju nizova
template <class Tip>
Tip zbroji (Tip *niza, Tip *nizb, Tip *rez, int brElem);


Nije moguce preopteretiti funkcije tako da se razlikuju samo u povratnom tipu:



//Pogreška: funkcija se razlikuje samo u povratnom tipu

template <class Tip>

int zbroji (Tip a, Tip b);

2.11.2. Predlošci klasa
Slicno kao kod predložaka funkcija postoje i predlošci klasa koji se koriste za definiranje
opcih klasa koje se kasnije konkretiziraju stvarnim klasama. Primjer takvih klasa su spremnici ili
kontejnerske klase. To su klase koje manipuliraju objektima neke druge klase te se cesto koriste
za pohranjivanje nizova objekata. Na primjer ako želimo napraviti klasu koja ce manipulirati
listama. Listu možemo predstaviti kao objekt. Pravila za održavanje liste ne ovise o stvarnim
objektima. Postoje standardne operacije nad listom: dodavanje elementa na pocetak, dodavanje
elementa, brisanje, pretraživanje itd. Operacije se konceptualno obavljaju isto neovisno o tipu,
stoga je korisno definirati opci predložak klase koji definira opcenite algoritme. Ako ne koristimo
predloške tada moramo imati klasu za svaku listu npr. CjelobrojnaLista, RealnaLista itd.

Za realizaciju liste trebamo dvije klase:


Klasa Lista koja ce definirati opca svojstva liste, memorirati prvi i posljednji clan liste i
definirati javno sucelje. Klasa Lista sastoji mora imati sljedece operacije:
o
void UgurajClan(element, izaKojeg) dodaje element iza clana na kojeg pokazuje
izaKojeg
o
void GoniClan(element) izbcuje clan na koji element pokazuje
o
bool Prazna() vraca true ako je lista prazna

Klasa ElementListe koja ce definirati objekt koji se smješta u listu. Objekt ce sadržavati
pokazivace na prethodni i iduci clan, te vrijednost objekta.
Deklaracija predloška klase zapocinje tako da se ispred kljucne rijeci class umetne kljucna rijec
template iza koje se unutar znakova <> umetnu argumenti predloška.

template <class Tip>

class Lista;

Ako imamo više parametara:

template <class Tip1, class Tip2, class Tip3 >

class NekaKlasa;

Klase definirane kao predlošci pozivaju se na isti nacin kao obicne klase s time da se iza imena
klase unutar <> moraju navesti parametri. Lista cijelih brojeva se ozncava kao:

Lista<int>


A lista kompleksnih brojeva:
Lista<Kompleksni>


Primjer: definicija klase ElementListe:

template<class Tip>
class ElementListe {
private:
Tip vrijednost;
ElementListe *prethodni, *sljedeci;
public:
ElemetListe *Prethodni() { return pretodni; }
ElemetListe *Sljedeci () { return sljedeci; }
void PostaviPrethodni (ElemetListe *pret) { pretodni=pret; }
void PostaviSljedeci (ElemetListe *sljed) { sljedeci=sljed; }
ElemetListe (const Tip &elem);
ElemetListe ();
Tip &DajVrijednost();
};


Identifikator Tip oznacava tip klase koji ce se navesti prilikom poziva klase. Pomocu tog
identifikatora možemo raditi sve što možemo i sa drugim tipovima: stvarati objekte, proslijedivati
ih, brisati itd. Unutar klase identifikator ElemetListe se koristi bez parametara jer su oni
navedeni u template deklaraciji na samome pocetku. No izvan deklaracije klase pored
identifikatora klase se uvijek moraju navesti parametri.

Instanciranje predložaka klase

Predložak klase se instancira tako da se iza imena klase unutar <> navedu svi parametri
predloška:
ElementListe<int> eli(5);
ElementListe<Kompleksni> elk(Kompleksni());
ElementListe<char *> elz(“Listam”);


U gornjem primjeru samo ime ElementListe oznacava sve moguce elemente liste i ono nije
dovoljno za definiranje objekta. Potpuna definicija je ElementListe<int> koja opisuje element
liste cijelih brojeva. Gornjim primjerom dobili smo tri neovisne klase. Prevoditelj ce tri puta
zamijeniti formalni tip stvarnim tipom. Nakon toga se provjerava sintaticka ispravnost koda. Za
neki tip kod može raditi a za drugi ne. To se može dogoditi ako neke operacije nisu podržane za
odredei tip (npr. za neke tipove ne postoji operator usporedbe).


Primjer:

template <class T>

class array {

T *m_pbuff;

int m_size;

public:

array(int N = 10): m_size(N) {m_pbuff=new T[N];}

~array() {delete [ ]Sm_pbuff;}

int size() {return m_size;}

void set(int x, T value);

T get(int x);

T & operator [ ]S(int i)

{return m_pbuff[i];}

};

template <class T> void array<T>::set(int x, T value)

{ m_pbuff[x]=value; }

template <class T> T array<T>::get(int x)
{ return m_pbuff[x]; }


//Get() i set() funkcije smo mogli definirati inline unutar definicije klase. One su definirane
//izvan definicije klase kako bi se pokazalo da se tada uvijek ispred definicije funkcije
//mora napisati genericki prefiks: template <class T> .

int main () {
array<int> myints(5);
array<float> myfloats(5);
myints.set (0, 100);
myfloats.set(3, 3.1416);
cout << "myints ima: "
<< myints.size() <<" elemenata"<< '\n';
cout << myints[0]S<< '\n';
cout << myfloats[3]S<< '\n';
return 0;



}

Rezultat je:

myints ima: 5 elemenata

100

3.1416

Primjer:
Klasa pair služi kao kontenjer dva elementa koji mogu biti razlicitih tipova. Formirat cemo niz
takovih parova pomocu genericke klase array. U taj niz cemo upisati i iz njega ispisati niz parova
kojima prvi element predstavlja redni broj (tip int), a drugi element je kvadratni korijen prvoga (tip

float).

template <class T1, class T2>

class pair

{

T1 value1;

T2 value2;

public:

pair (T1 first=0, T2 second=0) {

value1=first; value2=second;

}

T1 GetFirst () {

return value1;

}

void SetFirst (T1 val) {

value1 = val;

}

T2 GetSecond () {

return value2;

}

void SetSecond (T2 val) {

value2 = val;

}

};

int main ()


{

// niz od 5 parova int,float

array <pair<int,float> > arr(5);

for(int i=0; i<arr.size(); i++)

{

arr[i].SetFirst(i); // prvi sadrži redni broj

arr[i].SetSecond(sqrt(i)); // kvadratni korijen prvog

}

for(int i=0; i<arr.size(); i++)

cout << arr[i].GetFirst() <<':'

<<arr[i].GetSecond()<< endl;

cout << endl;

return 0;

}
Rezultat je:

0:0
1:1
2:1.41421
3:1.73205
4:2
Važno je upozoriti na oblik deklaracije niza parova:

array <pair<int,float> > arr(5);

1. Vidimo da se konkretna realizacije generickog tipa može izvršiti i pomocu drugih generickih
tipova.
2. U deklaraciji se pojavljuju dva znaka > >. Izmedu njih obvezno treba napisati razmak, jer ako
bi napisali
array <pair<int,float>> arr(5); // greška : >>

kompajler bi dojavio grešku zbog toga jer se znak >> tretira kao operator. Da bi se izbjegle
ovakve greške, preporucuje se koristiti typedef deklaracije oblika:

typedef pair<int,float> if_pair;
array <if_pair> arr(5);


Ugniježdeni predlošci

Moguce je definirati predložak neke klase unutar druge klase.

template<class T1>
class X {
public:


template <class T2>

class Y {
void Funkcija();
T1 *pok;


};
};

Clan Funkcija() je parametriziran s dva tipa T1 i T2 i njena definicija izvan klase izgleda ovako:
template<T1> template<T2>
void X<T1>::Y<T2>::Funkcija() {
//Tijelo funkcije
}


Instanca tj. objekt klase Y ovisi o parametrima klase X i o prametrima klase Y:


X<char>::Y<Kompleksni> objekt;



Predlošci i nasljedivanje

Postoje tri nacina na koji se mogu nasljedivati predlošci:


Prvi nacin je kada predložak služi kao osnovni nacin za izvodenje konkretne klase koja
nije predložak. Osnovni razred mora biti parametriziran konkretnim tipom podatka. Na
primjer klasa SkupRijeci nasljeduje klasu Lista:
class SkupRijeci: Lista<char *> {
//Tijelo klase
};


• Drugi nacin je da se predložak klase izvodi iz neke obicne klase. Npr klasa
Listanasljeduje klasu Kontejner koja sadrži opca svojstva objekta koji skladišti druge
objekte:

class Kontejner {

public:
virtual int BrojElemenata()=0;
virtual void Prazni();

};

template <class Tip>

class Lista : public Kontejner {

//Tijelo klase

};

Sve varijante klase Lista imat ce podobjekt klase Kontejner koji nije parametriziran predloškom.


Treci nacin je u kojem se predložak izvodi iz predloška. Osnovni razred tj. Predložak
mora imati listu parametara. Na primjer klasa Lista može poslužiti za izvodenje klase
Stog:
template <class Tip>
class Stog : Lista<char> {
//Tijelo klase
};


Ovaj slucaj je identican prvom slucaju s time da je osnovna klasa sada predložak.



Specijalizacija predloška

Ponekad se s jednim predloškom ne mogu obuhvatiti svi slucajevi realizacije s razlicitim
tipovima. U tom se slucaju, za tipove kojima realizacija odstupa od predloška, može definirati
poseban slucaj realizacije koji nazivamo specijalizacija predloška.

Za klasu koju definiramo predloškom:

template <class opci_tip> identifikator {...}

specijalizacija za konkretni tip se zapisuje u obliku:

template <> class identifikator <konkretni_tip> {...}

Specijalizacija se smatra dijelom predloška, pa njena deklaracija zapocinje sa template <>.U
njoj se moraju definirati svi clanovi predloška, ali iskljucivo s konkretnim tipovima za koje se
vrši specijalizacija predloška. Podrazumijeva se da moraju biti definirani konstruktor i destruktor,
ukoliko su definirani u opcem predlošku.

To cemo demonstrirati banalnim primjerom. U klasi pair definirana je funkcija modul() koja daje
ostatak dijeljenja prvog s drugim elementom kada su oba elementa tipa int, a kada su elementi
drugih tipova funkcija modul() vraca vrijednost 0.

template <class T1, class T2> //opci predložak
class pair {
T1 value1;
T2 value2;
public:
....
int modul () {return 0;}
};
template <> // specijalizacija predloška
class pair <int,int> { // za tip int,int
int value1, value2;
public:
....
int modul () {return value1 % value2;}
};



Predlošci s konstantnim parametrima

Parametri predloška mogu biti i integralne konstante(int,char,long, unsigned). Primjerice,

template <class T, int N> class array {...}

je predložak za definiranje klase array pomocu generickog tipa T i integralne konstante N.
Vrijednost integralne konstante se mora specificirati pri deklaraciji objekta. Primjerice, s

array <float,5> myfloats;

deklarira se myfloats kao niz realnih brojeva kojem se dodatna svojstva zadaju s konstantom
N=5. U slijedecem primjeru koristit cemo ovaj konstantni parametar za postavljanje velicine niza.

template <class T, int N>

class array

{

T m_buff[N]; // niz od N elemenata

int m_size;

public:

array() : m_size(N) {}

int size() {return m_size;}

T & operator [ ]S(int i) {return m_buff[i];}

};

int main () {

array <int,5> myints;

array <float,5> myfloats;

myints[0]= 100;

myfloats[3]= 3.1416;

cout << "myints ima: "<< myints.size() <<" elemenata"<< '\n';

cout << myints[0]S<< '\n';

cout << myfloats[3]S<< '\n';

return 0;

}
Dobije se ispis:

100

3.1416


Predodredeni i funkcijski parametri predloška

U predlošcima se mogu koristiti predodredeni parametri. Primjerice, predloškom

template <class T=int, int N=10>
class array {...}


se omogucuju deklaracije oblika:


array<> a_int_10; // niz od 10 int
array<float> a_float_10; // niz od 10 float
array<char,100> a_char_100; // niz od 100 char


Napomena: predodredeni parametri se ne smiju koristiti u funkcijskim predlošcima.


Parametri predloška mogu biti funkcije:


template <int Tfunc (int)>
class
{...


y = Tfunc(x);
}

Definicija i upotreba klase tvector<class T>

Genericka klasa tvector predstavlja podskup standardne klase vector. Namjena joj je
manipuliranje s homogenim kolekcijama elemenata proizvoljnog tipa. Izvršit cemo specifikaciju i
implementaciju klase tvector.

Objekti tvector klase imaju slijedece karakteristike:


capacity() -kapacitet vektora je broj celija koje se automatski alociraju za spremanje
elemenata vektora,

size() -velicina vektora je broj elemenata koje stvarno sadrži vektor.

operator [ ]S-Pojedinom elementu vektora se pristupa pomocu indeksnog operatora, ili se
koriste dvije funkcije:


push_back(el) dodaje element na indeksu iza posljednje upisanog elementa, a

pop_back(el) briše posljednji element iz vektora.

Pri pozivu push_back() funkcije vodi se racuna o kapacitetu vektora i vrši se automatsko
alociranje potrebne memorije (memorija se udvostrucuje ako potrebna velicina vektora
premašuje trenutni kapacitet). Namjena ove dvije funkcije je da se vektor može koristiti
kao dinamicka kolekcija elemenata (kontejner).

• resize(n) -ako je potreban veci kapacitet vektora, on se može dobiti pozivom funkcije
resize(n).
Specifikacija ADT tvector

//************************************************** *
// template <class T> tvector {....}
// Genericki parametar tipa T mora zadovoljiti:
// (1) T ima predodredeni konstructor
// (2) za tip T definiran je operator =
//
// tvector( )
// POST: Kapacitet vektora == 0
//
// tvector( int size )
// PRED: size >= 0
// POST: kapacitet/velicina vektora == size
//
// tvector( int size, const T & Value )
// PRED:: size >= 0
// POST: Kapacitet/velicina == size, svi elementi se iniciraju na Value
//
// tvector( const tvector & vec )
// Opis: kopirni konstruktor
// POST: vector je kopija od vec
//
// ~tvector( )
// Opis: destruktor
//
// const tvector & operator = ( const tvector & rhs )
// POST: dodjela vrijednosti od rhs;
// ako se razlikuju velicine ova dva vektora
// vrši se promjena velicine na rhs.size()
//
// pristupnici
//
// int length( ) const
// int capacity( ) const
// POST: vraca broj celija alociranih za vektor



// int size( ) const
// POST: vraca stvarni broj elemenata u vektoru
// koristi se za push_back i pop_back funkcije
//
// indeksirani pristup i mutatori
//
// void push_back(const T& t);
// POST: umece element t na poziciji size()
// ako je size>= capacity dvostruko
// povecava kapacitet
//
// void pop_back();
// POST: odstranjuje element s pozicije size()
// i smanuje size za 1
//
// T & operator [ ]S( int k );
// const T & operator [ ]S( int k );
// Opis: Dobava k-tog elementa, uz kontrolu indeksa
// PRED: 0 <= k < capacity()
// POST: vraca k-ti element
//
// void resize( int newSize );
// Opis: promjena velicine vektora u newSize
// PRED: newSize >= 0,
// kapacitet je capacity, a velicina je size()
18
// POST: 1. size() == newSize;
// Ako je newSize > size() tada i
// capacity=newSize
// inace se kapacitet ne mijenja
// 2. za 0 <= k <= min(size(), newSize),
// vector[k]je kopija originala
// ostali elementi se iniciraju
// pomocu predodredenog T konstruktora
// Nota: ako je newSize < size(), gube se neki elementi
//
// void clear();
// POST: size = 0, capacitet nepromijenjen
//
// void reserve(int size);
// Opis: rezervira memoriju
// PRED: size >0
// POST: resize(size) ako je size > capacity()


Primjer deklaracije:

tvector<int> v1; // vektor s 0-elementa
tvector<int> v2(4); // vektor s 4-elementa
tvector<int> v3(4, 22); // vektor s 4-elementa -svi vrijednosti 22.


Primjer: Napišite program u kojem korisnik unosi proizvoljan niz brojeva. Kao kontejner brojeva
koristite tvector objekt. Nakon završenog unosa izracunajte sumu i srednju vrijednost unesenog
niza.


#include <iostream>
#include "tvector.h"
using namespace std;


int main()
{
tvector<double> vec;
double val;


// unos proizvoljnog niza brojeva u vektor vec
while (cin >> val)
vec.push_back(val);

// unos završava kada se otkuca neko slovo!!
//zatim racunamo sumu i srednju vrijednost


double sum = 0;
for (int i=0; i<vec.size(); i++)
sum += vec[i];


double avg =sum /vec.size();
cout <<"Suma od "<<vec.size()
<<" elemenata: "<< sum
<<". Srednja vrijednost:"<< avg << endl;
return 0;
}


Uocite: pri unosu podataka vektor koristimo kao kontejner – u njega odlažemo elemente
pomocu funkcije push_back. Kasnije vektor koristimo kao obican niz (elementima pristupamo
pomocu indeksa). Korištenje vektora je pogodnije od rada s obicnim nizovima je ne moramo
voditi racuna o alokaciji memorije.



2.12. Preopterecenje operatora
Preoptereceni operator definiramo isto kao i funkciju pa se on može koristiti kao funkcija
pomocu kljucne rijeci operator i oznake operatora cije se djelovanje definira tom funkcijom.

class Counter

{

private:
int m_count;
public:

//konstruktori i destruktor
//...
// pristupnici i mutatori
int get_count() {return m_count;};


// definiranje operatora ++, =, ==, < , <<


Counter& operator = (const Counter &);
Counter& operator++(); //prefiks inkrement
Counter& operator++(int unused); //postfiks inkrement
friend bool operator == (const Counter &c1, const Counter& c2);
friend bool operator < (const Counter &c1, const Counter& c2);
friend ostream& operator << (ostream &s, const Counter &c1);


};


Ovakvom specifikacijom klase Counter omogucuju se izrazi oblika:


Counter a;
++a;
Counter b = a++;
if(a==b) cout << "vrijednost oba brojaca je" << a;


2.12.1. Oblici operatorskih funkcija
Vecina operatorskih funkcija može biti deklarirana kao:

• nestaticka clanska funkcija ili kao
• neclanska globalna funkcija.
Primjerice, operator == može biti deklariran na više nacina:

1. deklaracije operatora == kao clanske nestaticke funkcije
class Counter
{
private:
int m_count;
public:



//...
int get_count() {return m_count;};


Counter & operator =(const Counter &);
bool operator ==(const Counter & d);


};


2. deklaracije operatora == kao globalne funkcije
bool operator == (const Counter& c1, const Counter& c2);

3. deklaracije operatora == kao prijateljske funkcije
class Counter
{
private:
int m_count;
public:
//...
int get_count() {return m_count;};
Counter & operator =(const Counter &);


friend bool operator == (const Counter& c1, const Counter& c2);

};

Izuzetak od ovog pravila su operatori

[],(),=,i ->

koji mogu biti deklarirani samo kao nestaticke clanske funkcije.
Isto vrijedi i za složene operatora tipa

+=, *= itd.


Preopruceni oblici operatorskih funkcija.
Operator je simbolicki oznacen s @, a objekti i klase s a,bi c:
a je objekt klase A, b je objekt klase B, a c je objekt klase C.


Izraz Operator (@) Clanska funkcija
Globalna
funkcija
@a + -* & ! ~ ++ -A::
operator@() operator@(A)
a@ ++ -A::
operator@(int) operator@(A, int)
a@b
+ -* / % ^ & | < > == != <= >= << >>
&& || ,
A::operator@(B) operator@(A, B)
a@b = += -= *= /= %= ^= &= |= <<= >>= [ ]SA::operator@(B) -
a(b,c..) () A::operator()(B, C..) -
a->b -> A::operator->() -

2.12.2. Sucelje operatora
Sucelje operatora se opisuje tipom argumenata i tipom rezultata operatorske funkcije.
Preporucljivo je koristiti sucelje opisano u sljedecoj tablici.

Tip operatorske funkcije
Vraca
Prijenos argumenata
const
funkcija
Aritmeticki (+, -, *, /) vrijednost const referenca da
Pridjela vrjednosti (=, +=, -=) referencu (*this) const referenca ne
Relacijski (<, ==) vrijednost (bool) const referenca da
Unarni prefiks (++, --) referencu (*this) -ne
Unarni postfiks (++, --) vrijednost vrijednost (int dummy) ne
Unarni -i + vrijednost -da
Indeksni [ ]Sreferencu vrijednost (int) ne
Funkcijski () po volji po volji ne

Implementacija sucelja je u nadležnosti programera.
Pri izradi sucelja i implementacije treba voditi racuna:


da se poštuju pravila asocijativnosti i prioriteta djelovanja operatora kakovi vrijede za
proste skalarne tipove,
t2+t1/t2 se uvijek interpretira kao t2+(t1/t2)


da primjena operatora zadovoljava kontekst izraza u kojim oni koriste (glupo bi bilo dati
znacaj operatoru koji se razlikuje od znacaja kojeg ima u C jeziku, iako je to dopušteno).

Primjer: operator ==.

To je binarni i simetricni operator. Njime se usporeduje dva istovrsna operanda, i dobija rezultat
tipa bool. Simetricnost je u tome da operandi mogu zamijeniti mjesto. Ovo svojstvo implicira da
se treba deklarirati friend operatorsku funkcija, jer ako se operator deklarira pomocu clanske
funkcije tada se dobija asimetricnost u izvršenju izraza.

class Counter

{

private:

int m_count;

public:

//...

// 1 – asimetricna deklaracija ne zadovoljava primjenu

// bool operator ==(const Counter &);

// 2 – simetricna deklaracija zadovoljava primjenu operatora
friend bool operator == (const Counter& c1, const Counter& c2) const;

};

//// implementacija

bool operator == (const Counter &c1, const Counter& c2)

{

return c1.m_count == c2.m_count;

}


Primjer: operator pridjele vrijednosti =.

Njime se mijenja lijevi operand a desni ostaje nedirnut. Sucelje mora izrazitu tu cinjenicu.
Povoljna je deklaracija clanskom funkcijom jer imamo asimetricno djelovanje operatora.
Argument funkcije može biti const referenca. Funkcija mora vratiti referencu kako bi se mogli
koristiti izrazi oblika:

a = b= c;

To je ocito ako napišeno operatorski oblik:
a.operator=(b.operator=(c));

vidimo da b.operator=(c) mora vratiti referencu jer je to argument funkcije a.operator=(...).
Implementacija se realizira tako da funkcija operator= vraca dereferencirani this pokazivac, tj.

Counter& Counter::operator=(const Counter& c)
{
m_count = c. m_count;
m_mod = c. m_count;
return *this;
}


Da zakljucimo,

sucelje operatora se mora realizirati tako da se osigura slicnost s djelovanjem tog operatora na
standardne tipove. Implementcija je u nadležnosti programera.

2.12.3. Restrikcije
Mogu se preopteretiti slijedeci operatori:

new delete new[ ]Sdelete[ ]S
+-*/%^&| ~!=<>
+= -= *= /= %= ^= &= |= << >> >>= <<= ==
!= <= >= && || ++ --, ->* -> () []S


s tim da se operatori: +, -, * i & mogu koristiti u unarnom i binarnom obliku.



Ne smije se mijenjati znacaj slijedecih operatora:

• Tocka operator .
• operator indirekcija pokazivaca na clana klase .*
• operator dosega ::
• operator sizeof
zbog toga jer oni djeluju na ime a ne na objekt.

Ne smije se mijenjati znacaj ternarnog uvjetnog operatora ?:.

Ne smije se mijenjati znacaj novih type cast operatora: static_cast<>, dynamic_cast<>,
reinterpret_cast<>, i const_cast<> .

Ne smije se mijenjati znacaj # i ## predprocesorkih simbola.

Za operatorske funkcije vrijede pravila naslijedivanja kao i za ostale clanske funkcije. Izuzetak je
jedino operator pridjele vrijednosti, koji je uvijek definiran za svaku klasu (ako definiranje
operatora pridjele vrijednosti ne izvrši programer, tada to implicitno obavlja sam kompajler).

Ne smije se izmišljati nove operatore. Primjerice, nedozvoljeno je deklarirati znak @ kao
operator:

void operator @ (int); // nije dozvoljeno

Broj argumenata mora odgovarati broju argumenata koji se koriste u standardnim izrazima.

Za razliku od obicnih funkcija, u operatorskim deklaracijama se ne smiju koristiti predodredeni
parametri. Izuzetak je jedino operator ().


2.12.4. Postfiks i Prefiks Operatori
Operatori --i ++ mogu se koristiti kao prefiks i postfiks operatori. Da bi se mogla razlikovati
definicija postfiks i prefiks djelovanja ovih operatora, pravilo je da se postfiks operatori
deklariraju s jednim argumentom tipa int, iako se taj argument ne koristi, a prefiks operatori
se deklariraju bez argumenata. Primjerice, za klasu Counter se inkrement operator definira na
slijedeci nacin:

class Counter {
int m_count;
public:

Counter & operator++(); //prefiks

Counter operator++(int unused); //postfiks
...
};

// primjena


Counter c1, c2;
//prefiks: prvo inkrementira c2, i pridjeljuje ga c1
c1 = ++c2;


//postfiks; prvo pridjeli c2 u c1 pa inkrementiraj c2
c1 = c2++;


//implementacija


const Counter& Counter::operator++() // prefiks
{
++m_count;; // inkrementiraj brojac
return *this; // vrati njegovu referencu
}
const Counter Counter::operator++(int unused) // postfiks
{
Counter temp(*this); // zapamti stanje u temp
++ m_count;; // inkrementiraj brojac
return temp; // vrati temp objekt
}



Uocite da se u postfiks verziji vraca referenca, a u prefiks verziji se vraca vrijednost. Mogli smo
obje verzije napisati tako da se vraca void. U tom slucaju inkrementiranje brojaca bi se moglo
izvršiti samo u prostim naredbama tipa:

++c; ili c++;

Kada koristiti povrat referenca, a kada povrat vrijednosti objekta? U principu treba koristiti povrat
reference (*this) uvijek kada je to moguce. U postfiks verziji operatora ++ to nije moguce jer *this
predstavlja referencu inkrementirane vrijednosti, a funkcija mora vratiti prethodnu vrijednost
objekta.

2.12.5. Korištenje operatora kao normalnih funkcija
Counter c1, c2;
bool equal;
c1.operator++(0); // ekvivalentno: c1++;
c1.operator++(); // ekvivalentno: ++c1;
equal = operator==(c1, c2); // ekvivalentno: equal = c1==c2;


2.12.6. Višestruko preopterecenje operatora
Normalne funkcije se mogu višestruko preopteretiti na nacin da se definira više verzija s
razlicitim parametrima. Isto vrijedi i za operatore, preopterecenje može biti višestruko.


Tri verzije operatora == za klasu Counter


bool operator == (const Counter &c1, const Counter& c2);
bool operator == (const Counter &c, int i);
bool operator == (int i, const Counter& c);


U tom slucaju moguce je usporedivati vrijednost brojaca s cjelobrojnom varijablom.


Na samom pocetku ovog poglavlja definirana je friend funkcija:


ostream & operator << (ostream & s, const Counter &c1)

{

return s << c1.get_count(); // ostream objekt vraca referencu

}

koja omogucuje da se operatorom << usmjerava vrijednost brojaca na izlazni tok. Primjerice,

Counter c;

cout << c;


ispisuje vrijednost brojaca na standardnom izlazu. Funkcija vraca referencu na ostream objekt.
To je nužno za korištenje izraze oblika
Counter a, b, c;
cout << a << b << c;

Operatori << i >> su vjerojatno najoptereceniji operatori, jer se definiraju u vecini klasa koje
komuniciraju s ulazno/izlaznim tokovima.

2.12.7. Preopterecenje operatora pobrojanih tipova
Pobrojani tip obuhvaca skup konstanti. Cilj nam je da se pomocu operatora ++ i --može
iterativno mijenjati vrijednosti iz definiranog skupa konstanti.
Primjer:


enum Days{
Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday
};
Days& operator++(Days& d, int) // postfix ++
{


if (d == Sunday)
return d = Monday;
int temp = d; //pretvori u int
return d = (Days) (++temp);


}
int main()
{
Days day = Monday;
for (;;) //prikaz days kao cjelobrojnih vrijednosti
{
cout<< day <<endl;
day++;
if (day == Sunday)
break;
}
return 0;
}



Ako želimo da se simbolicke konstante ispisuju svojim imenom, a ne numerickom vrijednošcu,
tada se može preopteretiti << operator;

ostream& operator<<(ostream& os, Days d)
{
switch(d){
case Monday: return os<<"Monday";
case Tuesday: return os<<"Tuesday";
case Wednesday: return os<<"Wednesday";
case Thursday: return os<<"Thursady";
case Friday: return os<<"Friday";
case Saturday: return os<<"Saturday";
case Sunday: return os<<"Sunday";
default: return os<<"Unknown";
}
}


2.12.8 Klasa intArray i preopterecenje indeksnog operatora [ ]S
Kada klasa služi za apstrakciju niza elemenata poželjno je definirati indeksni operator [ ]Spomocu
kojeg se pristupa elementima niza. Poželjno je uvijek definirati dvije verzije operatora [ ]: const
verziju i ne-const verziju. Primjerice, definirat cemo klasu iarray koja nam može poslužiti za rad s

cjelobrojnim dinamickim nizovima.
const int def_iarray_size = 5;
class iarray // niz cjelobrovnih elemenata
{
int *m_arr; // pokazivac
na memoriju koja sadrži niz
int m_size; // broj elemenata niza
void init(const int *array, int size); // pomocna privatna funkcija
public:
iarray(int def_size = def_iarray_size) {
init((const int *) 0, def_size);
}
iarray(const iarray& rhs) // kopirni konstruktor
{
init(rhs.m_arr, rhs.m_size);
}
iarray(const int *array, int size) // konstruktor pomocu postojeceg
{ init(array, size); // int * niza
}
~iarray(void) { delete [ ]Sm_arr; } // destruktor


// pridjela vrijednosti

iarray& operator=(const iarray&);
int size() const {return m_size;}

// indeksni operator – const i non-const verzija

int& operator[ ](int index) { return m_arr[index]; }
const int& operator[ ](int index) const { return m_arr[index]; }
};
void iarray::init(const int *array, int size)
{


// alociraj memoriju velicine m_size cijelih brojeva

m_size = size;
m_arr = new int [size];

//inicijaliziraj niz
if (array != 0)
for (int index=0; index<size; index++)
{

marijanovic
18-11-2007, 14:33
m_arr[index]S= array[index];
}

}

iarray& iarray::operator=(const iarray& rhs)

{
if (this != &rhs)
{

// ako se objekti razlikuju
// dealociraj postojecu memoriju


delete [ ]Sm_arr;

// zatim alociraj i kopiraj rhs to lhs

init(rhs.m_arr, rhs.m_size);
}
return *this; // vrati referencu
}


Primjena:
iarray a;
a[0]S= 1;
for(int i = 1; i < a.size(); i++)
a[i]S= 7;

2.12.9 Operatori pretvorbe tipova
Opci oblik deklaracije operatora pretvorbe tipova je:

class Aclass {
....
public:
operator tip () {...}; // operator pretvorbe u tip
}


Deklaracija operatora pretvorbe tipova se razlikuje od uobicajenih deklaracija u dvije stvari.

1. ne deklarira se vrijednost funkcije, vec
deklaracija zapocima s rijecju operator,
2. funkcija pretvorbe parametara se definira bez parametara.
Primjerice, može nam biti korisno da se u nekim izrazima objekti tipa Counter tretiraju kao
cjelobrojne varijable koje sadrže vrijednost brojaca. To smo do sada realizirali na nacin da smo
koristili clansku funkciju get_count().

int square(int i) {return i*i;}
int main() {
Counter c;
c++;
int i = square(c.get_count());
....


Ako bi u klasi Counter definirali operator pretvorbe u tip int,
class Counter
{
int m_count;
public;


...

operator int () {return m_count;}

};
tada bi bilo moguce prethodni insert programa zapisati u obliku:

int square(int i) {return i*i;}

int main() {

Counter c;

c++;

int i = square(c); //c se tretira kao int

....

jer kompajler pri pozivu funkcije square raspolaže s operatorom pretvorbe objekta klase Counter
u vrijednost tipa int.

Ova tehnika programiranja je dosta kritizirana u akademskim krugovima, jer se njome zaobilazi
stroga kontrola tipova.

2.12.10. Funkcijski objekti
Funkcijski objekt se implementira kao klasa u kojoj je definiran operator poziva funkcije ().

class Fclass {

....

public:

tip operator () (parametri);

}

Objekti ove klase mogu se upotrebljavati kao funkcije:

Fclass Fobj;
Fobj(argumenti ..) // funkcijski objekt – vrši poziv funkcije


Regularne funkcije mogu imati proizvoljan broj argumenata, stoga je operator () iznimka medu
operatorskim funkcijama, jer se može definirati s proizvoljnim brojem argumenata ukljucujucii
predodredene parametre.


U slijedecem primjeru definirana klasa increment. Pomocu nje je definiran funkcijski objekt incr.
Koristimo ga kao obicnu funkciju (vidimo da incr(n) predstavlja poziv funkcije definirane u
klasom increment).

class increment

{

// generic increment function

public :

int operator() (int x) const { return ++x;}

};

void f(int n, const increment& incr)
{ cout << incr(n) << endl;
}
int main()
{
int i = 0;
increment incr;
f(i, incr); // ispisuje 1
return 0;


}

Koncept preopterecenja operatora je temeljni element implementacije apstraktnih tipova
podataka.


Preoptereceni operatori uzrokuju iste efekte kao clanske i globalne funkcije: mogu se
naslijediti i mogu se višestruko preopteretiti.

Preoptereceni operator može imati fiksni broj parametara, s tim da se ne smije deklarirati
predodredene parametre (izuzetak su funkcijski objekti).

Pravila asocijativnosti i prioriteta se preuzimaju iz pravila koji vrijede za izraze s
standardnim tipovima.

Preporucuje se da kontekst primjene preopterecenih operatora odgovara kontekstu
primjene standardnih operatora.

Specijalni tip preopterecenih operatora su operatori pretvorbe tipova. Za njih vrijede
posebna pravila; ne vracaju vrijednost i ne koriste argumente.

2.13. Prihvat i generiranje iznimki -Exception handling
2.13.1. try-catch-throw
Iznimke su situacije u kojima program ne može nastaviti normalan rad. Potrebno je prekinuti
izvodenje programa te izvodenje prenijeti na neku rutinu koja ce obraditi pogrešku. Pogreške
mogu nastati ako se dijeli s nulom, ponestane memorije i tako dalje. Rješenje problema je
mehanizam za rukovanje iznimkama. Ideja je da se program podijeli na dio u kojem može
nastati iznimka i na dio u kojem se on nastavlja kada nastane iznimka. Kljucna rijec
try
oznacava blok programa u kojem može nastati iznimka, a kljucna rijec
catch oznacava dio
programa koji ce se izvršiti kada nastane neka iznimka (Za catch-blok se cesto koristi naziv
exception-handler). Uz kljucnu rijec
catch u zagradama se navodi parametar koji oznacava
tip iznimke. Neke iznimke generira izvršni sustav C++ kompajlera, a neke može generirati
korisnik koristeci kljucnu rijec
throw.

Pojednostavljeno se ovaj mehanizam može zapisati u obliku:

class Iznimka { } // može biti "prazna klasa"
try { // program koji pokušavamo izvršiti
if (neka_greška) throw Iznimka();

// iznimku može generirati i izvršni sustav
}

catch (Iznimka){
// dio programa koji se izvršava kada korisnik
// generira iznimku tipa Iznimka
}

catch (std:exception& e){
// dio programa koji se izvršava kada nastane
// iznimka koju generira standardne biblioteka
}

catch (...){
// dio programa koji se izvršava za sve ostale
// iznimke
}


Primjer: U prvom primjeru pokazat cemo kako se generiraju uznimke koje u catch-blok šalju
vrijednost prostih tipova.

try {
int n;
char *mystring = new(nothrow) char [10];
if (mystring == NULL)

throw "Greska alokacije memorije!";
cout << "Unesi cijeli broj: ";
cin >> n;
if (n>9)


throw n;
else
mystring[n]='z';
}

catch (int i) {
cout << "Iznimka: ";
cout <<"indeks "<<i
<<" izvan dozvoljenog intervala!"
<< endl;
}

catch (char * str) {
cout << "Iznimka: " << str << endl;
}

Rezultat:
Unesi cijeli broj: 99
Iznimka: indeks 99 izvan dozvoljenog intervala!


Svaki catch blok hvata samo iznimke odredenog tipa. Prvi catch blok hvata iznimku tipa int, a
drugi blok iznimku tip string.



Primjer: Pokazat cemo kako se koriste korisnicki definirane klase kao parametri catch-bloka.
Klasu Range_error koristit cemo za generiranje iznimke kada se prekoraci neki dozvoljeni
cjelobrojni interval:

class Range_error {

public:
Range_error(int i) :m_i(i) { }
int m_i;

};

Pretpostavimo da smo definirali funkciju int2char() koja vraca znakovnu vrijednost ako je
cjelobrojni argument unutar intervala [0,255], u suprotnom generira se iznimka Range_error.

char int2char(int i) {
if(i < 0 || i>255) throw Range_error(i);
return (char) i;
}

Pri korištenju ove funkcije moguca su dva nacina prihvata iznimke. Prvi nacin je da catch ne
koristi argument:


try {
char c = int2char (600);
}
catch(Range_error) {
cout << "int2char: nedozvoljen argument";
}


Drugi je nacin da se catch specificira s formalnim parametrom x, pa tada može primati
argumente:


try {
char c = int2char (600);
}
catch(Range_error x) {
cout << " int2char: nedozvoljen argument "


<< x.m_i << endl;
}


2.13.2. Neprihvacene iznimke
Ako nastane iznimka za koju nije osiguran prihvat ( i ako ne postoji catch(...) ) tada se poziva
specijalna funkcija terminate(), kojom se prekida trenutni proces (program). Zapravo,
terminate je pokazivac
na funkciju kojem je predodredena vrijednost na adresu standardne C
funkciju abort( ), kojom se prekida program. To znaci da nece biti pozvani destruktori
globalnih i statickih objekata, pa se ovakav tip neprihvacenih iznimaka smatra
programerskom pogreškom. Moguce je da korisnik sam definira funkciju za prihvat ovakovih
iznimki. To se vrši pomocu funkcije set_terminate(), koja vraca pokazivac
na funkciju
terminate(), a prima kao argument void funkciju koja nema argumenata i koja ce zamijeniti
funkciju terminate().

Ugniježdeni try-catch

Try-catch-blokovi mogu biti ugniježdeni. Primjerice:

try {

try {

// neki kod
}
catch (int n) {

throw; // preusmjeri na vanjski catch-blok
}
}

catch (...) {
cout << "Iznimka";
}

U slucaju ugniježdenih blokova moguce je usmjeriti iznimku iz unutarnjeg catch bloka u
vanjski catch block. U ovom primjeru smo u tu svrhu u unutarnjem bloku koristili throw bez
argumenta. Preporuka je da se ne koriste ugniježdeni try-catch blokovi.

Grupiranje iznimki

Cesto se iznimke mogu logicno grupirati. Primjerice, za rad s matetematickim operacijama
mogli bi definirati sljedece:

class MathErr() {}; // greska u mat. operacijama
class ZeroDevide: public MathErr {}; // dijeljenje s nulom
class Overflow: public MathErr {}: // prekoracen maksimum
void f()
{

try {
//matematicke operacije
}

catch(ZeroDevide) {

// dijeljenje s nulom
}
catch(MathErr){

// prihvaca sve ostale mat. greske
//osim dijeljenja s nulom
}
}


U ovom slucaju se koristi naslijedivanje za definiranje objekta identifikacije iznimke. Zbog
toga, u ovom primjeru se dijeljenje s nulom prihvacau catch(ZeroDevide), a ostale
matematicke iznimke u catch(MathErr) ukljucujuci i Overflow (jer se Overflow izvodi iz
MathErr).

Deklaracija funkcija s iznimkama

Kada deklariramo neku funkciju

int f ();

nije poznato da li se u njoj koristi ili ne koristi mehanizam generiranja iznimki.

int f() throw();

znaci da se u funkciji f() ne generiraju iznimke, a

int f() throw(MarhErr);

znaci da se u funkciji f() generira iznimka MathErr.

Standardne iznimke

Standardne iznimke generiraju se iz koda koji stvara sam C++ kompajler ili iz koda
standardne biblioteke. Deklarirane su pomocu temeljne klase exception, koja je deklarirana u
zaglavlju <exception>:

class exception {

public:
exception() throw();
exception(const exception&) throw();
exception& operator = (const exception&) throw();
virtual ~exception() throw();
virtual const char * what() const throw();

private:
//
}
catch(bad_alloc)
{
// greska alokacije
}
catch (std::exception & e) // ostale izvršne greške
{
cout << "Exception: " << e.what();
}




Dozvoljeno kopiratri, ETF Osijek.
http://beta.etfos.hr/nastava/predmet_obavijesti_detalji.php?id=444&predmet=PR301