You are on page 1of 22

8.

Nizovi i pokazivai
Naglasci:
Jednodimenzionalni nizovi
Dvodimenzionalni nizovi
Nizovi objekata
Ekvivalentnost pokazivake i indeksne notacije nizova
Operacije s pokazivaima
Pokazivai na funkcije
Dinamiko alociranje nizova
Prihvat iznimki kod alociranja memorije
Kompleksnost deklaracija varijabli i funkcija

8.1 Nizovi

Jednodimenzionalni nizovi
Niz je imenovana i numerirana kolekcija istovrsnih objekata koje zovemo elementi niza. Elementi
niza mogu biti proste skalarne varijable i korisniki definirani objekti . Oznaavaju se imenom niza i
cjelobrojnom izrazom indeksom - koji oznaava poziciju elementa u nizu. Indeks niza se zapisuje u
uglatim zagradama iza imena niza. Primjerice, x[3] oznaava element niza x indeksa 3. Sintaksa
zapisa elementa jednodimenzionalnog niza je

element_niza: ime_niza [ indeks ]


indeks: izraz_cjelobrojnog_tipa

Prvi element niza ima indeks 0, a n-ti element ima indeks n-1. Prema tom pravilu, x[3] oznaava
etvrti element niza.

S elementima niza se manipulira kao s obinim skalarnim varijablama ili objektima, ovisno o nainu
kako je izvrena deklaracija niza. Sintaksa deklaracije jednodimenzionalnog niza je:

deklaracija_niza:
oznaka_tipa ime_niza [ konstantni_izraz ] ;

Primjerice, deklaracijom
int data[9];
definira data kao niz od 9 elementa tipa int.

data[0] data[1] data[2] data[3] data[4] data[5] data[6] data[7] data[8]

Elementi niza data su dostupni, ako je indeks definiran izrazom koji rezultira cijelim brojem iz
intervala od 0 do 8, npr.
data[0]

8. Nizovi i pokazivai 1
data[2+3] = data[0];
int x=5;
data[x] = 7;

U C jeziku se ne vri provjera da li je vrijednost indeksnog izraza unutar predvienog intervala. To je


u nadlenosti programera.

memorija
adresa sadraj
1000 data[0]
1004 data[1]
1008 data[2]
Elementi niza zauzimaju sukscesivne lokacije u 1012 data[3]
memoriji. 1016 data[4]
1020 data[6]
Vrijedi pravilo
1024 data[5]
adresa(data[n])=adresa(data[0]) + n*sizeof(data[0])) 1028 data[7]
1032 data[8]

Napomena.(int zauzima 4 bajta)

Deklaracijom niza se rezervira potrebna memorija, ali se ne vri se i inicijalizacija poetnih vrijednosti
elemenata niza. Za inicijalizaciju niza esto se koristi for petlja, pr.
int i, data[10];
for (i = 0; i < 10; i++)
data[i] = 0;
Niz se moe inicijalizirati i s deklaracijom slijedeeg tipa:
int data[9]= {1,2,23,4,32,5,7,9,6};
gdje se unutar vitiastih zagrada navodi lista konstanti.

Ako se inicijaliziraju svi potrebni elementi niza, tada nije nuno u deklaraciji navesti dimenziju niza.
Primjerice
int data[]= {1,2,23,4,32,5,7,9,6};
je potpuno ekvivalentno prethodnoj deklaraciji. Broj elemenata ovakovog niza uvijek moemo dobiti
naredbom:
int numelements = sizeof(data)/sizeof(int);
Niz se moe i parcijalno inicajalizirati. U deklaraciji
int data[10]= {1,2,23};
prva tri elementa imaju vrijednost 1,2,23, a ostale elemente kompajler postavlja na vrijednost nula.

Primjer: Histogram

Zadatak: U datoteci "ocjene.dat" u tekstualnom obliku zapisane su ocjene u rasponu od 1 do 10.


Sadraj datoteke "ocjene.dat neka ini slijedei niz ocjena:

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

8. Nizovi i pokazivai 2
Treba izraditi program imena hist.cpp pomou kojeg e se prikazati histogram ocjena u 10 grupa (1,
2,.. 10).

Predvidjeti da se podaci se iz datoteke "ocjene.dat" predaju programu hist.exe preusmjeravanjem


standardnog ulaza (tipkovnice) na datoteku ocjene dat.

/* datoteka: hist.cpp */

#include <iostream>
using namespace std;

int main(void)
{
int i, counts[10], ocjena;

// counts[i] sadrzava podatak o tome


// koliko je ocjena iz pojedine grupe (1,2,..9,10).

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


counts[i] = 0;

while (cin >> ocjena)


{
if(ocjena <1 || ocjena >10) break;
counts[ocjena-1]++;
}

cout << "Histogram:" << '\n';


for (i = 10; i > 0; i--)
{
cout << i << '\t';
for (int n = counts[i-1]; n> 0; n--)
cout << '*';
cout << '\n';
}
return 0;
}

c:>hist < ocjene.dat

10 *****
9 *******
8 ******
7 **********
6 *********
5 *******
4 **********
3 ********
2 ******
1 *****

Pazite: Pri pozivu programa u komandnoj liniji je sa hist < ocjene.dat izvreno
preusmjerenje standardnog ulaza. Efekt preusmjeravanja je da se sadraj datoteke
ocjene.dat tretira kao da je unesen sa standardnog ulaza.

Analiza programa hist.c:

8. Nizovi i pokazivai 3
Prvo se deklarira niz count od 10 elemenata
int i, counts[10], ocjena; tipa int.
for (i = 0; i < 10; i++) Pomou for petlje vrijednost elemenata niza
counts[i] = 0; inicijalizira se na vrijednost 0.
while (cin >> ocjena) Oitava se ocjena i inkrementira odgovarajui
{ element niza counts; cin vraa vrijednost EOF
if(ocjena <1 || ocjena >10)
break;
kada se oita znak za kraj datoteke -EOF (ako je
counts[ocjena-1]++; unos s tipkovnice znak EOF se unosi s Ctrl-Z
} <enter>)

