You are on page 1of 27

12.

Generičke funkcije i klase - predlošci

Naglasci:
 generičke funkcije
 generičke klase
 specijalizacija predložaka
 generička klasa vector
 generička klasa stack
 kompozicija objekata – “has a” veza objekata

Pod pojmom generičkog programiranja podrazumijeva se izrada programskog koda koji se u


nepromijenjenom obliku može primijeniti na različite tipove podataka. U C++ jeziku mogu se
definirati generičke funkcije i klase.

12.1. Generičke funkcije

U C jeziku se za generičko programiranje koriste predprocesorske makro naredbe. Primjerice,


#define Kvadrat(x) x*x
je makro naredba kojom se dio koda koji je u zagradama označen s x zamjenjuje s x*x. Makro naredbe
nisu funkcije. One se realiziraju u toku kompajliranja leksičkom zamjenom teksta. Primjerice

makro naredba izvršava se kao


int i; int i;
Kvadrat(i) * 3; i*i*3
float x; float x;
x*Kvadrat(6.7) x*6.7*6.7
float x; float x;
x = Kvadrat(x + 6.7) x = x + 6.7 * x + 6.7 //greška

U prva dva primjera pokazano je kako se ista makro naredba koristi za tipove int i float.
U trećem primjeru je ilustrirana najčešća pogreška kod korištenja makro naredbi ( uočite da će se zbog
pravila prioriteta prije izvršiti množenje 6.7*x nego predviđeno zbrajanje). Ova se greška može
izbjeće tako da se “argumenti” makro naredbe napišu u zagradama, tj.

#define Kvadrat(x) (x)*(x)

U C++ se isti efekt može postići korištenjem definiranjem inline funkcija:

inline int Kvadrat(int x) {return x*x;}


inline float Kvadrat(float x) {return x*x;}
inline double Kvadrat(double x) {return x*x;}

Preporuka je da se radije koriste inline funkcije nego makro naredbe, jer se time osigurava bolja
kontrola tipova i izbjegava mogućnost pojave prethodno opisane pogreške.

12. Generičke funkcije i klase 1


12.1.1 Predložak generičkih funkcija
Primjetimo da sva tri tipa definicije funkcije Kvadrat imaju isti oblik:
T Kvadrat(T x) {return x*x;}
gdje T može biti int, float ili double. Ova definicija ima generički oblik. U C++ jeziku se
može vršiti generičko definiranje funkcija, na način da se ispred definicije ili deklaracije funkcije,
pomoću ključne riječi template, označi da T predstavlja “generički tip”. Riječ template znači
predložak. Općenito, sintaksa zapisa generičke funkcije pomoću predloška je:

template <class generički_tip> definicija_funkcije


ili
template <typename generički_tip> definicija_funkcije

Razlika u ova dva zapisa je u upotrebi ključne riječi class ili typename, ali značenje je potpuno isto.
Ključna riječ typename je tek odnedavno u upotrebi. Generički tip se označava imenom. Primjerice,
generički oblik funkcije kvadrat je:

template <class T> T Kvadrat(T x) {return x*x;}


ili
template <typename T> T Kvadrat(T x) {return x*x;}

Pošto se izvršenje funkcije mora obaviti sa stvarnim tipovima, nužno je pri pozivu funkcije navesti tip
podatka za kojeg se treba izvršiti poziv funkcije. Opći oblik poziva generičke funkcije je:
ime_funkcije<tip> (parameteri);
Primjerice, poziv funkcije Kvadrat za cjelobrojne tipove je:
int x,y;
y = Kvadrat<int>(x);
Očito je da kompajler tek u trenutku poziva funkcije može odrediti način kako se prevodi generička
funkcija, stoga su generičke funkcije po upotrebi ekvivalentne makro naredbama.

Zapamti: da bi se moglo izvršiti kompajliranje programa u kojem se koristi


generička funkcija, kompajleru mora biti na raspolaganju potpuna definicija
funkcije, a ne samo deklaracija funkcije kao što je slučaj kod upotrebe
regularnih funkcija.

Kompletni primjer je dan u programu kvadrat1.cpp


//datoteka: kvadrat1.cpp
#include <iostream.h>

template <class T> T Kvadrat(T x)


{
T result = x*x;
return result;
}

int main ()
{
int i=5, k;
float a=10.9, b;
k=Kvadrat<int>(i);
b=Kvadrat<float>(a);

12. Generičke funkcije i klase 2


cout << k << endl;
cout << b << endl;
return 0;
}
Ispis je:
25
118.81
U ovom primjeru pozivamo funkciju Kvadrat s dva tipa argumenata: int i float. Uočite da je
funkcija Kvadrat napisana nešto drukčije. Cilj je bio pokazati da se generički tip T može koristiti
unutar funkcije za oznaku tipa lokalne varijable
T result;
U datoteci kvadrat2.cpp pokazano je da se ne mora pri pozivu funkcije specificirati generički tip ako
se iz tipova argumenata nedvosmisleno može zaključiti koji se parametrički tip koristi.
//datoteka: kvadrat2.cpp
#include <iostream.h>

template <class T> T Kvadrat(T x)


{
return x*x;
}

int main ()
{
int i=5, k;
float a=10.9, b;
k=Kvadrat(i);
b=Kvadrat(a);
cout << k << endl;
cout << b << endl;
return 0;
}
Preporuka je da se uvijek eksplicitno specificira parametrički tip. To ilustrira primjer u datoteci
kvadrat3.cpp.
//datoteka: kvadrat3.cpp
#include <iostream.h>

template <class T> T Kvadrat(T x)


{
return x*x;
}

int main ()
{
int i=5, k;
float a=10.9, b;
k=Kvadrat<int>(a); // kompajler vrši pretvorbu float u int
b=Kvadrat<float>(i); // kompajler vrši pretvorbu int u float
cout << k << endl;
cout << b << endl;
return 0;
}
Rezultat je:
100
25

12. Generičke funkcije i klase 3


U naredbi k=Kvadrat<int>(a); kompajler najprije generira kod funkcije Kvadrat s int
argumentom. Zatim, pošto je argument a tipa float, vrši pretvorbu float u int (vrijednost 10.9
pretvara u 10). Zbog toga je rezultat cjelobrojna vrijednost 100. Ova pretvorba ne bi bila moguća da je
naredba bila napisana bez specifikacije parametričkog tipa: k=Kvadrat(a) . Tada bi kompajler javio
grešku, tj. da se ne može izvršiti implementacija funkcije Kvadrat jer su nekompatibilni tipovi
argumenta i povratne vrijednosti.

