22
8. Nizovi i pokazivači Naglasci: Jednodimenzionalni nizovi Dvodimenzionalni nizovi Nizovi objekata Ekvivalentnost pokazivačke i indeksne notacije nizova Operacije s pokazivačima Pokazivači na funkcije Dinamičko 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 korisnički definirani objekti . Označavaju se imenom niza i cjelobrojnom izrazom – indeksom - koji označava poziciju elementa u nizu. Indeks niza se zapisuje u uglatim zagradama iza imena niza. Primjerice, x[3] označava 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] označava četvrti element niza. S elementima niza se manipulira kao s običnim skalarnim varijablama ili objektima, ovisno o načinu kako je izvršena 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 pokazivači 1

C++ Nizovi

Embed Size (px)

Citation preview

Page 1: C++ Nizovi

8. Nizovi i pokazivači Naglasci:

• Jednodimenzionalni nizovi • Dvodimenzionalni nizovi • Nizovi objekata • Ekvivalentnost pokazivačke i indeksne notacije nizova • Operacije s pokazivačima • Pokazivači na funkcije • Dinamičko 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 korisnički definirani objekti . Označavaju se imenom niza i cjelobrojnom izrazom – indeksom - koji označava poziciju elementa u nizu. Indeks niza se zapisuje u uglatim zagradama iza imena niza. Primjerice, x[3] označava 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] označava četvrti element niza. S elementima niza se manipulira kao s običnim skalarnim varijablama ili objektima, ovisno o načinu kako je izvršena 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 pokazivači 1

Page 2: C++ Nizovi

data[2+3] = data[0]; int x=5; data[x] = 7;

U C jeziku se ne vrši provjera da li je vrijednost indeksnog izraza unutar predviđenog intervala. To je u nadležnosti programera. Elementi niza zauzimaju sukscesivne lokacije u memoriji. Vrijedi pravilo adresa(data[n])=adresa(data[0]) + n*sizeof(data[0]))

memorija adresa sadržaj 1000 data[0] 1004 data[1] 1008 data[2] 1012 data[3] 1016 data[4] 1020 data[6] 1024 data[5] 1028 data[7] 1032 data[8] Napomena.(int zauzima 4 bajta)

Deklaracijom niza se rezervira potrebna memorija, ali se ne vrši se i inicijalizacija početnih 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 može inicijalizirati i s deklaracijom slijedećeg tipa: int data[9]= {1,2,23,4,32,5,7,9,6};

gdje se unutar vitičastih zagrada navodi lista konstanti. Ako se inicijaliziraju svi potrebni elementi niza, tada nije nužno 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 možemo dobiti naredbom:

int numelements = sizeof(data)/sizeof(int);

Niz se može 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.

Sadržaj datoteke "ocjene.dat” neka čini slijedeći 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 pokazivači 2

Page 3: C++ Nizovi

Treba izraditi program imena hist.cpp pomoću 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 izvršeno “preusmjerenje” standardnog ulaza. Efekt preusmjeravanja je da se sadržaj datoteke ocjene.dat tretira kao da je unesen sa standardnog ulaza.

Analiza programa hist.c:

8. Nizovi i pokazivači 3

Page 4: C++ Nizovi

int i, counts[10], ocjena; for (i = 0; i < 10; i++) counts[i] = 0;

Prvo se deklarira niz count od 10 elemenata tipa int.

Pomoću for petlje vrijednost elemenata niza inicijalizira se na vrijednost 0.

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

Očitava se ocjena i inkrementira odgovarajući element niza counts; cin vraća vrijednost EOF kada se očita znak za kraj datoteke -EOF (ako je unos s tipkovnice znak EOF se unosi s Ctrl-Z <enter>)

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

For petljom se ispituje vrijednost niza count od counts[9] do counts[0], te ispisuje linija histograma pomoću znaka zvjezdice.

Kako se niz prenosi u funkciju Prijašnji primjer programa histogram.cpp bit će modificiran, na način da će se koriti tri funkcije:

- pojavnost ocjena u nizu count bilježit će se funkcijom record, - crtanje zvjezdica obavljat će funkcija printstars, a - crtanje histograma obavljat će funkcija printhistogram.