for (i = 10; i > 0; i--)


{
cout << i << '\t'; For petljom se ispituje vrijednost niza count
for (int n = counts[i-1]; od counts[9] do counts[0], te ispisuje
n > 0; n--) linija histograma pomou znaka zvjezdice.
cout << '*';
cout << '\n';
}

Kako se niz prenosi u funkciju


Prijanji primjer programa histogram.cpp bit e modificiran, na nain da e se koriti tri funkcije:
- pojavnost ocjena u nizu count biljeit e se funkcijom record,
- crtanje zvjezdica obavljat e funkcija printstars, a
- crtanje histograma obavljat e funkcija printhistogram.

// datoteka: histf.cpp

#include <iostream> void record( int ocjena, int


counts[])
using namespace std;

void record( int ocjena, int counts[]) Kada se jednodimenzionalni niz pojavljuje u
{ deklaraciji ili definiciji funkcije, tada se ne
counts[ocjena-1]++;
navodi dimenzija niza.
}
Uoimo de se vrijednost elemenata niza
void printstars(int n) moe mijenjati unutar funkcije. Oito je da se
{ niz ne prenosi po vrijednosti (by value), jer
while (n-- > 0) tada to ne bi bilo mogue.
cout << '*';
cout << '\n'; Pravilo je:
}
Nizovi se u funkciju prenose kao
void printhistogram(int counts[]) memorijske reference (by reference).
{ Kompajler memorijsku referencu (adresu)
for (int i = 10; i > 0; i--) { niza pamti "u njegovom imenu" stoga se
cout << i << '\t'; pri pozivu funkcije navodi samo ime, bez
printstars(counts[i-1]);
}
uglatih zagrada.
}

int main(void)
{
int ocjena;

8. Nizovi i pokazivai 4
int counts[10] = {0};

while (cin >> ocjena) {


if(ocjena <1 || ocjena >10)
break;
record(ocjena, counts);
}

cout << "Histogram" << '\n';


printhistogram(counts);
return 0;
}

Viedimenzionalni nizovi
Viedimenzionalnim nizovima se pristupa preko dva ili vie indeksa. Npr. deklaracijom:
int x[3][4];
definira se dvodimenzionalni niz koji ima 3 x 4 = 12 elemenata. Deklaraciju se moe itati i ovako:
definiran je niz kojem su elementi 3 niza od 4 elementa tipa int.
Elementima se pristupa preko dva indeksa. U slijedeem primjeru pokazano je kako se dobije suma
svih elemenata matrice x;
int i, j, num_rows=3, num_cols=4;
int sum=0;
for (i = 0; i < num_rows; i++)
for (j = 0; i < num_cols; j++)
sum += x[i][j];
cout << "suma elemenata matrice = " << sum;

memorija
adresa sadraj
U C jeziku dvodimenzionalni nizovi, koji opisuju matrice, u
memoriji su sloeni po redovima matrice. Najprije prvi 1000 x[ 0][ 0]
redak, zatim drugi, itd.. 1004 x[ 0][ 1]
1008 x[ 0][ 2]
U radu s matricama nije potrebno razmiljati kako je niz
1012 x[ 0][ 3]
sloen u memoriji, jer se elementima pristupa preko dva 1016 x[ 1][ 0]
indeksa: prvi je oznaka redka, a drugi je oznaka stupca 1020 x[ 1][ 1]
matrice. 1024 x[ 1][ 2]
matrini prikaz 1028 x[ 1][ 3]
1032 x[ 2][ 0]
x[0][0] x[0][1] x[0][2] x[0][3] 1036 x[ 2][ 1]
x[1][0] x[1][1] x[1][2] x[1][3] 1040 x[ 2][ 2]
1044 x[ 2][ 3]
x[2][0] x[2][1] x[2][2] x[2][3]

Viedimenzionalni niz se moe inicijalizirati ve u samoj deklaraciji, npr.


int x[3][4] = { { 1, 21, 14, 8},
{12, 7, 41, 2},
{ 1, 2, 4, 3}};
Navoenje unutarnjih vitiastih zagrada je opciono, pa se moe pisati i
int x[3][4] = {1, 21, 14, 8, 12, 7, 41, 2, 1, 2, 4, 3};
Ovakovi stil se ne preporuuje, jer je tee uoiti raspored elemenata po redovima i stupcima.

8. Nizovi i pokazivai 5
Pri deklariranju argumenta funkcije koji su viedimenzionalnih nizovi takoer se ne navodi prva
dimenzija, ali ostale dimenzije treba deklarirati, kako bi kompajler znao kojim su redom elementi
sloeni u memoriji.
Primjer, definirajmo funkciju kojom se rauna suma elemenata dvodimenzionalne matrice:
int sum_mat_el(int x[][4], int nrows, int ncols)
{
int i, j, sum=0;
for (i = 0; i < nrows; i++)
for (j = 0; i < ncols; j++)
sum += x[i][j];
return sum;
}
Uoimo, da su u definiciji funkcije navedeni i parametri nrows i ncols koji opisuju broj redaka i
stupaca matrice. Oito je da ovo nije ba najbolji nain za rad s matricama, jer je koritenje ove
funkcije mogue samo za matrice koje imaju 4 stupca. Kasnije emo pokazati kako se moe ova
funkcija modificirati da vrijedi za matrice proizvoljnih dimenzija.