Napomenimo još da se mogu definirati generičke funkcije s više generičkih tipova. Primjerice.
template <class T, class U> T GetMin (T a, U b)
{
return (a<b?a:b);
}
definira funkciju s dva argumenta kojima se generički tipovi T i U. Moguće je izvršiti poziv funkcije
na slijedeći način:
int i,j;
long l;
i = GetMin<int,long> (j,l);
ili još jednostavnije
i = GetMin (j,l);
Iako su j i l različitog tipa, kompajler sam vrši ugrađene integralne pretvorbe tipova.

12.2. Generičke klase


12.2.1 Predložak generičke klase
Na isti način kao što se definiraju generičke funkcije mogu se definirati i generičke klase. Definicija
članova klase se provodi pomoću generičkog tipa. Primjerice, u datoteci arrtempl1.cpp definirana je
generička klasa array:
template <class T> class array
{.... }
pomoću koje se može realizirati nizove proizvoljnog tipa. Primjerice, deklaracijom
array<int> myints(5);
se formira niz od 5 cjelobrojnih elemenata

//datoteka arrtempl.cpp
#include <iostream.h>

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 [] m_pbuff;}
int size() {return m_size;}
void set(int x, T value);
T get(int x);
T & operator [] (int i) {return m_pbuff[i];}
const T & operator [] (int i) const {return m_pbuff[i];}
};

12. Generičke funkcije i klase 4


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]; }

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] << '\n';
cout << myfloats[3] << '\n';
return 0;
}
Rezultat je:
myints ima: 5 elemenata
100
3.1416
Elementima niza se pristupa pomoću set/get funkcija ili pomoću indeksne notacije. Get() i set()
funkcije smo mogli definirati inline unutar definicije klase. One su pak definirane izvan definicije
klase kako bi se pokazalo da se tada uvijek ispred definicije funkcije napisati generički prefiks:
template <class T> .
U slijedećem primjeru definirat ćemo klasu pair koja će nam služiti kao kontenjer dva elementa koji
mogu biti različitih tipova. Formirat ćemo niz takovih parova pomoću generičke klase array. U taj
niz ćemo upisati i iz njega ispisati niz parova kojima prvi element predstavlja redni broj (tip int), a
drugi element je kvadratni korijen prvoga (tip float).
//datoteka pair-array.cpp
#include <iostream>
#include <cmath>
using namespace std;

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 [] m_pbuff;}
int size() {return m_size;}
T & operator [] (int i) {return m_pbuff[i];}
const T & operator [] (int i) const {return m_pbuff[i];}
};

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;}
};

12. Generičke funkcije i klase 5