// datoteka: histf.cpp #include <iostream> using namespace std; void record( int ocjena, int counts[]) { counts[ocjena-1]++; } void printstars(int n) { while (n-- > 0) cout << '*'; cout << '\n'; } void printhistogram(int counts[]) { for (int i = 10; i > 0; i--) { cout << i << '\t'; printstars(counts[i-1]); } } int main(void) { int ocjena;

void record( int ocjena, int counts[])

Kada se jednodimenzionalni niz pojavljuje u deklaraciji ili definiciji funkcije, tada se ne navodi dimenzija niza. Uočimo de se vrijednost elemenata niza može mijenjati unutar funkcije. Očito je da se niz ne prenosi po vrijednosti (by value), jer tada to ne bi bilo moguće. Pravilo je: Nizovi se u funkciju prenose kao memorijske reference (by reference). Kompajler memorijsku referencu (adresu) niza pamti "u njegovom imenu" stoga se pri pozivu funkcije navodi samo ime, bez uglatih zagrada.

8. Nizovi i pokazivači 4

Page 5: C++ Nizovi

int counts[10] = {0}; while (cin >> ocjena) { if(ocjena <1 || ocjena >10) break; record(ocjena, counts); } cout << "Histogram" << '\n'; printhistogram(counts); return 0; }

Višedimenzionalni nizovi Višedimenzionalnim nizovima se pristupa preko dva ili više indeksa. Npr. deklaracijom:

int x[3][4];

definira se dvodimenzionalni niz koji ima 3 x 4 = 12 elemenata. Deklaraciju se može čitati i ovako: definiran je niz kojem su elementi 3 niza od 4 elementa tipa int. Elementima se pristupa preko dva indeksa. U slijedećem 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;

U C jeziku dvodimenzionalni nizovi, koji opisuju matrice, u memoriji su složeni po redovima matrice. Najprije prvi redak, zatim drugi, itd.. U radu s matricama nije potrebno razmišljati kako je niz složen u memoriji, jer se elementima pristupa preko dva indeksa: prvi je oznaka redka, a drugi je oznaka stupca matrice. matrični prikaz

x[0][0] x[0][1] x[0][2] x[0][3]

x[1][0] x[1][1] x[1][2] x[1][3]

x[2][0] x[2][1] x[2][2] x[2][3]

memorija adresa sadržaj 1000 x[ 0][ 0] 1004 x[ 0][ 1] 1008 x[ 0][ 2] 1012 x[ 0][ 3] 1016 x[ 1][ 0] 1020 x[ 1][ 1] 1024 x[ 1][ 2] 1028 x[ 1][ 3] 1032 x[ 2][ 0] 1036 x[ 2][ 1] 1040 x[ 2][ 2] 1044 x[ 2][ 3]

Višedimenzionalni niz se može inicijalizirati već u samoj deklaraciji, npr. int x[3][4] = { { 1, 21, 14, 8}, {12, 7, 41, 2}, { 1, 2, 4, 3}};

Navođenje unutarnjih vitičastih zagrada je opciono, pa se može pisati i int x[3][4] = {1, 21, 14, 8, 12, 7, 41, 2, 1, 2, 4, 3};

Ovakovi stil se ne preporučuje, jer je teže uočiti raspored elemenata po redovima i stupcima.

8. Nizovi i pokazivači 5

Page 6: C++ Nizovi

Pri deklariranju argumenta funkcije koji su višedimenzionalnih nizovi također se ne navodi prva dimenzija, ali ostale dimenzije treba deklarirati, kako bi kompajler znao kojim su redom elementi složeni u memoriji. Primjer, definirajmo funkciju kojom se računa 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; }

Uočimo, da su u definiciji funkcije navedeni i parametri nrows i ncols koji opisuju broj redaka i stupaca matrice. Očito je da ovo nije baš najbolji način za rad s matricama, jer je korištenje ove funkcije moguće samo za matrice koje imaju 4 stupca. Kasnije ćemo pokazati kako se može ova funkcija modificirati da vrijedi za matrice proizvoljnih dimenzija.