Nizovi objekata
Elementi niza mogu biti bilo kojeg tipa ukljuujui i objekte definirane nekom klasom (strukturom).
Primjerice, deklaracijom
Counter counts[10];
deklarira se niz od 10 objekata tipa Counter. Ovakovi oblik deklaracije podrazumijeva da kompajler
uz rezerviranje potrebne memorije izvri i poziv predodreenog konstruktora za svaki element niza.
lanskim funkcijama objekata pristupa se pomou toka operatora, primjerice s
counts[3].get_count()
dobije se vrijednost izbroja brojaa koji je etvrti u nizu counts. Primjer primjene niza brojaa dan je
u datoteci hist-obj.cpp. Pokazano je da se problem izrade histograma moe elegantno rijeiti
koritenjem klase Counter.

Specifikacija klase Counter zapisana je u datoteci counter.h, a implementacija funkcija u datoteci


counter.cpp.

// datoteka: hist-obj.cpp
// realizacija histograma pomou klase Counter
#include <iostream>
#include <counter.h> // specifikacija brojaa

using namespace std;

void record( int ocjena, Counter counts[])


{
counts[ocjena-1].incr_count();
}

void printstars(int n)
{
while (n-- > 0)
cout << '*';
cout << '\n';
}

void printhistogram(Counter counts[])


{
for (int i = 10; i > 0; i--) {

8. Nizovi i pokazivai 6
cout << i << '\t';
printstars(counts[i-1].get_count());
}
}

int main(void)
{
int ocjena;
Counter counts[10];

while (cin >> ocjena) {


if(ocjena <1 || ocjena >10)
break;
record(ocjena, counts);
}

cout << "Histogram" << '\n';


printhistogram(counts);
return 0;
}

Izvrni program se dobije komandom


c:> cl /GX hist_obj.cpp counter.cpp
Usporedimo li ovaj program s programom histf.cpp uoit emo samo tri razlike:

realizacija histograma pomou realizacija histograma pomou


cjelobrojnog niza klase Counter
int counts[10] = {0}; Counter counts[10];

counts[ocjena-1]++; counts[ocjena-1].incr_count();

printstars(counts[i-1]); printstars(counts[i-1].get_count());

Prva je razlika u deklaraciji. Kod niza s primitivnim cjalobrojnim tipom potrebno je eksplicitno
inicirati sve elemente na vrijednost nula, dok primjenom klase brojaa tu funkciju obavlja
predodreeni konstruktor klase. Zatim slijede dvije razlike zbog razliitog naina pristupa elementima
niza. Oito je da e se verzija s klasom Counter neto sporije izvravati, jer se funkcija incr_count()
sporije izvrava od prostog inkrementiranja cjelobrojne varijable. S druge strane gledano, verzija s
klasom Counter je razumljivije napisana, i moe se jasnije, bez posebnih programskih komentara,
shvatiti o emu se radi.
Ovaj posebni sluaj zapravo ukazuje na injenicu da se objektno-zasnovani programi esto sporije
izvravaju nego se to moe postiu programiranjem niske razine u C jeziku. esto se govori da
objektno zasnovani programi imaju overhead odnosno da izvravaju vie instrukcija nego bi se to
moglo postii programiranjem niske razine. Kod veih programa ovaj overhead nije znaajan jer se
poveanjem apstrakcije, koju omoguava objektno programiranje, dobijaju elegantnija i sigurnija
rjeenja, a nerijetko i bre izvrenje programa, nego je to mogue s klasinim proceduralnim
programiranjem.

8.2 Pokazivai i nizovi


Ako se deklarira pokaziva istog tipa kao i elementi niza, onda se njemu moe pridijeliti adresa bilo
kojeg elementa niza. Primjerice, iskazom int *p= &a[3] adresa elementa indeksa 3 se pridjeljuje
pokazivau p. Stoga, kaemo da se pomou pokazivaa moe se "etati po nizu".

8. Nizovi i pokazivai 7
esto e se koristiti iskazi oblika:
int a[10], i, x;
int *p, *pi;

p = &a[0]; // inicijalizacija pokazivaa


x = *p; // x-u se pomou pokazivaa daje vrijednost a[0]
p = p + i; // p pokazuje na element a[i]
x = *p; // x ima vrijednost a[i]
p++; // p pokazuje na a[i+1]

Oni se formiraju prema slijedeim pravilima:

1. Ime niza ima se tretira se kao "pokazivaka konstanta" koja predstavlja adresu prvog elementa
niza. Naredba
p = a;
vrijednost pokazivaa p postavlja na adresu elementa a[0]. Ovo je ekvivalentno naredbi:
p = &a[0];

2. Pravilo pokazivake aritmetike:

Ako pokaziva pi pokazuje na a[i], tada pi + k pokazuje na a[i+k], odnosno, ako je


pi = &a[i];
tada vrijedi odnos;
*(pi+k) *(p+i+k) a[i+ k]

3. Ekvivalentnost indeksne i pokazivake notacije niza.

Ime niza napisano bez zagrada predstavlja pokaziva na prvi element. Stoga, ako je deklariran niz
array[], tada izraz *array oznaava prvi element, *(array + 1) oznaava drugi element, itd.
Poopimo li ovo pravilo na cijeli niz array, vrijede slijedei odnosi:
*(array) array[0]
*(array + 1) array[1]
*(array + 2) array[2]
...
*(array + n) array[n]
S pokazivaima se takoer moe koristiti indeksna notacija. Tako, ako je inicijaliziran pokaziva p:
p = array;
tada vrijedi:
p[0] *p array[0]
p[1] *(p + 1) array[1]
p[2] *(p + 2) array[2]
...
p[n] *(p + n) array[n]

Zapamti: Nije dozvoljena pokazivaka aritmetika s memorijskim referencama


niza : array++; je nedozvoljen izraz, jer se ne moe mijenjati konstanta
p++; je dozvoljen izraz, jer je pokaziva p varijabla

Primjer "etnje" po nizu koritenjem pokazivaa.

8. Nizovi i pokazivai 8
int *p = a;
for (i = 0; i < 10; i++) for (i = 0; i < 10; i++)
cout << *p++ << endl; cout << a[i] << endl;