int main ()
{
array <pair<int,float> > arr(5); // niz od 5 parova int,float

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


{
arr[i].SetFirst(i); // prvi sadrži redni broj, a drugi
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 generičkog tipa može izvršiti i pomoću drugih generičkih tipova.
2. U deklaraciji se pojavljuju dva znaka > >. Između 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 ovakove greške, preporučuje se koristiti typedef deklaracije oblika:


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

12.2.2 Specijalizacija predloška


Ponekad se s jednim predloškom ne mogu obuhvatiti svi slučajevi realizacije s različitim tipovima. U
tom se slučaju, za tipove kojima realizacija odstupa od predloška, može definirati poseban slučaj
realizacije koji nazivamo specijalizacija predloška.
Za klasu koju definiramo predloškom:
template <class gen_tip> identifikator {...}
specijalizacija za konkretni tip se zapisuje u obliku:
template <> class identifikator <konkretni_tip> {...}
Specijalizacija se smatra dijelom predloška, stoga njena deklaracija započima sa template <>. U njoj
se moraju definirati svi članovi predloška, ali isključivo s konkretnim tipovima za koje se vrši
specijalizacija predloška. Podrazumijeva se da moraju biti definirani konstruktor i destruktor, ukoliko
su definirani u općem predlošku.
To ćemo demonstrirati banalnim primjerom u programu pair-spec.cpp. U klasi pair definirana je
člansku funkciju modul() koja daje ostatak dijeljenja prvog s drugim elementom kada su oba
elementa tipa int, a kada su elementi drugih tipova funkcija modul() vraća vrijednost 0.

12. Generičke funkcije i klase 6


//datoteka: pair-spec.cpp
#include <iostream.h>

template <class T1, class T2> //opći predložak


class pair {
T1 value1;
T2 value2;
public:
pair (T1 first=0, T2 second=0)
{value1=first; value2=second;}
....
int modulo () {return 0;}
};

template <> // specijalizacija predloška


class pair <int,int> {
int value1, value2;
public:
pair (int first, int second)
{value1=first; value2=second;}
....
int modulo () {return value1 % value2;}
};

int main () {
pair <int,int> myints (100,75);
pair <float,int> myfloats (100.0,75);
cout << myints.modulo() << '\n';
cout << myfloats.modulo() << '\n';
return 0;
}
Dobije se ispis:
25
0

12.2.3 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 pomoću generičkog tipa T i integralne konstante N. Vrijednost
integralne konstante se mora specificirati pri deklaraciji objekta. Primjerice, s
array <float,5> myfloats;
deklarira se myfloat kao niz realnih brojeva kojem se dodatna svojstva zadaju s konstantom N=5. U
slijedećem primjeru koristit ćemo ovaj konstantni parametar za postavljanje veličine niza.

//datoteka arrtemp2.cpp
#include <iostream.h>

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 [] (int i) {return m_buff[i];}

12. Generičke funkcije i klase 7


const T & operator [] (int i) const {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] << '\n';
cout << myfloats[3] << '\n';
return 0;
}

Dobije se ispis:
100
3.1416

12.2.4 Predodređeni i funkcijski parametri predloška


U predlošcima se mogu koristiti predodređeni parametri. Primjerice, predloškom
template <class T=int, int N=10> class array {...}
se omogućuju 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: predodređeni parametri se ne smiju koristiti u funkcijskim predlošcima.

Parametri predloška mogu biti funkcije


template <int Tfunc (int)>
class X
{...
y = Tfunc(x); !!!
...
}

12.2.5 Predlošci i makro naredbe


Predloške možemo smatrati inteligentnim makro naredbama. Primjena predložaka ima nekoliko
prednosti u odnosu na maro naredbe:
 Lakše ih je shvatiti jer predlošci izgledaju kao regularne klase (ili funkcije).
 U razvoju predloška lakše se provodi testiranje s različitim tipovima.
 Kompajler osigurava znatno veću kontrolu pogreški nego je to slučaj s makro naredbama.
 Funkcijski predlošci imaju definiran doseg, jer se mogu definirati kao dio klase (friend) ili
namespace.
 Funkcijski predlošci mogu biti rekurzivni.
 Funkcijski predlošci se mogu preopteretiti.

Pri kompajliranju neke datoteke kompajler mora raspolagati s potpunom implementacijom predloška.
Stoga je uobičajeno da se specifikacija i implementacija funkcija zapisuje u istoj “.h” datoteci.

Makro karakter predložaka se posebno očituje kod klasa koje imaju statičke članove. U tom slučaju se
za svaku realizaciju predloška inicira posebna statička varijabla (kod regularnih se klasa za sve objekte
jedne klase generira samo jedna statička varijabla).

12. Generičke funkcije i klase 8


12.3 Definicija i upotreba klase tvector<class T>
Generička klasa tvector predstavlja podskup standardne klase vector. Namjena joj je da njome
manipuliramo s homogenim kolekcijama elemenata prozvoljnog tipa. Izvršit ćemo specifikaciju i
implementa klase tvector temeljem znanja koja smo stekli razmatrajući generičku klasu array i klasu
TString.

Objekti tvector klase imaju slijedeće karakteristike:


 Kapacitet vektora (capacity()) je broj ćelija koje se automatski alociraju za spremanje
elemenata vektora,
 Veličina vektora (size()) je broj elemenata koje stvarno sadrži vektor. Ona je manja ili jednaka
kapacitetu vektora.
 Pojedinom elementu vektora se pristupa pomoću indeksnog operatora. Vektor sam vrši
kontrolu da pristup preko indeksa bude unutar kapaciteta. Ako je potreban veći kapacitet
vektora on se može dobiti pozivom funkcije resize(n).
 Za pristup elementima vektora predviđene su i dvije posebne 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 računa o kapacitetu vektora i vrši se
automatsko alociranje potrebne memorije (memorija se udvostručuje ako potrebna veličina
vektora premašuje trenutni kapacitet). Namjena ove dvije funkcije je da se vektor može
koristiti kao dinamička kolekcija elemenata (kontenjer).

capacity()
1. indeksirani pristup

0 1 2 3 4 5 ? ?

push_back(el) 2. push/pop pristup


size()
pop_back()

12.3.1 Specifikacija ADT tvector


// *******************************************************************
// template <class T> tvector {....}
//
// Generički parametar tipa T mora zadovoljiti:
// (1) T ima predodređeni konstructor
// (2) za tip T definiran je operator =
//
// Odstupanje od PRED-uvjeta rezultira greškom i prekidom programa.
//
// Konstruktori/destruktor
//
// tvector( )
// POST: Kapacitet vektora == 0
//

12. Generičke funkcije i klase 9


// tvector( int size )
// PRED: size >= 0
// POST: kapacitet/veličina vektora == size
//
// tvector( int size, const T & fillValue )
// PRED:: size >= 0
// POST: Kapacitet /veličina vektora == size, svi elementi
// se iniciraju na fillValue nakon operacija konstruktora
//
// tvector( const tvector & vec )
// Opis: kopirni konstruktor
// POST: vector je kopija od vec
//
// ~tvector( )
// Opis: destruktor
//
// operator pridjele vrijednosti
//
// const tvector & operator = ( const tvector & rhs )
// POST: pridjela vrijednosti od rhs;
// ako se razlikuju veličine ova dva vektora
// vrši se promjena veličine na rhs.size()
//
// pristupnici
//
// int length( ) const
// int capacity( ) const
// POST: vraća broj ćelija alociranih za vektor
// int size( ) const
// POST: vraća 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: umeće element t na poziciji size()
// ako je size>= capacity dvostruko povećava kapacitet
//
// void pop_back();
// POST: odstranjuje element s pozicije size()--, smanuje size za 1
//
// T & operator [ ] ( int k );
// const T & operator [ ] ( int k );
// Opis: Dobava k-tog elementa, uz kontrolu indeksa
// PRED: 0 <= k < capacity()
// POST: vraća k-ti element
//
// void resize( int newSize );
// Opis: promjena veličine vektora u newSize
// PRED: newSize >= 0, kapacitet je capacity, a veličina je size()
// POST: 1. size() == newSize;
// Ako je newSize > size() tada i capacity=newSize
// inače se kapacitet ne mijenja
// 2. za 0 <= k <= min(size(), newSize), vector[k]
// je kopija originala; ostali elementi se iniciraju
// pomoću predodređenog T konstruktora
// Nota: ako je newSize < size(), gube se neki elementi
//
// void clear();
// POST: size = 0, capacitet nepromijenjen
// void reserve(int size);

12. Generičke funkcije i klase 10


// Opis: rezervira memoriju
// PRED: size >0
// POST: resize(size) ako je size > capacity()
//
// primjer upotrebe:
// tvector<int> v1; // 0-element vektor
// tvector<int> v2(4); // 4-element vektor
// tvector<int> v3(4, 22); // 4-element vektor, svi elementi == 22.

12.3.2 Implementacija tvector klase

//datoteka: tvector.h
#ifndef _TVECTOR_H
#define _TVECTOR_H

// *******************************************************************
// based on the Vector class in Tapestry, first edition
// and on the AP vector class. However, this class mirrors
// the STL vector with push_back, pop_back, size(), resize(), reserve()
// This vector does NOT support STL-style iterators; it does do range
// checking on operator []
//
// *******************************************************************

template <class T>


class tvector
{
public:
tvector( ); // predodređeni konstruktor - size==0
tvector( int size ); // konstruktor za size veličinu
tvector( int size, const T & fill ); // početna vrijednost = fill
tvector( const tvector & vec ); // kopirni konstruktor
~tvector( ); // destruktor

const tvector & operator = ( const tvector & vec );

int length( ) const; // podrška za stare programs, rađe


int capacity( ) const; // koisti umjesti length()
int size( ) const; // # elementata

T & operator [ ] ( int index ); // indeksni pristup


const T & operator [ ] ( int index ) const;

void resize( int newSize ); // promjena veličine niza;


void reserve(int size); // rezervira kapacitet=size
void push_back(const T& t); // dodaj na kraj niza
void pop_back(); // odstrani zadnji element
T& back(); // vraca zadnji element
const T& back() const; // vraca zadnji element
void clear(); // size == 0, kapacitet nepromijenjen

private:

int m_size; // broj elemenata u nizu


int m_capacity; // maks. kapacitet niza
T * m_arr; // pok. stvarne memorije niza
};

#include "tvector.cpp"

12. Generičke funkcije i klase 11


#endif

// *******************************************************************
// datoteka: tvector.cpp
// tvector klasa - podskup standardne C++ klase vector class
// *******************************************************************

#include <cstdlib>
#include <iostream>
using namespace std;

template <class T>


tvector<T>::tvector()
//POST: Kapacitet vektora == 0 elemenata
: m_size(0), m_capacity(0), m_arr(0)
{ }

template <class T>


tvector<T>::tvector(int size)
// PRED:: size >= 0
// POST: kapacitet/veličina vektora == size
: m_size(size), m_capacity(size), m_arr(new T[size])
{ }

template <class T>


tvector<T>::tvector(int size, const T & fillValue)
// PRED:: size >= 0
// POST: Kapacitet/veličina vektora == size, svi elementi se iniciraju
// na fillValue nakon operacija konstruktora
: m_size(size), m_capacity(size), m_arr(new T[size])
{
for(int k = 0; k < size; k++)
m_arr[k] = fillValue;
}

template <class T>


tvector<T>::tvector(const tvector<T> & vec)
// Opis: kopirni konstruktor
// POST: vector je kopija od vec
: m_size(vec.size()),
m_capacity(vec.capacity()),
m_arr(new T[m_capacity])
{
for(int k = 0; k < m_size; k++)
m_arr[k] = vec.m_arr[k];
}

template <class T>


tvector<T>::~tvector ()
// destruktor
{
delete [] m_arr;
m_arr = 0; // assure
}

template <class T>


const tvector<T> &
tvector<T>::operator = (const tvector<T> & rhs)
// POST: pridjela vrijednosti od rhs;
// ako se razlikuju veličine ova dva vektora

12. Generičke funkcije i klase 12


// vrši se promjena veličine na rhs.size()
{
if (this != &rhs) { // don't assign to self!
delete [] m_arr; // get rid of old storage
m_capacity = rhs.capacity();
m_size = rhs.size();
m_arr = new T [m_capacity]; // allocate new storage
for(int k=0; k < m_size; k++)
m_arr[k] = rhs.m_arr[k];
}
return *this; // permit a = b = c = d
}

template <class T>


int tvector<T>::capacity() const
// POST: vraća broj ćelija alociranih za vektor
{
return m_capacity;
}

template <class T>


int tvector<T>::size() const
{
return m_size;
}

template <class T>


void tvector<T>::push_back(const T& t)
// POST: umeće element t na poziciji size()
// ako je size>= capacity dvostruko povećava kapacitet
{
if (m_size >= m_capacity) {
reserve(m_capacity == 0 ? 2 : 2*m_capacity);
}
m_arr[m_size] = t;
m_size++;
}

template <class T>


void tvector<T>::pop_back()
// POST: odstranjuje element s pozicije size(), smanuje veličinu za 1
{
if (m_size > 0) m_size--;
}

template <class T>


T& tvector<T>::back()
// POST: vraca element s pozicije size()-1,
{ return m_arr[m_size -1]; }

template <class T>


const T& tvector<T>::back() const
// POST: vraca element s pozicije size()-1,
{ return m_arr[m_size -1]; }

template <class T>


T & tvector<T>::operator [] (int k)
// Opis: Dobava k-tog elementa, uz kontrolu indeksa
// PRED: 0 <= k < capacity()
// POST: vraća k-ti element

12. Generičke funkcije i klase 13


{
if (k < 0 || m_capacity <= k) {
cerr << "Nedozvoljen index: " << k << " max index = ";
cerr << (m_size-1) << endl;
exit(1);
}
return m_arr[k];
}

template <class T>


const T & tvector<T>::operator [] (int k) const
// vraća const reference
{
if (k < 0 || m_capacity <= k)
{
cerr << "Nedozvoljen index: " << k << " max index = ";
cerr << (m_size-1) << endl;
exit(1);
}
return m_arr[k];
}

template <class T>


void tvector<T>::resize(int newSize)
// Opis: promjena veličine vektora u newSize
// PRED: newSize >= 0, kapacitet je capacity, a veličina je size()
// POST: 1. size() == newSize;
// Ako je newSize > size() tada i capacity=newSize
// inače se kapacitet ne mijenja
// 2. za 0 <= k <= min(size(), newSize), vector[k]
// je kopija originala; ostali elementi se iniciraju
// pomoću predodređenog T konstruktora
// Nota: ako je newSize < size(), gube se neki elementi
{
if (newSize < m_size) {
m_size = newSize; // capacity doesn't "shrink"
return;
}
// alociraj novu mem. i kopiraj elemente

int k;
T * newArr = new T[newSize];
for(k=0; k < m_size; k++) {
newArr[k] = m_arr[k];
}
delete [] m_arr; // deealociraj staru memoriju
m_capacity = m_size = newSize; // ažuriraj capacity/size
m_arr = newArr;
}

template <class T>


void tvector<T>::reserve(int size)
{
// punt to resize in current implementation

int oldSize = m_size;


if (size > m_capacity) {
resize(size);
}
m_size = oldSize;
}

12. Generičke funkcije i klase 14


template <class T>
void tvector<T>::clear()
{
m_size = 0;
}

12.3.3 Aplikacija tvector klase

Zadatak: Napišite program u kojem korisnik unosi proizvoljan niz brojeva. Kao kontenjer brojeva
koristite tvector objekt. Nakon završenog unosa izračunajte 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!!

//nakon završenog unosa računamo 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;
}