Nizovi objekata Elementi niza mogu biti bilo kojeg tipa uključujući 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 izvrši i poziv predodređenog konstruktora za svaki element niza. Članskim funkcijama objekata pristupa se pomoću točka operatora, primjerice s

counts[3].get_count()

dobije se vrijednost izbroja brojača koji je četvrti u nizu counts. Primjer primjene niza brojača dan je u datoteci hist-obj.cpp. Pokazano je da se problem izrade histograma može elegantno riješiti korištenjem klase Counter. Specifikacija klase Counter zapisana je u datoteci counter.h, a implementacija funkcija u datoteci counter.cpp. // datoteka: hist-obj.cpp // realizacija histograma pomoću klase Counter #include <iostream> #include <counter.h> // specifikacija brojača 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 pokazivači 6

Page 7: C++ Nizovi

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; } Izvršni program se dobije komandom

c:> cl /GX hist_obj.cpp counter.cpp

Usporedimo li ovaj program s programom histf.cpp uočit ćemo samo tri razlike: realizacija histograma pomoću cjelobrojnog niza

realizacija histograma pomoću 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 brojača tu funkciju obavlja predodređeni konstruktor klase. Zatim slijede dvije razlike zbog različitog načina pristupa elementima niza. Očito je da će se verzija s klasom Counter nešto sporije izvršavati, jer se funkcija incr_count() sporije izvršava od prostog inkrementiranja cjelobrojne varijable. S druge strane gledano, verzija s klasom Counter je razumljivije napisana, i može se jasnije, bez posebnih programskih komentara, shvatiti o čemu se radi. Ovaj posebni slučaj zapravo ukazuje na činjenicu da se objektno-zasnovani programi često sporije izvršavaju nego se to može postiću programiranjem niske razine u C jeziku. Često se govori da objektno zasnovani programi imaju “overhead” – odnosno da izvršavaju više instrukcija nego bi se to moglo postići programiranjem niske razine. Kod većih programa ovaj “overhead” nije značajan jer se povećanjem apstrakcije, koju omogućava objektno programiranje, dobijaju elegantnija i sigurnija rješenja, a nerijetko i brže izvršenje programa, nego je to moguće s klasičnim proceduralnim programiranjem.

8.2 Pokazivači i nizovi Ako se deklarira pokazivač istog tipa kao i elementi niza, onda se njemu može pridijeliti adresa bilo kojeg elementa niza. Primjerice, iskazom int *p= &a[3] adresa elementa indeksa 3 se pridjeljuje pokazivaču p. Stoga, kažemo da se pomoću pokazivača može se "šetati po nizu".

8. Nizovi i pokazivači 7

Page 8: C++ Nizovi

Često će se koristiti iskazi oblika: int a[10], i, x; int *p, *pi; p = &a[0]; // inicijalizacija pokazivača x = *p; // x-u se pomoću pokazivača 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 slijedećim pravilima: 1. Ime niza ima se tretira se kao "pokazivačka konstanta" koja predstavlja adresu prvog elementa niza. Naredba

p = a;

vrijednost pokazivača p postavlja na adresu elementa a[0]. Ovo je ekvivalentno naredbi: p = &a[0];

2. Pravilo pokazivačke 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 pokazivačke notacije niza.

Ime niza napisano bez zagrada predstavlja pokazivač na prvi element. Stoga, ako je deklariran niz array[], tada izraz *array označava prvi element, *(array + 1) označava drugi element, itd. Poopćimo li ovo pravilo na cijeli niz array, vrijede slijedeći odnosi:

*(array) ⇔ array[0] *(array + 1) ⇔ array[1] *(array + 2) ⇔ array[2] ... *(array + n) ⇔ array[n]

S pokazivačima se također može 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 pokazivačka aritmetika s memorijskim referencama niza : array++; je nedozvoljen izraz, jer se ne može mijenjati konstanta p++; je dozvoljen izraz, jer je pokazivač p varijabla

Primjer "šetnje" po nizu korištenjem pokazivača.

8. Nizovi i pokazivači 8

Page 9: C++ Nizovi

int *p = a;

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

cout << *p++ << endl;

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

cout << a[i] << endl;

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

Pokazivači 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. Kažemo da se niz u funkciju prenosi po adresi - pomoću pokazivača. Primjer: obje funkcije print imaju isto djelovanje i mogu se pozvati s istim argumentima:

void print(int x[], int size)

{

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

cout << x[i] << endl;

}

void print( int *x, int size)

{

while (size-- > 0)

cout << *x++ << endl;

}

/* poziv funkcije print */

int niz[10], size=10;

. . . . .

print(niz, size);

Ponovimo:

• Pokazivači zauzimaju središnje mjesto u oblikovanju C programa, posebno kada se radi s nizovima.

• Pokazivači i nizovi su u specijalnom odnosu. Ime niza, napisano bez uglatih zagrada predstavlja pokazivačku konstanu koja predstavlja adresu prvog elementa niza.

• Indeksna notacija ima ekvivalentni oblik pokazivačke notacije. Uglate zagrade možemo tretirati kao indeksni operator, jer kada se koriste iza imena pokazivača, uzrokuju da se tim pokazivačem može operirati kao s nizom.

• Nizovi se prenose u funkciju na način da se u funkciju prenosi pokazivač na prvi element niza (odnosno adresa niza). Pošto funkcija zna adresu niza, u njoj se može koristiti naredbe koje mijenjaju sadržaj elemenata niza bilo u indeksnoj ili u pokazivačkoj notaciji.

• Funkcija ne raspolaže s podatkom o broju elemenata niza. Zadatak je programera da tu vrijednost, ukoliko je potrebna, predvidi kao parametar funkcije.

Pazi !

Pokazivaču se pridjeljuju adrese int *p, i;

p = i; //greška

p = &i; // ispravno

Pokazivaču se indirekcijom može pridijeliti samo odgovarajući tip

int *p;

float x;

8. Nizovi i pokazivači 9

Page 10: C++ Nizovi

p = &x; // greška

Operator indirekcije se primjenjuje samo na pokazivačke varijable

p = *i; i = *p; ?

Pokazivači moraju biti inicijalizirani na realnu adresu podataka, u suprotnom može doći do pisanja po programskoj memoriji

p = &i;

p = 5; //greška

cout << *p;

Pazi da ne koristiš NULL pokazivač! Zašto? Što je upisano na NULL adresi?

p = NULL; p = &i;

*p = 6;

8.3 Dinamičko alociranje nizova Najčešća je primjena pokazivača u dinamičkom alociranju nizova. Ono se se provodi pomoću operatora new i delete. Sintaksa za alociranje niza je: pokazivačka_varijabla = new oznaka_tipa[izraz]; Veličina alocirane memorije iznosi: sizeof(oznaka_tipa)*izraz Podrazumijeva sa da izraz mora rezultirati prirodnim brojem. Ako se alokacija izvrši uspješno, adresa alocirane memorije se pridjeljuje pokazivačkoj_varijabli. Ako se ne može izvršiti alokacija uspješno pokazivačkoj_varijabli se pridjeljuje vrijednost 0 (NULL) ili se vrši poziv funkcije za prihvat iznimki (taj slučaj će biti pojašnjen kasnije). Primjerice, s

int n = 10; float * A= new float[n];

alocira se memorija za n elemenata tipa float. Adresa te memorije se pridjeljuje pokazivaču A. Pomoću tog pokazivača pristupamo elementima niza na isti način kako bi to radili sa statički alociranim nizovima. Primjerice, petljom

for (int i = 0; i < n; i++) { A[i] = i * i; }

elementi niza dobijaju vrijednost Ai=i2. Nakon završenog 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 našem slučaju dealociranje se vrši naredbom delete [] A;. Nakon dealokacije vrijednost pokazivača je neodređena.

Zapamtite: Pri dealociranju niza, između operatora delete i pokazivača alocirane memorije treba obvezno napisati [].

8. Nizovi i pokazivači 10

Page 11: C++ Nizovi

//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 Pokazivači na funkcije Funkcije su također memorijski objekti pa se može deklarirati i inicijalizirati pokazivače 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 pokazivača može se izvršiti poziv funkcije u obliku: (*pF)(lista_argumenata);

8. Nizovi i pokazivači 11

Page 12: C++ Nizovi