U oba se sluaja tiska iste vrijednosti niza. Obje petlje su jednako efikasne i prihvatljive u
programiranju.

Pokazivai i argumenti funkcije tipa niza


Pri pozivu funkcije stvarni argumenti se kopiraju u formalne argumente (parametre) funkcije. Kada je
argument funkcije ime niza kopira se adresa prvog elementa, dakle stvarni argument koji se prenosi u
funkciju je pokaziva na prvi element niza. Kaemo da se niz u funkciju prenosi po adresi - pomou
pokazivaa.

Primjer: obje funkcije print imaju isto djelovanje i mogu se pozvati s istim argumentima:

void print(int x[], int size) void print( int *x, int size)
{ {
for (int i = 0; i < size; i++) while (size-- > 0)
cout << x[i] << endl; cout << *x++ << endl;
} }
/* poziv funkcije print */
int niz[10], size=10;
. . . . .
print(niz, size);

Ponovimo:
Pokazivai zauzimaju sredinje mjesto u oblikovanju C programa, posebno kada se radi s
nizovima.
Pokazivai i nizovi su u specijalnom odnosu. Ime niza, napisano bez uglatih zagrada
predstavlja pokazivaku konstanu koja predstavlja adresu prvog elementa niza.
Indeksna notacija ima ekvivalentni oblik pokazivake notacije. Uglate zagrade moemo
tretirati kao indeksni operator, jer kada se koriste iza imena pokazivaa, uzrokuju da se tim
pokazivaem moe operirati kao s nizom.
Nizovi se prenose u funkciju na nain da se u funkciju prenosi pokaziva na prvi element niza
(odnosno adresa niza). Poto funkcija zna adresu niza, u njoj se moe koristiti naredbe koje
mijenjaju sadraj elemenata niza bilo u indeksnoj ili u pokazivakoj notaciji.
Funkcija ne raspolae s podatkom o broju elemenata niza. Zadatak je programera da tu
vrijednost, ukoliko je potrebna, predvidi kao parametar funkcije.

Pazi !
Pokazivau se pridjeljuju adrese int *p, i;
p = i; //greka
p = &i; // ispravno
Pokazivau se indirekcijom moe pridijeliti samo int *p;
odgovarajui tip float x;

8. Nizovi i pokazivai 9
p = &x; // greka
Operator indirekcije se primjenjuje samo na pokazivake p = *i; i = *p; ?
varijable
Pokazivai moraju biti inicijalizirani na realnu adresu p = &i;
podataka, u suprotnom moe doi do pisanja po p = 5; //greka
programskoj memoriji
cout << *p;
Pazi da ne koristi NULL pokaziva! Zato? p = NULL; p = &i;
to je upisano na NULL adresi? *p = 6;

8.3 Dinamiko alociranje nizova


Najea je primjena pokazivaa u dinamikom alociranju nizova. Ono se se provodi pomou
operatora new i delete.

Sintaksa za alociranje niza je:


pokazivaka_varijabla = new oznaka_tipa[izraz];

Veliina alocirane memorije iznosi:

sizeof(oznaka_tipa)*izraz

Podrazumijeva sa da izraz mora rezultirati prirodnim brojem. Ako se alokacija izvri uspjeno, adresa
alocirane memorije se pridjeljuje pokazivakoj_varijabli. Ako se ne moe izvriti alokacija uspjeno
pokazivakoj_varijabli se pridjeljuje vrijednost 0 (NULL) ili se vri poziv funkcije za prihvat iznimki
(taj sluaj e biti pojanjen kasnije).

Primjerice, s
int n = 10;
float * A= new float[n];
alocira se memorija za n elemenata tipa float. Adresa te memorije se pridjeljuje pokazivau A.
Pomou tog pokazivaa pristupamo elementima niza na isti nain kako bi to radili sa statiki
alociranim nizovima. Primjerice, petljom
for (int i = 0; i < n; i++)
{
A[i] = i * i;
}
elementi niza dobijaju vrijednost Ai=i2.

Nakon zavrenog rada s nizom, odnosno kada se izlazi iz bloka u kojem je niz alociran, niz treba
dealocirati.

Sintaksa iskaza za dealokaciranje niza je:


delete [] pokaziva_niza;

U naem sluaju dealociranje se vri naredbom delete [] A;. Nakon dealokacije vrijednost
pokazivaa je neodreena.

Zapamtite: Pri dealociranju niza, izmeu operatora delete i pokazivaa alocirane


memorije treba obvezno napisati [].

8. Nizovi i pokazivai 10
//datoteka: dynamicArray.cpp

#include <iostream.h>
using namespace std;

int main()
{
int n; // Broj elemenata niza
float *A; // Pokaziva niza

cout << "Unesite broj elemenata niza? ";


cin >> n;

// Alocira se niz A od n float elemenata.


A = new float [n];

// A[i] = i^2.
for (int i = 0; i < n; i++)
A[i] = i * i;

// Ispis vrijednosti elemenata niza.


for (int i = 0; i < n; i++)
cout << "A[" << i << "] == " << A[i] << endl;

// Dealokacija niza.
delete [] A;
return 1;
}
Primjer ispisa:
Unesite broj elemenata niza? 7
A[0] == 0
A[1] == 1
A[2] == 4
A[3] == 9
A[4] == 16
A[5] == 25
A[6] == 36

8.4 Pokazivai na funkcije


Funkcije su takoer memorijski objekti pa se moe deklarirati i inicijalizirati pokazivae na funkcije.
Pravilo je da se za funkciju imena F, koja je deklarirana (ili definirana) u obliku:

oznaka_tipa F (list_ parametara);

pokaziva na tu funkciju, imena pF, deklarira u obliku:

oznaka_tipa ( *pF) (list_ parametara);

Ime funkcije, napisano bez zagrada predstavlja adresu funkcije, pa se pridjelom vrijednosti:

pF = F;

inicira pokaziva na funkciju pF na adresu funkcije F. Kada je pokaziva iniciran, indirekcijom