Uočite: pri unosu podataka vektor koristimo kao kontenjer – u njega odlažemo elemente pomoću
funkcije push_back. Kasnije vektor koristimo kao običan niz (elementima pristupamo pomoću
indeksa). Korištenje vektora je pogodnije od rada s običnim nizovima je ne moramo voditi računa o
alokaciji memorije.

Zadatak: napišite program u kojem se očitavaju znakovi iz neke tekstualne datoteke. Program treba
ispisati ukupan broj slova i koliko se puta (u posticima) neko slovo pojavljuje u toj datoteci.

Ovaj zadatak je sličan problemu histograma iz poglavlja 5. Pošto ukupno ima 255 znakova oformit
ćemo vektor od 255 cijelih brojeva (ASCII skup ima 127 znakova, ali hrvatska slova su kodirana
izvan ASCII skupa pa moramo razmotriti 256 znakova) .
Program ćemo realizirati pomoću dvije funkcije. Funkcijom Count() očitavat ćemo znakove iz ulazne
datoteke i inkrementirati element vektora kojem je indeks jednak numeričkoj vrijednosti znaka.
Funkcijom Print() ispisat ćemo statistiku pojave slova.
//datoteka: slova.cpp
#include <iostream>
#include <fstream> // ifstream
#include <cstdlib> // exit()
#include <cctype> // tolower()