ili još jednostavnije, sa pF(lista_argumenata); jer, oble zagrade predstavljaju operator poziva funkcije (kompajler sam vrši indirekciju pokazivača, ako se iza njega napišu oble zagrade). Primjerice, sa double (*pMatFun)(double); deklarira se pokazivač pMathFun kojem možemo pridijeliti adresu standardnih matematičkih funkcije jer one imaju isti tip parametara i rezultata funkcije (pr. double(sin(double)). Pokazivač na funkciju može biti i argument funkcije, primjerice funkcija

void Print( double (*pMatFun)(double), double x) { cout << pMatFun (x); }

se može koristiti za ispis vrijednosti matematičkih 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 pokazivači na funkciju za ispis vrijednosti standardnih i korisnički definiranih matematičkih funkcija. Izbor funkcije i argumenta funkcije se vrši 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 pokazivači 12

Page 13: C++ Nizovi

{ 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 pokazivača na funkciju. Primjerice, deklaracijom

#include math.h double (*pF[4])(double) = {sin, cos, tan, exp};

deklariran je niz od 4 elementa koji sadrže pokazivač na funkciju kojoj je parametar tipa double i koja vraća vrijednost tipa double. Također je izvršena i inicijalizacija elemenata niza na adresu standardnih matematičkih funkcija. Sada je naredba x = (*pF[1])(3.14) ekvivalentna naredbi x= cos(3.14). Primjer upotrebe niza pokazivača 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 greške pri alokaciji memorije Promotrimo program:

#include <iostream> using namespace std;

8. Nizovi i pokazivači 13

Page 14: C++ Nizovi

int main() { char* p = new char[1000000000]; cout << "kraj, p = " << long(p) << endl; return 0; }

U ovom programu se pokušava alocirati 1GB memorije. To nije moguće kod većine današnjih računala. Ovisno o implementaciji kompajlera, rezultat izvršenja programa će biti: 1. Kada se primjeni Microsoft VC kompajler program će završiti s porukom:

Kraj, p=0

dakle, taj kompajler u slučaju da se ne može izvršiti alokacija memorije za vrijednost pokazivača p postavlja vrijednost 0 (NULL). 2. Kada se primijeni GNU gcc kompajler (ver.3), izvršenje programa će biti prekinuto već pri izvršenju 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 slučaj 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 pokreće mehanizam iznimki (ne prekida se program) već se vraća NULL pokazivač, samo u slučaju ako se alokacija izvrši 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) poželjno je da korisnik definira i registrira funkciju za prihvat pogreške pri alokaciji memorije. C++ run-time sistem osigurava da se aktivira funkcija za prihvat greške koja nastaje pri alokaciji memorije. Tu funkciju se može redefinirati na sljedeći način:

1. Funkcija za prihvat greške ima prototip bez parametara i bez povrata vrijednosti, primjerice može se koristiti funkcija: void no_memory() { cerr << "operator new: nema slobodne memorije\n"; exit(1); }

koja će dojaviti da nema slobodne memorije i prekinuti izvršenje programa. 2. Funkcija za prihvat greške se mora registrirati pomoću 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 greška, bit će pozvana funkcija no_memory().

Kompletni primjer je dan u programu new1.cpp.

8. Nizovi i pokazivači 14

Page 15: C++ Nizovi

#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: preporučuje se definirati prihvat greške alokacije memorije u svim programima koji koriste veće memorijske objekte.

Kasnije ćemo upoznati još kompleksniji mehanizam prihvata iznimki.

8.6 Kompleksnost deklaracija Očito je da se u C++ jeziku koriste dosta kompleksne deklaracije. One na prvi pogled ne otkrivaju o kakovim se tipovima radi. Sljedeća 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 pokazivača na objekt T T *(x[]); niz pokazivača na objekt T T (*x)[]; pokazivač na niz objekata tipa T T x(); funkcija koja vraća objekt tipa T T *x() funkcija koja vraća pokazivač na objekt tipa T T (*x()); funkcija koja vraća pokazivač na objekt tipa T T (*x)(); pokazivač na funkciju koja vraća objekt tipa T T (*x[n])(); niz od n pokazivača na funkciju koja vraća objekt tipa T

Dalje ćemo pokazati:

1. Kako sistematski pročitati ove deklaracije. 2. Kako se uvođenjem sinonima tipova (pomoću typedef) može znatno smanjiti kompleksnost

deklaracija.

Deklaraciju nekog identifikatora čitamo ovim redom: Identifikator je (... desna strana deklaracije) (.. lijeva strana deklaracije)

8. Nizovi i pokazivači 15

Page 16: C++ Nizovi

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 više operatora s desne strane nastavljamo čitanje po istom pravilu. Zatim analiziramo zapis s lijeve strane identifikatora ( tu može 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 značaj zapisa u zagradama. Primjerice, u deklaraciji

T (*x[n])(double);

najprije se čita dio deklaracije (*x[n]) koji znači da je x niz pokazivača. Pošto je iza ovog izraza s desne strane (double) znači da je x niz pokazivača na funkciju koja prima argument tipa double i koja vraća tip T. Znatno jednostavniji i razumljiviji način pisanja kompleksnih deklaracija je da se koriste sinonimi za kompleksne tipove, definirani pomoću typedef. Primjerice, sa typedef deklaracijom

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

uvodi se oznaka tipa t_fdd koja predstavlja funkciju koja vraća double i prima argument tipa double. Dalje možemo definirati tip t_pFdd koji je pokazivač na funkciju koja vraća double i prima argument tipa double sa

typedef t_Fdd *t_pFdd; /* tip pokazivača na funkciju ...*/

što je ekvivalentno deklaraciji sinonima tipa: typedef char *(*t_pFdd)(double);

Pomoću tipa t_pFdd mogli bi deklarirati niz pokazivača na funkciju iz prethodnog programa, deklaracijom

t_pFdd pF[3] = {Kvadrat, sin, cos};

Očito da je ovakovu deklaraciju znatno lakše 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 pokazivača 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 pokazivači 16

Page 17: C++ Nizovi

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 pokazivači 17

Page 18: C++ Nizovi

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 način moći ćemo vršiti obradu tekstualnih zapisa. U C++ jeziku se koriste dva načina rada sa stringovima.

• Prvi način 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 više funkcija za rad s ovakovim stringovima, a deklarirane su u datoteci <string.h> odnosno <cstring>.

• Drugi je način, 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 sadrži 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 sadrži 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 računa o tome da poljednji element niza mora biti jednak nuli. Pogledajmo primjer u kojem se string tretira kao obični 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 izvršenja programa, dobije se poruka: Hello, World! Duljina stringa je 13.

8. Nizovi i pokazivači 18

Page 19: C++ Nizovi

Ovaj program vrši 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 korištena je standardna funkcija

size_t strlen(char *s);

koja vraća vrijednost duljine stringa. Kao argument funkcija prihvaća vrijednost pokazivač na char, odnosno adresu početnog elementa stringa. (size_t je sinonim za unsigned). Funkcija strlen se može implementriati na slijedeći način:

1.verzija: unsigned strlen(char s[]) { unsigned i=0; while (s[i] != '\0') // petlja se prekida kada je s[i]==0, inače i++; // inkrementira se brojač znakova, koji na kraju return i; // sadrži duljinu stringa (bez nultog znaka) } 2.verzija: pomoću pokazivača unsigned strlen(char *s) { unsigned len = 0; while (*s++ != '\0') // petlja se prekida kada je *s==0, inače len++; // inkrementira se brojač znakova i pokazivač return len; // len sadrži duljinu stringa (bez nultog znaka) }

String se može inicijalizirati navođenjem znakova od kojih se sastoji, kao u prijašnjem primjeru, ili pomoću 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, inače len++; // inkrementira se brojač znakova i pokazivač return len; // len sadrži 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 pokazivači 19

Page 20: C++ Nizovi

Nakon izvršenja 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 sadržaj stringa u "hello world"; Ako se procjenjuje da će trebati više 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!", zaključno s nultim znakom. Dozvoljeno je literalni string koristiti kao referencu niza: cout << "0123456789ABCDEF"[n]; <=> char digits[] ="0123456789ABCDEF"; cout << digits[n];

Slijedeća 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 vraća 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 pokazivačkim 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 pokazivača će se brže izvršavati od verzije koja je zapisana u indeksnoj notaciji. Funkcija strcmp služi usporedbi dva stringa s1 i s2. Deklarirana je s:

int strcmp(const char *s1, const char *s2)

Funkcija vraća vrijednost 0 ako je sadržaj oba stringa isti, negativnu vrijednost ako je s1 leksički manji od s2, ili pozitivnu vrijednost ako je s1 leksički veći od s2. Leksička usporedba se izvršava znak po znak prema kodnoj vrijednosti ASCII standarda.

8. Nizovi i pokazivači 20

Page 21: C++ Nizovi

/*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 uspoređuje da li su znakovi s istim indeksom isti i da li je dostignut kraj niza (znak '\0'). Kad petlja završi, ako su oba znaka jednaka, to znači da su i svi prethodni znakovi jednaki. U tom slučaju funkcija vraća vrijednost 0. U suprotnom funkcija vraća 1 ili –1 ovisno o numeričkom 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 pokazivačima predstavlja optimiranu verziju prethodne funkcije. Uočite da se u u for petlji ne koristi izraz inicijalizacije petlje. Najprije se uspoređuju znakovi na koje pokazuju s1 i s2. Ako su znakovi isti inkrementira se vrijednost oba pokazivača. Tijelo petlje će se izvršiti samo u slučaju ako su stringovi isti (tada da s1 i s2 konačno pokazuju na znak '\0'). U suprotnom petlja se prekida, a funkcija vraća numeričku razliku ASCII koda znakova koji se razlikuju. Napomena: verzije s inkrementiranjem pokazivača često rezultiraju bržim izvršenjem programa, ali predstavljaju programe koje je nešto teže 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)

Vraća duljinu stringa s.

char *strcpy(char *s, const char *t)

Kopira string t u string s, uključujući '\0'; vraća s.

char *strncpy(char *s, const char *t, size_t n)

Kopira najviše n znakova stringa t u s; vraća 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; vraća s.

char *strncat(char *s, const char *t, size_t n)

8. Nizovi i pokazivači 21

Page 22: C++ Nizovi

Dodaje najviše n znakova stringa t na string s, i znak '\0'; vraća s.

int strcmp(const char *s, const char *t)

Uspoređuje string s sa stringom t, vraća <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)

Uspoređuje najviše n znakova stringa s sa stringom t; vraća <0 ako je s<t, 0 ako je s==t, ili >0 ako je s>t.

char *strchr(const char *s, int c)

Vraća pokazivač na prvu pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

char *strrchr(const char *s, int c)

Vraća pokazivač na zadnju pojavnost znaka c u stringu s, ili NULL znak ako c nije sadržan u stringu s.

char *strstr(const char *s, const char *t)

Vraća pokazivač na prvu pojavu stringa t u stringu s, ili NULL ako string s ne sadrži string t.

size_t strspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji čine string t.

size_t strcspn(const char *s, const char *t)

Vraća duljinu prefiksa stringa s koji sadrži znakove koji nisu prisutni u stringu t.

char *strpbrk(const char *s, const char *t)

Vraća 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)

Vraća pokazivač na string kojeg interno generira kompajler za dojavu greške u nekim sistemskim operacijama. Argument je obično globalna varijabla errno, čiju vrijednost također postavlja kompajler pri sistemskim operacijama.

char *strtok(char *s, const char *sep)

strtok je funkcija kojom se može izvršiti razlaganje stringa na niz leksema koji su razdvojeni znakovima-separatorima. Skup znakova-separatora se zadaje u stringu sep. Funkcija vraća pokazivač na leksem ili NULL ako nema leksema.

Korištenje funkcije strtok je specifično jer u strigu može biti više leksema, a ona vraća pokazivač na jedan leksem. Da bi se dobili sljedeći 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 sljedećim iskazi daju ispis tri leksema (Prvi drugi i treci): char *leksem = strtoken(s, " ,\t"); /* dobavi prvi leksem */ while( leksem != NULL) { /* ukoliko postoji */ printf("", leksem); /* ispiši ga i */ lexem = strtok(NULL, " ,\t"); /* dobavi sljedeći leksem */ } /* pa ponovi postupak */

8. Nizovi i pokazivači 22