pokazivaa moe se izvriti poziv funkcije u obliku:

(*pF)(lista_argumenata);

8. Nizovi i pokazivai 11
ili jo jednostavnije, sa

pF(lista_argumenata);

jer, oble zagrade predstavljaju operator poziva funkcije (kompajler sam vri indirekciju pokazivaa,
ako se iza njega napiu oble zagrade).

Primjerice, sa double (*pMatFun)(double); deklarira se pokaziva pMathFun kojem moemo


pridijeliti adresu standardnih matematikih funkcije jer one imaju isti tip parametara i rezultata
funkcije (pr. double(sin(double)).

Pokaziva na funkciju moe biti i argument funkcije, primjerice funkcija


void Print( double (*pMatFun)(double), double x)
{
cout << pMatFun (x);
}
se moe koristiti za ispis vrijednosti matematikih funkcija.

Print(sin, 3.1); /* ispisuje se vrijednost sinusne funkcije


za vrijednost argumenta 3.1*/
Print(cos, 1.7) /* ispisuje se vrijednost kosinus funkcije
za vrijednost argumenta 1.7*/
Primjer: U programu pfunc.c se koriste pokazivai na funkciju za ispis vrijednosti standardnih i
korisniki definiranih matematikih funkcija. Izbor funkcije i argumenta funkcije se vri u interakciji s
korisnikom programa.

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

double Kvadrat (double x)


{
return x*x;
}

void PrintVal( double (*pFunc)(double), double x)


{
cout << "\nZa x=" << x
<< " dobije se " << pFunc(x) << endl;
}

int main()
{
double val=1;
char choice;

double (*pFunc)(double);

cout << "Upisi broj:";


cin >> val;

cout << "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n";


cout << "\nOdaberi 1, 2 li 3\n";

cin >> choice;


switch (choice)

8. Nizovi i pokazivai 12
{
case '1': pFunc = Kvadrat; break;
case '2': pFunc = sin; break;
case '3': pFunc = cos; break;
default: return 0;
}
PrintVal (pFunc, val);
return 0;
}

esto se koriste nizovi pokazivaa na funkciju. Primjerice, deklaracijom


#include math.h
double (*pF[4])(double) = {sin, cos, tan, exp};
deklariran je niz od 4 elementa koji sadre pokaziva na funkciju kojoj je parametar tipa double i koja
vraa vrijednost tipa double. Takoer je izvrena i inicijalizacija elemenata niza na adresu standardnih
matematikih funkcija.

Sada je naredba
x = (*pF[1])(3.14) ekvivalentna naredbi x= cos(3.14).

Primjer upotrebe niza pokazivaa na funkciju dan je u programu niz-pfun.c:

/* program: niz-pfun.c
* koristenje niza pokazivaca na funkciju
*/
#include <iostream>
#include <cmath>
using namespace std;

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

int main()
{
double val=1;
int izbor;

double (*pF[3])(double)={Kvadrat, sin, cos};

cout << "Upisi broj:";


cin >> val;

cout << "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n";


cout << "\nOdaberi 1, 2 li 3\n";
cin >> izbor;

if (izbor >=1 && izbor <=3)


cout << "\nRezultat je " << (*pF[izbor-1])(val) << endl;
return 0;
}

8.5 Prihvat greke pri alokaciji memorije

Promotrimo program:
#include <iostream>
using namespace std;

8. Nizovi i pokazivai 13
int main()
{
char* p = new char[1000000000];

cout << "kraj, p = " << long(p) << endl;


return 0;
}
U ovom programu se pokuava alocirati 1GB memorije. To nije mogue kod veine dananjih
raunala. Ovisno o implementaciji kompajlera, rezultat izvrenja programa e biti:

1. Kada se primjeni Microsoft VC kompajler program e zavriti s porukom:


Kraj, p=0
dakle, taj kompajler u sluaju da se ne moe izvriti alokacija memorije za vrijednost pokazivaa p
postavlja vrijednost 0 (NULL).

2. Kada se primijeni GNU gcc kompajler (ver.3), izvrenje programa e biti prekinuto ve pri
izvrenju prve naredbe s porukom:
"This application has requested the Runtime to terminate it in an unusual
way."
Nedostatak slobodne memorije, je iznimna situacija, pa je ostavljeno da se taj sluaj obradi posebnim
mehanizmom koji se naziva "exception handler". Njega emo upoznati kasnije.

Prema novom ISO standardu iz 1988. godine, koji je implementiran u gcc kompajleru ver. 3, izraz
alokacije ne pokree mehanizam iznimki (ne prekida se program) ve se vraa NULL pokaziva,
samo u sluaju ako se alokacija izvri tako da se koristi new operator s dodatnim argumentom imena
nothrow, primjerice:
char* p = new (nothrow) char[1000000000];
Argument nothrow je tipa nothrow_t, koji predstavlja praznu klasu, a deklariran je u <new.h>.

Ukoliko se ne primijeni argument (nothrow) poeljno je da korisnik definira i registrira funkciju za


prihvat pogreke pri alokaciji memorije.

C++ run-time sistem osigurava da se aktivira funkcija za prihvat greke koja nastaje pri alokaciji
memorije. Tu funkciju se moe redefinirati na sljedei nain:

1. Funkcija za prihvat greke ima prototip bez parametara i bez povrata vrijednosti, primjerice
moe se koristiti funkcija:
void no_memory()
{
cerr << "operator new: nema slobodne memorije\n";
exit(1);
}
koja e dojaviti da nema slobodne memorije i prekinuti izvrenje programa.

2. Funkcija za prihvat greke se mora registrirati pomou funkcije set_new_handler koja je


deklarirana u <new.h>:
set_new_handler(no_memory);
3. Nakon toga, pri bilo kojoj alokaciji memorije, ukoliko se pojavi greka, bit e pozvana
funkcija no_memory().

Kompletni primjer je dan u programu new1.cpp.

8. Nizovi i pokazivai 14
#include <iostream>
#include <new>

using namespace std;

void no_memory()
{
cerr << "operator new: nema slobodne memorije\n";
exit(1);
}

int main()
{
set_new_handler(no_memory);

char* p = new char[1000000000];

cout << "Kraj, p = " << long(p) << endl;


return 0;
}

Napomena: preporuuje se definirati prihvat greke alokacije memorije u svim


programima koji koriste vee memorijske objekte.

Kasnije emo upoznati jo kompleksniji mehanizam prihvata iznimki.

8.6 Kompleksnost deklaracija


Oito je da se u C++ jeziku koriste dosta kompleksne deklaracije. One na prvi pogled ne otkrivaju o
kakovim se tipovima radi. Sljedea tablica pokazuje deklaracije koje se esto koriste.

U deklaraciji x je ime koje predstavlja ...


T x; objekt tipa T
T x[]; (otvoreni) niz objekata tipa T
T x[n]; niz od n objekata tipa T
T *x; pokaziva na objekt tipa T
T **x; pokaziva na pokaziva tipa T
T *x[]; niz pokazivaa na objekt T
T *(x[]); niz pokazivaa na objekt T
T (*x)[]; pokaziva na niz objekata tipa T
T x(); funkcija koja vraa objekt tipa T
T *x() funkcija koja vraa pokaziva na objekt tipa T
T (*x()); funkcija koja vraa pokaziva na objekt tipa T
T (*x)(); pokaziva na funkciju koja vraa objekt tipa T
T (*x[n])(); niz od n pokazivaa na funkciju koja vraa objekt tipa T

Dalje emo pokazati:

1. Kako sistematski proitati ove deklaracije.


2. Kako se uvoenjem sinonima tipova (pomou typedef) moe znatno smanjiti kompleksnost
deklaracija.

Deklaraciju nekog identifikatora itamo ovim redom:

Identifikator je (... desna strana deklaracije) (.. lijeva strana deklaracije)

8. Nizovi i pokazivai 15
S desne strane identifikatora mogu biti uglate ili oble zagrade.
Ako su uglate zagrade itamo : identifikator je niz ,
ako su oble zagrade itamo identifikator je funkcija.

Ukoliko ima vie operatora s desne strane nastavljamo itanje po istom pravilu.
Zatim analiziramo zapis s lijeve strane identifikatora ( tu moe biti operator indirekcije i oznaka tipa).
Ako postoji operator indirekcije itamo: identifikator je (... desna strana) pokaziva na tip.
Ako postoji dvostruki operator indirekcije itamo: identifikator je (... desna strana)
pokaziva na pokaziva tip
Ukoliko je dio deklaracije napisan u zagradama onda se najprije ita znaaj zapisa u zagradama.

Primjerice, u deklaraciji
T (*x[n])(double);
najprije se ita dio deklaracije (*x[n]) koji znai da je x niz pokazivaa. Poto je iza ovog izraza s
desne strane (double) znai da je x niz pokazivaa na funkciju koja prima argument tipa double i
koja vraa tip T.

Znatno jednostavniji i razumljiviji nain pisanja kompleksnih deklaracija je da se koriste sinonimi za


kompleksne tipove, definirani pomou typedef.

Primjerice, sa typedef deklaracijom


typedef double t_Fdd(double); /* tip funkcije ... */
uvodi se oznaka tipa t_fdd koja predstavlja funkciju koja vraa double i prima argument tipa double.
Dalje moemo definirati tip t_pFdd koji je pokaziva na funkciju koja vraa double i prima
argument tipa double sa
typedef t_Fdd *t_pFdd; /* tip pokazivaa na funkciju ...*/
to je ekvivalentno deklaraciji sinonima tipa:
typedef char *(*t_pFdd)(double);
Pomou tipa t_pFdd mogli bi deklarirati niz pokazivaa na funkciju iz prethodnog programa,
deklaracijom
t_pFdd pF[3] = {Kvadrat, sin, cos};
Oito da je ovakovu deklaraciju znatno lake razumjeti.
Primjenimo li navedene typedef deklaracije u programu niz-pfun.c, dobije se

/* program: niz-pfun1.c
* koristenje niza pokazivaca na funkciju
*/
#include <iostream>
#include <cmath>
using namespace std;

typedef double t_Fdd(double); // tip funkcije koja ...


typedef t_Fdd *t_pFdd; // tip pokazivaa na funkciju koja ...

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

void PrintVal(t_pFdd pFunc, double x)


{
cout << "\nZa x=" << x
<< " dobije se " << pFunc(x) << endl;
}

8. Nizovi i pokazivai 16
int main()
{
double val=1;
int izbor;
t_pFdd pF[3] = {Kvadrat, sin, cos};

cout << "Upisi broj:";


cin >> val;

cout << "\n(1)Kvadrat \n(2)Sinus \n(3)Kosinus \n";


cout << "\nOdaberi 1, 2 li 3\n";
cin >> izbor;

if (izbor >=1 && izbor <=3)


PrintVal(pF[izbor-1], val);
return 0;
}

8. Nizovi i pokazivai 17
8.7 ASCIIZ string
Do sada smo radili s nizovima znakova u obliku tekstualnih konstanti koje smo zvali literalni string
("Hello World!"). Cilj nam je da nizove znakova tretiramo kao varijable. Na taj nain moi emo vriti
obradu tekstualnih zapisa.

U C++ jeziku se koriste dva naina rada sa stringovima.


Prvi nain je da se string tretira kao niz znakova kojem posljednji znak mora biti jednak nuli.
Ovakovi stringovi se nazivaji i ASCIIZ stringovi (ASCII +zero). U standardnoj biblioteci C
jezika postoji vie funkcija za rad s ovakovim stringovima, a deklarirane su u datoteci
<string.h> odnosno <cstring>.
Drugi je nain, standardiziran u C++ jeziku, da se koristi klasa string koja je deklarirana u
datoteci <string>.

Najprije emo upoznati kako se manipulira stringom u C jeziku. Funkcije C- jezika tretiraju string kao
za memorijski objekt koji sadri niz znakova, uz uvjet da posljednji znak u nizu mora biti jednak nuli.
Nula na kraju stringa se koristi kao oznaka kraja stringa. Primjerice, u stringu koji sadri tekst:
Hello, World!, indeks nultog znaka je 13, to je jednako duljini stringa. Ovaj string u memoriji
zauzima 14 bajta.
0 1 2 3 4 5 6 7 8 9 0 1 2 3

'H' 'e' 'l' 'l' 'o' ', ' ' ' 'W' 'o' 'r' 'l' 'd' '!' '\0'

Indeks nultog znaka je upravo jednak broju znakova u stringu.

Prema ovoj definiciji ASCIIZ string je svaki niz koji se deklarira kao:
niz znakova (npr. char str[10];) , ili kao
pokaziva na znak (char *str;),
pod uvjetom da se pri inicijalizaciji niza, i kasnije u radu s nizom uvijek vodi rauna o tome da
poljednji element niza mora biti jednak nuli.

Pogledajmo primjer u kojem se string tretira kao obini niz znakova.

//Datoteka; hello2.cpp
//Prvi C++ program - drugi put.
#include <iostream>
#include <cstring> // deklaracija funkcije strlen
using namespace std;

int main()
{
char hello[14] = { 'H', 'e', 'l', 'l', 'o', ',', ' ',
'W', 'o', 'r', 'l', 'd', '!', '\0' };
cout << hello << endl;
cout << "Duljina stringa je " << strlen(hello) << endl;
return 0;
}

Nakon izvrenja programa, dobije se poruka:

Hello, World!
Duljina stringa je 13.

8. Nizovi i pokazivai 18
Ovaj program vri istu funkciju kao i na prvi C-program, tiska poruku: Hello, World!. Prvo je
definirana i inicijalizirana varijabla hello. Ona je tipa znakovnog niza od 14 elemenata.
Inicijalizacijom se u prvih 13 elemenata upisuju znakovi (Hello World), posljednji element se inicira
na nultu vrijednost. Ispis ove varijable . Za ispis duljine stringa koritena je standardna funkcija
size_t strlen(char *s);
koja vraa vrijednost duljine stringa. Kao argument funkcija prihvaa vrijednost pokaziva na char,
odnosno adresu poetnog elementa stringa. (size_t je sinonim za unsigned).

Funkcija strlen se moe implementriati na slijedei nain:

1.verzija:

unsigned strlen(char s[])


{
unsigned i=0;
while (s[i] != '\0') // petlja se prekida kada je s[i]==0, inae
i++; // inkrementira se broja znakova, koji na kraju
return i; // sadri duljinu stringa (bez nultog znaka)
}

2.verzija: pomou pokazivaa

unsigned strlen(char *s)


{
unsigned len = 0;
while (*s++ != '\0') // petlja se prekida kada je *s==0, inae
len++; // inkrementira se broja znakova i pokaziva
return len; // len sadri duljinu stringa (bez nultog znaka)
}
String se moe inicijalizirati navoenjem znakova od kojih se sastoji, kao u prijanjem primjeru, ili
pomou literalne konstante:
char hello[] = "Hello, World!"; // kompajler rezervira mjesto u memoriji

Primjer:

//Datoteka; hello3.cpp
//Prvi C++ program - drugi put.
#include <iostream>
using namespace std;

unsigned strlen(char *s)


{
unsigned len = 0;
while (*s++ != '\0') // petlja se prekida kada je *s==0, inae
len++; // inkrementira se broja znakova i pokaziva
return len; // len sadri duljinu stringa (bez nultog znaka)
}

int main()
{
char hello[] = "Hello, world!";
cout << hello << endl;
cout << "Duljina stringa je " << strlen(hello) << endl;
cout << "Zauzece memorije je" << sizeof(hello)*sizeof(char)
<< " bajta" << endl;
return 0;
}

8. Nizovi i pokazivai 19
Nakon izvrenja programa, dobije se poruka:
Hello, World!
Duljina stringa je 13
Zauzece memorije je 14 bajta

Elementi stringa se mogu mijenjati naredbom pridjele vrijednosti, primjerice, naredbe:

hello[0] = 'h';
hello[6] = 'w';

mijenjaju sadraj stringa u "hello world";

Ako se procjenjuje da e trebati vie mjesta za string, nego se to navodi inicijalnim literalnim
stringom, tada treba eksplicitno navesti dimenziju stringa. Deklaracijom,
char hello[50] = "Hello, World!";
kompajler rezervira 50 mjesta u memoriji te inicira prvih 14 elemenata na "Hello, World!", zakljuno s
nultim znakom.

Dozvoljeno je literalni string koristiti kao referencu niza:


cout << "0123456789ABCDEF"[n]; <=> char digits[] ="0123456789ABCDEF";
cout << digits[n];

Slijedea dva primjera pokazuju rad s stringovima. Bit e opisano kako se mogu napisati standardne
funkcije za kopiranje stringa (strcpy) i usporedbu dva stringa (strcmp).
Funkcija strcpy kopira znakove stringa src u string dest, a vraa adresu od dest.

1. Verzija s nizovima:
char *strcpy( char dst[], char src[])
{
int i;
for (i = 0; src[i] != \0; i++)
dst[i] = src[i];
dst[i] = \0;
return dst;
}
2. Verzija s pokazivakim varijablama
char *strcpy(char *dest, const char *src)
{
char *dp = dest;
while((*dp++ = *src++) != '\0') // kopira se i \0
; /*prazna naredba*/
return dest;
}

Ova verzija s inkrementiranjem pokazivaa e se bre izvravati od verzije koja je zapisana u


indeksnoj notaciji.
Funkcija strcmp slui usporedbi dva stringa s1 i s2. Deklarirana je s:
int strcmp(const char *s1, const char *s2)
Funkcija vraa vrijednost 0 ako je sadraj oba stringa isti, negativnu vrijednost ako je s1 leksiki
manji od s2, ili pozitivnu vrijednost ako je s1 leksiki vei od s2. Leksika usporedba se izvrava
znak po znak prema kodnoj vrijednosti ASCII standarda.

8. Nizovi i pokazivai 20
/*1. verzija*/
int strcmp( char s1[], char s2[])
{
int i = 0;
while(s1[i] == s2[i] && s1[i] != \0)
i++;
if (s1[i] < s2[i])
return -1;
else if (s1[i] > s2[i])
return +1;
else
return 0;
}
Najprije se u for petlji usporeuje da li su znakovi s istim indeksom isti i da li je dostignut kraj niza
(znak '\0'). Kad petlja zavri, ako su oba znaka jednaka, to znai da su i svi prethodni znakovi jednaki.
U tom sluaju funkcija vraa vrijednost 0. U suprotnom funkcija vraa 1 ili 1 ovisno o numerikom
kodu znakova koji se razlikuju.
/*2. verzija*/
int strcmp(char *s1, char *s2)
{
for(; *s1 == *s2; s1++, s2++)
{
if(*s1 == '\0')
return 0;
}
return *s1 - *s2;
}
Verzija s pokazivaima predstavlja optimiranu verziju prethodne funkcije. Uoite da se u u for petlji
ne koristi izraz inicijalizacije petlje. Najprije se usporeuju znakovi na koje pokazuju s1 i s2. Ako su
znakovi isti inkrementira se vrijednost oba pokazivaa. Tijelo petlje e se izvriti samo u sluaju ako
su stringovi isti (tada da s1 i s2 konano pokazuju na znak '\0'). U suprotnom petlja se prekida, a
funkcija vraa numeriku razliku ASCII koda znakova koji se razlikuju.

Napomena: verzije s inkrementiranjem pokazivaa esto rezultiraju brim izvrenjem


programa, ali predstavljaju programe koje je neto tee razumjeti od verzija s indeksiranim
nizovima.

8.7.1 Standardne funkcije za rad s ASCIIZ stringovima


U standardnoj biblioteci postoji niz funkcija za manipuliranje sa stringovima . One su deklarirane u
datoteci <cstring> ili <string.h>. Funkcija im je:

size_t strlen(const char *s)


Vraa duljinu stringa s.
char *strcpy(char *s, const char *t)
Kopira string t u string s, ukljuujui '\0'; vraa s.
char *strncpy(char *s, const char *t, size_t n)
Kopira najvie n znakova stringa t u s; vraa s. Dopunja string s sa '\0' znakovima ako
t ima manje od n znakova.
char *strcat(char *s, const char *t)
Dodaje string t na kraj stringa s; vraa s.
char *strncat(char *s, const char *t, size_t n)

8. Nizovi i pokazivai 21
Dodaje najvie n znakova stringa t na string s, i znak '\0'; vraa s.
int strcmp(const char *s, const char *t)
Usporeuje string s sa stringom t, vraa <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t.
Usporedba je leksikografska, prema ASCII "abecedi".
int strncmp(const char *s, const char *t, size_t n)
Usporeuje najvie n znakova stringa s sa stringom t; vraa <0 ako je s<t, 0 ako je s==t,
ili >0 ako je s>t.
char *strchr(const char *s, int c)
Vraa pokaziva na prvu pojavnost znaka c u stringu s, ili NULL znak ako c nije sadran u
stringu s.
char *strrchr(const char *s, int c)
Vraa pokaziva na zadnju pojavnost znaka c u stringu s, ili NULL znak ako c nije sadran u
stringu s.
char *strstr(const char *s, const char *t)
Vraa pokaziva na prvu pojavu stringa t u stringu s, ili NULL ako string s ne sadri string t.
size_t strspn(const char *s, const char *t)
Vraa duljinu prefiksa stringa s koji sadri znakove koji ine string t.
size_t strcspn(const char *s, const char *t)
Vraa duljinu prefiksa stringa s koji sadri znakove koji nisu prisutni u stringu t.
char *strpbrk(const char *s, const char *t)
Vraa pokaziva na prvu pojavu bilo kojeg znaka iz string t u stringu s, ili NULL ako nije
prisutan ni jedan znak iz string t u stringu s.
char *strerror(int n)
Vraa pokaziva na string kojeg interno generira kompajler za dojavu greke u nekim
sistemskim operacijama. Argument je obino globalna varijabla errno, iju vrijednost takoer
postavlja kompajler pri sistemskim operacijama.
char *strtok(char *s, const char *sep)
strtok je funkcija kojom se moe izvriti razlaganje stringa na niz leksema koji su
razdvojeni znakovima-separatorima. Skup znakova-separatora se zadaje u stringu sep.
Funkcija vraa pokaziva na leksem ili NULL ako nema leksema.

Koritenje funkcije strtok je specifino jer u strigu moe biti vie leksema, a ona vraa pokaziva na
jedan leksem. Da bi se dobili sljedei leksemi treba ponovo zvati istu funkciju, ali s prvim
argumentom jednakim NULL. Primjerice, za string
char * s = "Prvi drugi,treci";
ako odaberemo znakove separatore: razmak, tab i zarez, tada sljedeim iskazi daju ispis tri leksema
(Prvi drugi i treci):
char *leksem = strtoken(s, " ,\t"); /* dobavi prvi leksem */
while( leksem != NULL) { /* ukoliko postoji */
printf("", leksem); /* ispii ga i */
lexem = strtok(NULL, " ,\t"); /* dobavi sljedei leksem */
} /* pa ponovi postupak */

8. Nizovi i pokazivai 22

You might also like