12. Generičke funkcije i klase 15


#include <string>
#include <iomanip> // za formatirani ispis
using namespace std;
#include "tvector.h"

void Print(const tvector<int> & counts, int broj_slova);


void Count(istream & input, tvector<int> & counts, int & broj_slova);

int main()
{
int broj_slova = 0;
string filename;
cout << "Ime datoteke: ";
cin >> filename;
// otvori datoteku
ifstream input(filename.c_str());
if (input.fail() )
{ cout << "Ne moze se otvoriti: " << filename << endl;
exit(1);
}
// inicijaliziraj vektor - brojač znakova na 0
tvector<int> charCounts(256,0);
// napravi histogram
Count(input,charCounts,broj_slova);
// ispiši statistiku pojave slova (u postocima)
Print(charCounts,broj_slova);
return 0;
}

void Count(istream & input, tvector<int> & counts, int & broj_slova)
// PRED: input datoteka otvorana za čitanje
// counts[k] == 0, 0 <= k < CHAR_MAX
// POST: counts[k] = broj pojave znaka k
// broj_slova = broj slova
{
unsigned char ch;
while (input.get(ch)) // čitaj znak
{ if (isalpha(ch)) // ako je slovo (a-z)?
broj_slova++;
ch = tolower(ch); // pretvori u mala slova
counts[ch]++; // povećaj izbroj
}
}

void Print(const tvector<int> & counts, int broj_slova)


// PRED: broj_slova u counts['a']..counts['z']
// POST: ispisuje se vrijednost counts od 'a' do 'z'
// u dvije kolone i daje postotak pojave slova
{
const int MIDALPH = 13; // polovina od 26 slova
cout.setf(ios::fixed);
cout.precision(1); // ispis na 1 decimalno mjesto
for(char k = 'a'; k <= 'm'; k++)
{ cout << k << setw(7) << counts[k] << " ";
cout << setw(4) << 100 * double(counts[k])/broj_slova
<< "% \t\t";
cout << char(k+MIDALPH) << setw(7)
<< counts[k+MIDALPH] << " ";
cout << setw(4) << 100 * double(counts[k+MIDALPH])/broj_slova
<< "%" << endl;
}

12. Generičke funkcije i klase 16


}
Kada se ovaj program primijeni na datoteku slova.cpp dobije se ispis:
Ime datoteke: slova.cpp
a 87 7.5% n 84 7.3%
b 19 1.6% o 120 10.4%
c 66 5.7% p 35 3.0%
d 35 3.0% q 0 0.0%
e 74 6.4% r 60 5.2%
f 14 1.2% s 77 6.7%
g 6 0.5% t 114 9.9%
h 21 1.8% u 53 4.6%
i 99 8.6% v 41 3.5%
j 30 2.6% w 7 0.6%
k 23 2.0% x 4 0.3%
l 48 4.2% y 1 0.1%
m 26 2.3% z 11 1.0%

Zadatak: Formirajte vektor koji će sadržavati kolekciju stringova. Otvorite neku .cpp datoteku,
očitavajte liniju po liniju teksta. Svaku liniju spremite u kolekciju stringova, ali na način da se odstrane
svi komentari koji započimaju s //.

#include <iostream>
#include <fstream> // ifstream
#include <cstdlib> // exit()
#include <string>
using namespace std;
#include "tvector.h"

void Decomment(const string& in, string& out)


{
//PRED: in sadrži liniju teksta
//POST: out sadrži liniju bez komentara koji počima s //

out = "";
int idx = in.find("//"); //da li počima komentar
if(idx==0)
return; // komentar počinje u prvom stupcu
else if(idx>0) // dobavi dio stringa do znaka komentara
out = in.substr(0, idx);
else //linija bez komentara
out = in;
}

int main()
{
string infilename, outfilename;
cout << "Ime ulazne .cpp datoteke: ";
cin >> infilename;
cout << "Ime izlazne datoteke: ";
cin >> outfilename;
if(infilename == outfilename)
{ cout << "Imena datoteka se moraju razlikovati" << endl;
exit(1);
}
ifstream input(infilename.c_str()); // otvori ulaznu datoteku
if (input.fail() )
{ cout << "Ne moze se otvoriti: " << infilename << endl;
exit(1);

12. Generičke funkcije i klase 17


}

string str; // radni string


tvector<string> txt; // vektor za kolekciju stringova

// rezerviraj memoriju za 1000 stringova.


// Time se smanjuje broj potrebnih alokacija
// rezerviranjem se ne mijenja trenutna veličina vektora

txt.reserve(1000);

while (getline(input, str)) // čitaj iz datoteke liniju teksta u str


{
string out;
Decomment(str, out);
txt.push_back(out);
}

input.close();

// ispiši u izlaznu datoteku


ofstream output(outfilename.c_str());
if (output.fail() )
{ cout << "Ne moze kreirati: " << outfilename << endl;
exit(1);
}

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


{
output << txt[i] << endl;
}
output.close();
return 0;
}

U predloženom rješenju biti će odstranjen dio teksta koji započima s // pa sve do kraja linije. To
znači da će biti odstranjen i tekst koji se nalazi unutar literalnog stringa, primjerice linija
int idx = str.find("//"); //da li počima komentar
će biti ispisana kao
int idx = str.find("
Da bi izbjegli ovo pogrešku mora se uzeti u obzir i mogućnost da unutar literalnog stringa mogu biti
zapisani znakovi //.

Proces analize linije teksta najbolje je izvršiti znak po znak. U tu svrhu poslužit ćemo se prikazom
jednog automata. Automat analizira trenutni znak (ch) i na temelju tog znaka i stanja u kojem se nalazi
odlučuje o prijelazu u neko drugo stanje ili zadržava trenutno stanje. Na slici su krugovima označena
4 stanja, a strelicama su označeni prijelazi iz stanja.

1. TEXT – je početno stanje. Označeno je podebljanom kružnicom. U tom stanju se analizira


slijedeći znak i odlučuje o prijelazu u novo stanje.

2. SLASH je stanje koje nastaje ako u TEXT stanju slijedeći znak je / (slash).

3. COMMENT je stanje koje automat prelazi ako se u SLASH stanju ponovo pojavi znak / .
Uovom stanju automat ostaje sve do kraja linije. Ovo stanje je završno stanje (označeno je

12. Generičke funkcije i klase 18


dvostrukom kružnicom). Kada je automat u završnom stanju može se prekinuti rad automata,
bez obzira što još nisu analizirani svi znakovi.

4. LIT_STRING je stanje koje nastaje ako u TEXT stanju ili u SLASH stanju slijedeći znak je
navodnik. Automat ostaje u ovom stanju dok se ponovo ne pojavi znak navodnika. Tada se
automat vraća u stanje TEXT.

ch =='/'
ch =='/' SLASH

ch != '/'
start ch !='\"'
TEXT ch =='\"' COMM
ch ENT
ch =='\"'

ch != '/' ch
ch !='\"' LIT
ch =='\"'
STRING
ch != '\"'

Ovaj automat se jednostavno realizira pomoću slijedeće funkcije:

enum State{TEXT, FIRST_SLASH, COMMENT, LIT_STRING};

void Decomment(const string& input, string& output)


{
State state = TEXT;
output = "";
for(int i=0; i<input.length(); i++)
{
char ch = input[i];
switch(state)
{
case TEXT:
if (ch == '/') // potencijalno je početak komentara
state = FIRST_SLASH; //
else {
if (ch == '\"')
state = LIT_STRING;
output+=ch;
}
break;
case LIT_STRING:
if (ch == '\"') //literalni string završava
state = TEXT;
output+=ch;
break;
case FIRST_SLASH:
if (ch == '/') // dvostruki slash

12. Generičke funkcije i klase 19


state = COMMENT; // započima komentar
else // samo jedan SLASH
{
output += '/'; // ispiši slash od prošlog stanja
output += ch; // i trenutni znak
if (ch == '\"')
state = LIT_STRING
else
state = TEXT; // stanje početno
}
break;
case COMMENT: // završno stanje
return; // prekini analizu: komentar je do kraja linije
// output sadrži liniju bez komentara
}
}
}

Prikazani automat se često naziva konačni deterministički automat. Njegova je primjena česta u
leksičkoj analizi teksta.

I ovo rješenje ima manu. Što ako se unutar literalnog stringa pojavi escape sekvenca, primjerice
"string s \" escape sekcencom\" "
Tada bi se prekinulo stanje LIT_STRING prije kraja stringa.
Problem bi mogli riješiti tako da u automat dodamo peto stanje – ESC, prikazano na slici:

ch =='/'
ch =='/' SLASH

start
TEXT ch =='\"' COMM
ch ENT
ch =='\"'

LIT
ch =='\"'
ch != '/' STRING ch
ch !='\"'
ch =='\\'
ch != '\"'

ESC ch

Napišite funkciju Decomment() kojom se realizira ovaj automat.

12. Generičke funkcije i klase 20


12.4 STOG ili STACK
Stog (eng. stack) je naziv za kolekciju podataka kojoj se pristupa po principu LIFO – last in, first out.
Primjerice, kada slažemo tanjure tada stavljamo jedan tanjur poviše drugog – tu operaciju zovemo
push, a kada uzimamo tanjur onda uvijek uzimamo onaj kojeg smo posljednjeg stavili u stog – tu
operaciju nazivamo pop.

12.4.1 Specifikacija apstraktnog tipa Stack


Specifikacija ADT Stack je:
 empty() - vraća true ako je stack prazan
 size() – vraća broj elemenata stoga
 push(el) – unosi se element el na stog
 pop() – skida se element s vrha stoga (operacija je uspješna ako na stogu postoji bar jedan
element)
 el = top() – vraca vrijednost elementa s vrha stoga (operacija je uspješna ako na stogu postoji
bar jedan element)

12.4.2 Specifikacija i implementacija klase Stack


Ovaj ADT ćemo realizirati pomoću klase vector. Funkcije push i pop ćemo realizirati pomoću
članskih funkcija klase vector: push_back() i pop_back(), a funkciju top() pomoću pomoću
članske funkcija back(), koja vraća posljednji element vektora. Nema ograničenja na veličinu stoga,
jer vektor može rasti do proizvoljne veličine. Funkcije empty() i size() su iste kao kod klase
vector.
#include <vector>
template <class Type>
class Stack
{
public:
Stack(){}
~Stack(){}

bool empty() const {return m_v.empty();}


// Vraća true ako je stog prazan

int size() {return m_v.size();}


// Vraća broj elemenata na stogu

Type& top() {return (m_v.back()); }


const Type& top() const {return (m_v.back());}
// PRE: na stogu je bar jedan elemant
// Vraća vrijednost elementa na vrhu stoga
// POST: sadržaj stoga nepromijenjen

void pop() {m_v.pop_back(); }


// PRE: na stogu je bar jedan elemant
// odstranjuje element s vrha stoga

void push(const Type &elem) {m_v.push_back(elem); }


// postavlja element na vrh stoga
// POST: na stogu je jedan element više

private:
std::vector<Type> m_v;
};

Važna napomena:

12. Generičke funkcije i klase 21


Klasa Stack sadrži samo jednu člansku varijablu vector<T> m_v; dakle Stack klasa sadrži člana
koji je objekt tipa vector. Često se ovakova povezanost, kada jedna klasa ima članove drugih
klasa, naziva kompozicija (composition) ili “has-a” veza klasa.
Prividno izgleda da konstruktor i destruktor klase Stack ne obavljaju nikakovu radnju, jer su definirani
kao “prazne” funkcije. U slučaju kompozicije klasa kompajler uvijek pri pozivu konstruktora klase
poziva i konstruktor članskih objekata, u ovom slučaju se poziva konstruktor vektora v. Isto vrijedi i
za destruktor. Ako članski objekt ima više konstruktora, onda se mora u okviru konstruktora
eksplicitno navesti koji se oblik konstruktora članskih objekata poziva. Ako se to ne navede izvršava
se predodređeni konstruktor.

12.4.3 Primjena stoga za proračun izraza postfiksne notacije


Korištenjem programski simuliranog stoga jednostavno se provodi računanje matematičkih izraza u
postfiksnoj notaciji. Postfiksna notacija izraza se piše tako da se prije pišu operandi, a tek onda
operator koji na njih djeluje, npr.

infiksna notacija izraza postfiksna notacija izraza


A + B * C A B C * +
(A + B) * C A B + C *
(a + b)/(c – d) a b + cd - /
a * b / c a b * C /

Ovaj tip notacije se naziva i obrnuta poljska notacije, prema autoru Lukasiewiczu.

Svojstva postfiksne notacije su:


 Svaka formula se može napisati bez zagrada.
 Infiksni operatori moraju uvažavati pravila pioriteta što nije potrebno kod postfiksne notacije.
 Za proračun postfiksne notacije prikladna je upotreba stoga.

Pretvorba infiksne u postfiksnu notaciju se izvodi slijedećim algoritmom:


1. Kompletno ispiši zagrade između svih operanada, tako da zagrade potpuno odrede redoslijed
izvršenja operacija.
2. Pomakni svaki operator na mjesto desne zagrade
3. Odstrani zagrade

Primjerice, pretvorba izraza (8+2*5) / (1+3*2-4), prema gornjem pravilu, tj.


prema 1. ( ( 8 + ( 2 * 5 ) ) / ( 1 + ( ( 3 * 2 ) - 4 ) ) )
prema 2. i 3. ( ( 8 + ( 2 * 5 ) ) ( 1 + ( ( 3 * 2 ) - 4 ) ) /
( 8 ( 2 * 5 ) + 1 ( ( 3 * 2 ) - 4 ) + /
8 2 5 * + 1 ( ( 3 * 2 ) 4 - + /
8 2 5 * + 1 3 2 * 4 - + /

daje postfiksnu notaciju: 8 2 5 * + 1 3 2 * + 4 - /

Za izračun postfiksnog izraza, koji ima n simbola, vrijedi algoritam:


1. Neka je k =1 indeks prvog simbola
2. Dok je k <= n ponavljaj
Ako je k-ti simbol operand tada ga gurni na stog.
Ako je k-ti simbol operator tada dobavi dvije vrijednosti sa stoga (najprije drugi, pa prvi
operand), izvrši naznačenu operaciju i rezultat vrati na stog.
Uvećaj k za 1
3. Algoritam završava s rezultatom na stogu.

Primjer:

12. Generičke funkcije i klase 22


Izraz zapisan infiksnoj notaciji: (8+2*5) / (1+3*2-4)
ima postfiks notaciju: 8 2 5 * + 1 3 2 * + 4 - /

1 8 2 5 * + 1 3 2 * + 4 - / push 8
2 2 5 * + 1 3 2 * + 4 - / push 2
3 5 * + 1 3 2 * + 4 - / push 5
4 * + 1 3 2 * + 4 - / b=top(), pop(), a=2 * b=5
a=top(), pop(),
push (10)
5 + 1 3 2 * + 4 - / b=top(), pop(), a=8 + b=10
a=top(), pop(),
push(18)
6 1 3 2 * + 4 - / push 1
7 3 2 * + 4 - / push 3
8 2 * + 4 - / push 2
9 * + 4 - / b=top(), pop(), a=3 * b=2
a=top(), pop(),
push (6)
10 + 4 - / b=top(), pop(), a=1 + b=6
a=top(), pop(),
push(7)
11 4 - / push 4
12 - / b=top(), pop(), a=7 - b=4
a=top(), pop(),
push(3)
13 / b=top(), pop(), a=18 / b=3
a=top(), pop(),
push(6)

8 8 8 8 18 18 18 18 18 18 18 18 6
2 2 10 1 1 1 1 7 7 3
5 3 3 6 4
2

Raspored stanja na stogu nakon pojedinog koraka proračuna.

Izračun izraza pomoću postfiksne notacije je jedan od uobičajenih načina kako neki intepreteri
izračunavaju izraze zapisane u višim programskim jezicima - najprije se vrši pretvorba infiksnog
zapisa izraza u postfiksni zapis, a zatim se izračun izraza vrši pomoću stoga. U ovom slučaju ne koristi
se stog kojim upravlja procesor već se rad stog simulira programski.

//Datoteka: polish.cpp
#include <iostream>
#include <cstring>
#include "stack-vec.h" // ili std lib: #include <stack>
using namespace std;

#define isDigit(c) (((c) >= '0') && ((c) <= '9'))

int main(int argc, char *argv[])


{
char *str;
int i, len;
int arg1, arg2;
Stack<int> S;

12. Generičke funkcije i klase 23


if (argc < 2) {
cout << "Za proracun (30/(5-2))*10 otkucaj:\n";
cout << "c:> polish \"30 5 2 - / 10 *\n\"";
exit(1);
}
str = argv[1];
len = strlen(str);

for (i = 0; i < len; i++)


{
if (str[i] == '+'){
arg2 = S.top(); S.pop();
arg1 = S.top(); S.pop();
S.push(arg1+arg2);
}
if (str[i] == '*'){
arg2 = S.top(); S.pop();
arg1 = S.top(); S.pop();
S.push(arg1*arg2);
}
if (str[i] == '-') {
arg2 = S.top(); S.pop();
arg1 = S.top(); S.pop();
S.push(arg1-arg2);
}
if (str[i] == '/') {
arg2=S.top(); S.pop();
arg1= S.top();S.pop();
if (arg2==0) {
cout << "Djeljenje s nulom\n"; exit(1);
}
S.push(arg1/arg2);
}
if (isDigit(str[i])) /* konverzija niza znamenki u broj */
{
S.push(0);
do {
arg1 = S.top(); S.pop();
S.push(10*arg1 + ((int)str[i]-'0'));
i++;
} while (isDigit(str[i]));
i--;
}
}
arg1 = S.top();
cout << "Rezultat: "<< str <<"="<<arg1<<" \n";
return 0;
}

Kada se u komandnoj liniji pozove program polish s argumentom:


c:> polish "30 5 2 - / 10 *"
dobije se ispis:
Rezultat: 30 5 2 - / 10 *=100

12. Generičke funkcije i klase 24


12.4.5 Simulacija izvršnog stoga (run-time stack)

Do sada smo više puta spomenuli da se za prijenos argumenata i rezultata funkcije koristi dio
memorije koji se naziva izvršni stog (eng. run-time stack). Razlog tome je činjenica da u strojnom
jeziku procesora ne postoje naredbe za prijenos parametara pri pozivu funkcije. Poziv funkcije zapravo
znači da se izvršenje programa nastavlja od adrese funkcije. Nakon izvršene posljednje naredbe neke
funkcije ponovo se vrši skok na naredbu koja slijedi nakon naredbe poziva funkcije. Dakle, strojnim
jezikom se pristupa funkcijama koje se u C jeziku tretiraju kao void funkcije i koje ne koriste
argumente. U višim programskim jezicima se ipak definiraju funkcije koje koriste argumente i koje
vraćaju vrijednosti. Pokazat ćemo kako je to realizirano simulirajući izvršni stog s objektom Stog:

vector<int> Stog; // simulator izvrsnog stoga


int Reg; // registar u kojeg se upisuje rezultat funkcija

void push(int n) {Stog.push_back(n);}


int & top(int n=0) {return Stog [Stog.size()-1+n];}
void pop() { Stog.pop_back();}

Uobičajeno je da se pri prevođenju programa u strojni kod, prijenos argumenata u funkciju obavlja na
sljedeći način:
i) Pozvana funkcija se realizira tako da se očekuje da se argumenti funkcije nalaze na stogu, na način
da se posljednji argument nalazi na vrhu stoga. Vrijednost koju funkcije vraća se sprema u jedan od
registara procesora kojeg ćemo nazvati Reg. Primjerice funkciju:
int minimum(int x, int y)
{
if (x > y)
return y;
else
return x;
}
se na razini strojnog koda tretira kao void funkciju:
void minimum() // funkcija definirana bez parametara
{
//očekuje se da pozivna funkcija postavi argumente na vrh stoga

int &y = top(); // y argument je na vrhu stoga


int &x = top(-1); // x argument je na stogu ispod y,
// jer je prvi gurnut na stog
if(x > y)
Reg = y; // rezultat vraćamo u Reg
else
Reg = x;
}

ii) U pozivnoj se funkciji prije poziva neke funkcije svi argumenti stavljaju na izvršni stog, jedan za
drugim, pomoću naredbe push(arg1); push(arg2 )...a zatim se vrši poziv funkcije. Nakon povrata iz
funkcije vrijednost koju funkcija vraća se nalazi u registru Reg. Također, nakon povrata iz funkcije u
pozivnoj funkciji treba očistiti stog pozivom funkcije pop(), onoliko puta koliko je prethodno bilo
stavljeno argumenbata na stog.
To je pokazano u programu params1.cpp

//Datoteka: params1.cpp
//program koji racuna kvadrat broja 5

12. Generičke funkcije i klase 25


#include <iostream>
#include <vector>
using namespace std;

vector<int> Stog; // simulator izvrsnog stoga


int Reg; // registar u kojeg se upisuje rezultat funkcija

void push(int n) {Stog.push_back(n);}


int & top(int n=0) {return Stog[Stog.size()-1+n];}
void pop() {Stog.pop_back();}

void minimum();

int main()
{
int x = 5, y = 7, rezultat;
push(x); // argument stavljamo na stog
push(y); // argument stavljamo na stog
minimum(); // pozivamo funkciju minimum
rezultat = Reg; // rezultat je u registru Reg
pop(); // ocistimo stog, koji sadrzi argument y
pop(); // ocistimo stog, koji sadrzi argument x
cout << "minimum od "<< x <<" i "<<y
<<" je " << rezultat << endl;
return 0;
}

void minimum() // funkcija definirana bez parametara


{ //ocekuje se da pozivna funkcija postavi argumente na vrh stoga
int &y = top(); // y argument je na vrhu stoga
int &x = top(-1); // x argument je na stogu ispod y,
// jer je prvi gurnut na stog
if(x > y)
Reg = y; // rezultat vracamo u Reg
else
Reg = x;
}

Dobijer se ispis:
Minimum od 5 i 7 je 5

Uočite da su reference x i y sinonimi za varijable top() i top(-1). Mogli smo pisati i


void minimum()
{
if(top(-1) > top())
Reg = top(); // rezultat vraćamo u Reg
else
Reg = top(-1);
}
ali se uvođenjem referenci dobija bolje rješenje jer se smanjuje broj poziva funkcije top().

Pogledajmo još primjer u kojem se realizira kvazi-asemblerski kod funkcije kojom se računa suma
elemenata nekog niza.
int suma_niza(int A[], int n)
{
int sum = 0;
while(n--)

12. Generičke funkcije i klase 26


{
sum += A[n]
}
return sum;
}

Cilj nam je pokazati kako se reference prenose u funkciju.

Kompletni primjer kvazi-asemblerske realizacije ove funkcije dan je u programu params2.cpp.


//Datoteka: params2.cpp
//Program racuna sumu elemenata niza od 5 elemenata
#include <iostream>
#include <vector>
using namespace std;

vector<int> Stog; // simulator izvrsnog stoga


int Reg; // registar u kojeg se upisuje rezultat funkcija

void push(int n) {Stog.push_back(n);}


int & top(int n=0) {return Stog[Stog.size()-1+n];}
void pop() { Stog.pop_back();}

void suma_niza();

int main()
{
int A[5] = {1,2,3,4,5};
int len = 5, rezultat;
push((int)A); // prvo stavljamo na stog adresu niza
push(len); // zatim na stog stavljamo duljinu niza
suma_niza(); // pozivamo funkciju
rezultat = Reg; // rezultat je u registru R
pop(); // konacno ocistimo stog
pop();
cout << "suma niza je "<< rezultat << endl;
return 0;
}

void suma_niza() // funkcija definirana bez parametara


{ //ocekuje se da pozivna funkcija postaviti argumente na vrh stoga
int &n = top(); // duljina niza je argument na vrhu stoga
int* &A =(int*&) top(-1); // adresa niza je ispod njega
Reg = 0;
petlja:
if(n == 0) goto end_while;
n--;
Reg += A[n];
goto petlja;
end_while:
;
// rezultat se nalazi u registaru Reg
}

U ovim primjerima može se naslutiti koliko je težak proces programiranja asemblerskim jezikom.

Važno je zapamtiti način na koji se u C++ jeziku vrši prijenos argumenata funkcije, bilo da se radi o
vrijednostima ili referencama.

12. Generičke funkcije i klase 27

You might also like