4.1 polja

33
POLJA U programskom jeziku C i C++ polja se koriste za pohranjivanje većeg broja podataka istog tipa u radnoj memoriji. Npr. ako se u programu želi postići evidencija deset ocjena učenika, tada se odgovarajuće polje deklarira na slijedeći način: int ocjene[5]; Može se reći da varijabla ocjene predstavlja pet cijelih brojeva. Preciznije rečeno, ovime smo stvorili polje koje se zove ocjene. Polje se sastoji od pet elemenata (kaže se da je polje dimenzije pet), a svaki element je tipa int. PRISTUP ELEMENTIMA POLJA Svaki element polja može se promatrati i upotrebljavati kao obična varijabla. Svakom elementu polja može se pristupiti pomoću njegovog indeksa. Indeks je zapravo pozicija elementa u polju. U programskom jeziku C++ indeksi se kreću od nule. Drugim riječima, prvi element polja ima indeks nula, drugi element polja ima indeks jedan, … Primjer: int main() { int ocjene[5]; cout << “Unesite prvu ocjenu: “; cin >> ocjene[0]; cout << “Unesena ocjena iznosi: “ << ocjene[0] << endl; return 0; } Pristup elementu polja preko indeksa izvodi se tako da se nakon naziva polja u uglatim zagradama navede indeks elementa kojemu se želi pristupiti. Pri tome se može unutar uglatih zagrada navesti cjelobrojna konstanta, ali je dozvoljeno staviti i bilo koji legalni C++ aritmetički izraz. Već spomenuto polje ocjene se može načelno prikazati na slijedeći način:

Upload: api-3759825

Post on 07-Jun-2015

1.269 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: 4.1 polja

POLJA

U programskom jeziku C i C++ polja se koriste za pohranjivanje većeg broja podataka istog tipa u radnoj memoriji. Npr. ako se u programu želi postići evidencija deset ocjena učenika, tada se odgovarajuće polje deklarira na slijedeći način:

int ocjene[5];

Može se reći da varijabla ocjene predstavlja pet cijelih brojeva. Preciznije rečeno, ovime smo stvorili polje koje se zove ocjene. Polje se sastoji od pet elemenata (kaže se da je polje dimenzije pet), a svaki element je tipa int.

PRISTUP ELEMENTIMA POLJA

Svaki element polja može se promatrati i upotrebljavati kao obična varijabla. Svakom elementu polja može se pristupiti pomoću njegovog indeksa. Indeks je zapravo pozicija elementa u polju. U programskom jeziku C++ indeksi se kreću od nule. Drugim riječima, prvi element polja ima indeks nula, drugi element polja ima indeks jedan, …

Primjer:

int main(){ int ocjene[5]; cout << “Unesite prvu ocjenu: “; cin >> ocjene[0]; cout << “Unesena ocjena iznosi: “ << ocjene[0] << endl;

return 0;}

Pristup elementu polja preko indeksa izvodi se tako da se nakon naziva polja u uglatim zagradama navede indeks elementa kojemu se želi pristupiti. Pri tome se može unutar uglatih zagrada navesti cjelobrojna konstanta, ali je dozvoljeno staviti i bilo koji legalni C++ aritmetički izraz.

Već spomenuto polje ocjene se može načelno prikazati na slijedeći način:

ocjene[0] ocjene[1] ocjene[2] ocjene[3] ocjene[4]

Ova slika ilustrira jako važnu činjenicu. Naime, polje ocjene se sastoji od pet elemenata. Posljednji element tog polja ima indeks 4. Općenito vrijedi ako je MAX dimenzija polja (broj elemenata polja), tada zadnji element polja ima indeks MAX – 1. To proizlazi iz činjenice da prvi element ima indeks nula.

Kada se pristupa elementima polja treba posebno paziti da indeks bude u dozvoljenim granicama. Za prethodno deklarirano polje ocjene, indeks ne smije biti manji od nula niti veći od 4. Prevodioci za programski jezik C++ ne provjeravaju jesu li indeksi ispravni, već je to ostavljeno na brigu programeru. Iz tog razloga, slijedeći program će se moći uspješno prevesti:

Page 2: 4.1 polja

int main(){ int polje[5];

cout << “Unesite ocjenu: “; cin >> polje[10]; cout << “Unijeli ste: “ << polje[10] << endl;

return 0;}

Ovaj program se može uspješno prevesti, a pri pokretanju se često čini kao da radi. Ovo je međutim teška pogreška. Naime, u ovom programu je rezervirana memorija za pet elemenata polja. Međutim, vrijednost učitana sa tipkovnice se zapisuje u polje[10] (tj. u 11. element). Time se zapravo piše po dijelu radne memorije koji više ne pripada ovom polju. Taj dio memorije može biti slobodan (u tom slučaju će program raditi), ali može i pripadati nekoj drugoj varijabli ili čak i nekom drugom programu. U ovom drugom slučaju, događaju se nepredviđena ponašanja programa. Najčešće se program počinje rušiti, često na vrlo čudnim mjestima i takve greške je naknadno teško otkriti. Stoga je jako važno obratiti pažnju da se indeksi kreću u dozvoljenim granicama. Posebno česta greška ovog tipa je pristup elementu koji se nalazi neposredno iza zadnjeg elementa. Slijedeći primjer ilustrira ovu pogrešku:

#include <iostream.h>

int main(){ int x = 3; int polje[5];

polje[5] = 50; cout << x << endl;

return 0;}

Nakon pokretanja programa ispisati će se 50, a ne 3 jer je pisanjem izvan granica polja, indirektno promijenjen sadržaj varijable x.

Općenito se može reći, za polje koje je dimenzije MAX:

int polje[MAX];

dozvoljeni indeksi se kreću od nula do MAX – 1.

Page 3: 4.1 polja

OSNOVNE OPERACIJE SA POLJIMA

Dosada je bilo opisano kako pristupiti točno određenom elementu polja, a u nastavku će biti prikazane osnovne operacije nad svim elementima polja. Važna činjenica je da osnovne operacije (učitavanje, ispis, pridruživanje, …) nisu podržane za cijelo polje. S druge strane uzevši u obzir činjenicu da je svaki element polja zapravo obična varijabla, te operacije jednostavno se mogu napraviti.

DEKLARACIJA POLJA

U slijedećim primjerima, upotrebljavati će se polje od 30 float elemenata. Tipični izgled svih programa će biti slijedeći:

#define MAX 30

int main(){ float polje[MAX];

// ovdje će se upotrebljavati polje

return 0;}

Umjesto da se odmah unutar uglatih zagrada kod deklaracije navede da je dimenzija polja, ovdje je korištena konstanta MAX zbog preglednosti.

UČITAVANJE SVIH ELEMENTA POLJA

Izvođenje operacije nad cijelim poljem svodi se zapravo na izvođenje određene operacije nad pojedinim elementima polja. Npr. ako treba učitati polje (za što ne postoji posebna naredba), to se radi tako da se učita jedan po jedan element polja. Tipično, da bi se izvela neka operacija nad poljem, potrebno je u petlji proći po svim elementima polja. Tipična petlja koja to izvodi ima slijedeći oblik:

float polje[MAX];int i;

for (i = 0;i < MAX;i++){ // ovdje se obavljaju operacije nad elementom polje[i]}

Ova petlja se izvršava točno MAX puta, pri čemu se prvi korak petlje izvrši za i = 0, a zadnji korak za i = MAX – 1. To je točno ono što je potrebno pri radu sa poljima jer prvi element ima indeks 0, dok zadnji element u polju dimenzije MAX ima indeks MAX – 1. Ovdje je važno uočiti da se, za razliku od svih prethodnih primjera, za pristup elementu polja koristi varijabla (brojač) i, a ne brojčana konstanta.

Page 4: 4.1 polja

Konkretno, učitavanje svih elemenata polja se može obaviti na slijedeći način:

#define MAX 30

int main(){ float polje[MAX];

// učitavanje svih elemenata polja int i; for (i = 0;i < MAX;i++) { cout << "Unesite " << i << ". element: "; cin >> polje[i]; }

return 0;}

ISPIS SVIH ELEMENATA POLJA

Analogno učitavanju, ispis polja se mora obaviti tako da se ispiše jedan po jedan član:

// ispis svih elemenata poljacout << "Elementi polja su: " << endl;for (i = 0;i < MAX;i++) cout << i << ". element: " << polje[i] << endl;

KONTROLA VELIČINE POLJAU dosadašnjim primjerima, korištena je fiksna veličina polja što je veliki nedostatak polja. Najčešće pri pisanju programa, programer ne može unaprijed znati točan broj elemenata polja. Taj problem je moguće djelomično riješiti na slijedeći način. Prilikom deklaracije:

#define MAX 30

float polje[MAX];

rezervirano je polje od točno MAX (u ovom slučaju 30) elemenata. Veličina polja u memoriji se ne može povećati, niti smanjiti. Međutim, dozvoljeno je koristiti manje od MAX elemenata. Time se može omogućiti korisniku programa da za vrijeme izvođenja programa odredi koliko će zapravo elemenata biti u polju. Tipično se to radi tako da se u nekoj cjelobrojnoj varijabli čuva “prividna” dimenzija polja. Prividna dimenzija je zapravo broj elemenata polja koje korisnik programa koristi, dok je stvarna dimenzija (MAX), zapravo stvarna veličina polja u radnoj memoriji. Pri pisanju takvog programa treba paziti da prividna dimenzija polja ne postane veća od stvarne dimenzije. U ranije napisano polje stane točno MAX elemenata. Moguće je spremiti manje od MAX elemenata, ali nikako ne i više od toga. Najjednostavnija varijanta programa koji dopušta korisniku da utječe na broj elemenata koji se čuvaju u polju se može napraviti na slijedeći način:

Page 5: 4.1 polja

#define MAX 30

int main(){ float polje[MAX]; int n;

// ucitavanje dimenzije polja cout << "Unesite dimenziju polja (maksimalno " << MAX << "):"; do { cin >> n; } while (n < 0 || n > MAX);

// ucitavanje n elemenata polja int i; for (i = 0;i < n;i++) { cout << "Unesite " << i << ". element: "; cin >> polje[i]; }

// ispis svih elemenata polja cout << "Elementi polja su: " << endl; for (i = 0;i < n;i++) cout << i << ". element: " << polje[i] << endl;

return 0;

}

Još jednom je potrebno naglasiti da je stvarna veličina polja uvijek fiksno određena. U polje iz gornjeg primjera uvijek stane točno 30 elemenata. Međutim program nudi korisniku da unese koliko će on elemenata koristiti, što se učitava u varijablu n koja predstavlja prividnu dimenziju polja. Pri tome treba paziti da n ne smije biti više od MAX te se stoga učitavanje obavlja u do while petlji. Učitana prividna dimenzija n se sada upotrebljava u for petljama za ispis i učitavanje elemenata polja. Jedina razlika u odnosu na ranije prikazana rješenja je u uvjetu zaustavljanja for petlji. Umjesto i < MAX, što je bio uvjet zaustavljanja u ranijim petljama, ovdje je uvjet zaustavljanja i < n. Time se postiže da se for petlja izvede n puta, a ne MAX puta što zapravo znači da će se učitati i ispisati n elemenata, a ne MAX elemenata. Osnovni nedostatak ovakvog pristupa je činjenica da MAX predstavlja gornju granicu. Ako se želi omogućiti spremanje više od 30 članova polja, potrebno je ponovo prevesti program. Uz to, ovakvom upotrebom se bespotrebno troši memorija. Npr. ako je MAX = 1000, a korisnik je unio n = 5, tada se koristi samo pet elemenata polja, dok ostalih 995 bespotrebno zauzima prostor u radnoj memoriji.

Page 6: 4.1 polja

DIREKTAN PRISTUP ELEMENTU

Upravo opisan program pristupa svim elementima polja. Ukoliko želimo omogućiti korisniku programa da pristupi točno određenom elementu, to se može napraviti na slijedeći način:

int x;

cout << “Unesite redni broj elementa: “;cin >> x;

if (x >= 0 && x < n) cout << “Trazeni element iznosi: “ << polje[x];else cout << “Pogresan unos!\n”;

Pomoću if naredbe se provjerava da se pristupa elementima koji se zaista nalaze unutar polja.

Ovakav program zahtijeva od korisnika programa da broji od nule. Drugim riječima, ako korisnik želi npr. vidjeti 4. element, mora unijeti broj 3. Ako želimo napraviti program tako da korisnik unosi indekse počevši od jedinice, potrebna je minimalna promjena:

cout << “Unesite redni broj elementa: “;cin >> x;

if (x >= 1 && x <= n) cout << “Trazeni element iznosi: “ << polje[x – 1];else cout << “Pogresan unos!\n”;

Nakon što korisnik unese redni broj, odgovarajući element je unos korisnika umanjen za jedan (1. element za korisnika je zapravo nulti element polja, 2. element za korisnika je 1. element polja itd.). Važno je primjetiti da su granice ispitivanja sada od 1 do n, a ne od 0 do n-1.

PRIDRUŽIVANJE POLJA

Pridruživanje jednog polja drugome u programskom jeziku C++ nije posebno podržano, nego je to potrebno samostalno napraviti. U slijedećem primjeru će se sadržaji dva polja zbrojiti te će se rezultat pridružiti u treće polje:

float a[MAX],b[MAX],c[MAX];int n;

// ovdje se ucita n te polja a i b

for (int i = 0;i < n;i++) c[i] = a[i] + b[i];

Page 7: 4.1 polja

OSNOVNI ALGORITMI PRI UPOTREBI POLJA

TRAŽENJE ELEMENTA U POLJU

Traženje elementa u polju znači da treba na neki način ispitati postoji li u polju element koji ima neku određenu vrijednost. Traženje se načelno svodi na petlju koja za svaki element u polju ispituje je li taj element jednak nekoj traženoj vrijednosti. Za ranije definirano float polje, to se može obaviti na slijedeći način:

// trazenje elementa polja (postoji li neki zapis u polju)float traz;int nnadjenih = 0;

cout << "Unesite trazenu vrijednost: ";cin >> traz;

for (i = 0;i < n;i++) if (polje[i] == traz) // ako element postoji u polju { cout << "Element je pronadjen na " << i+1 << ". poziciji" << endl; nnadjenih++; }

if (nnadjenih == 0) cout << "Taj element ne postoji u polju!" << endl;

Za svaki element u polju se ispituje je li on jednak traženoj vrijednosti. Ukoliko je, pozicija na kojoj je nađen ispisuje se na zaslon (uvećana za jedan, kako bi bilo razumljivije za običnog korisnika). Dodatno se broji koliko je elemenata nađeno. To je izvedeno tako da se u varijablu nnadjenih prije početka traženja zapiše broj nula. Za svaki nađeni element, ta se varijabla uveća za jedan, te na kraju sadrži broj, koliko je elemenata pronađeno.

TRAŽENJE NAJVEĆEG I NAJMANJEG ELEMENTA U POLJU

Traženje najvećeg elementa u polju (tj. elementa sa najvećom vrijednosti), se obavlja na slijedeći način. Potrebno je uvesti pomoćnu varijablu max te u nju zapisati vrijednost nultog člana polja. Za sve ostale elemente u polju treba ispitati jesu li veći od max, te ako jesu, njihovu vrijednost treba zapisati u max. Nakon što se izvrši cijela petlja, u varijabli max se nalazi vrijednost najvećeg člana u polju. Konkretno se to može napraviti na slijedeći način:

// trazenje najveceg clana u poljufloat max;

max = polje[0];for (i = 1;i < n;i++) if (polje[i] > max) max = polje[i];

cout << "Najveci clan u polju iznosi: " << max << endl;

Ovdje je važno uočiti da petlja kreće od i = 1 (a ne i = 0, što je bio slučaj kod svih dosadašnjih petlji). To je iz razloga što je nulti član već zapisan u max te za njega ne treba provjeravati je li veći od max.

Page 8: 4.1 polja

Na sličan način se može dobiti i najmanji element u polju:

// trazenje najmanjeg clana u poljufloat min;

min = polje[0];for (i = 1;i < n;i++) if (polje[i] < min) min = polje[i];

cout << "Najmanji clan u polju iznosi: " << min << endl;

RAČUNANJE ZBROJA SVIH ČLANOVA POLJA

Ovo se opet jednostavno može napraviti tako da se u petlji prođe po svim elementima polja i vrijednost svakog elementa pribroji nekoj varijabli npr. zbroj. Prije same petlje potrebno je postaviti početnu vrijednost varijable zbroj na nula.

// racunanje zbroja svih elemenata poljafloat zbroj = 0;for (i = 0;i < n;i++) zbroj += polje[i];cout << "Zbroj svih elemenata u polju: " << zbroj << endl;

SORTIRANJE POLJAZadatak sortiranja je preurediti polje tako da elementi budu poredani po nekakvom redoslijedu. Postoje dvije vrste sortiranja: uzlazno i silazno. Uzlaznog sortiranje preuređuje polje tako da elementi budu poredani u rastućem redoslijedu od najmanjeg prema najvećem. Suprotno tome, silaznim sortiranjem se dobije polje u kojem su elementi poredani u padajućem redoslijedu od najvećeg prema najmanjem. Postoje razni algoritmi za sortiranje, a ovdje će biti prikazan najjednostavniji, tzv. Bubble sort i to uzlazna varijanta. Uzlaznim sortiranjem, dakle, želimo postići da elementi polja budu poredani po rastućem redoslijedu. To znači da svaki element mora biti manji ili jednak od svog “desnog susjeda”. Pod pojmom “desni susjed” misli se na prvi slijedeći element (npr. desni susjed elementa polje[5] je polje[6]). Pri sortiranju je potrebno svaki element za kojeg vrijedi da je veći od svog desnog susjeda zamijeniti sa svojim desnim susjedom. Taj postupak se ponavlja dok se obavlja barem jedna zamjena. Konkretno riješenje je slijedeće:

int sort;

do{ sort = 0;

for (i = 0;i < n - 1;i++) if (polje[i] > polje[i + 1]) { float temp;

temp = polje[i]; polje[i] = polje[i + 1]; polje[i + 1] = temp; sort = 1; }} while (sort == 1);

For petlja obavlja upravo opisani postupak. Unutar nje se obavlja provjera je li neki element veći od svog desnog susjeda, te ako je onda se sa njim zamjenjuje. Očito je da jedno izvršavanje takve for petlje neće uvijek sortirati polje. Stoga se for petlja nalazi unutar do

Page 9: 4.1 polja

while petlje koja se ponavlja sve dok se izvode zamjene. Naime, for petlja zapisuje u varijablu sort je li napravila koju zamjenu (ako je tada vrijedi sort = 1, u suprotnom je sort = 0). Ukoliko nije napravljena niti jedna zamjena, to znači da je niz sortiran i do while petlja završava. U suprotnom cijeli postupak se ponavlja. Ukoliko se želi dobiti silazno sortiranje, dovoljno je zamijeniti uvjet usporedbe sa:

if (polje[i] < polje[i + 1])

POLJA I POKAZIVAČI

Formalno gledano, polje se može smatrati konstantnim pokazivačem. Kada se u izvornom kodu upotrijebi samo naziv polja bez uglatih zagrada, to zapravo označava adresu prvog elementa. Drugim riječima naredbe:

cout << polje;cout << &polje[0];

ispisuju na zaslon isti podatak (tj. adresu prvog elementa).

Polje se dakle smatra pokazivačem, a vrijedi i obratno. To zapravo znači da se pokazivači mogu upotrebljavati kao polja uz minimalna proširenja. Za pokazivače koji se upotrebljavaju kao polja vrijede ista pravila kao i za sve druge pokazivače:

1. Pokazivač je prije upotrebe potrebno postaviti na ispravnu vrijednost, bilo pridruživanjem postojeće adrese, bilo zauzimanjem nove memorije

2. Nakon upotrebe pokazivača, ako je potrebno osloboditi zauzetu memoriju.

Zauzimanje i oslobađanje memorije se odvija malo drugačije nego kod “običnih” pokazivača. Memorija se zauzima naredbom new, pri čemu se kao argument daje veličina polja. Ako memorija nije uspješno zauzeta, u pokazivaču se nalazi NULL vrijednost. Sam pokazivač se nakon toga upotrebljava točno isto kao da je riječ o polju (jer zapravo i jest riječ o polju). Nakon upotrebe, potrebno je osloboditi memoriju pozivom delete. Razlika je u samim pozivima new i delete. Slijedeći primjer pokazuje osnovnu upotrebu:

int *polje;polje = new int[10]; // zauzimanje memorije za polje od deset

// elemenata tipa intif (polje != NULL){ // upotreba kao da je “obično” polje // npr. for (i = 0;i < 10;i++) // cin >> polje[i];}

delete [] polje; // obavezno staviti uglate zagrade iza delete // ukoliko je riječ o polju

Jedina razlika je dakle u pozivima new i delete te u provjeri je li memorija uspješno zauzeta. Nakon toga, polje se upotrebljava na dosad opisane načine.

Page 10: 4.1 polja

U situacijama kada se korisniku programa dopušta da bira dimenziju polja, upotreba pokazivača je primjerenija. Korisnik prvo unosi dimenziju polja, a tek tada se rezervira memorija. Ovo je ilustrirano slijedećim primjerom:

int main(){ float *polje; int n;

// ucitavanje dimenzije polja cout << "Unesite dimenziju polja: "; cin >> n; polje = new float[n]; if (polje == NULL) { cout << “Nedovoljno memorije!”; return 0; }

// ucitavanje n elemenata polja int i; for (i = 0;i < n;i++) { cout << "Unesite " << i << ". element: "; cin >> polje[i]; }

// ispis svih elemenata polja cout << "Elementi polja su: " << endl; for (i = 0;i < n;i++) cout << i << ". element: " << polje[i] << endl;

delete [] polje;

return 0;}

Ovdje je bitno uočiti da konstanta MAX više nije potrebna. Naime, memorija za polje se rezervira pozivom new tek nakon što korisnik unese dimenziju polja. Time je omogućeno korisniku da unese bilo koji broj kao dimenziju. Naravno, ako u radnoj memoriji nema dovoljno slobodnog prostora za smještanje toliko velikog polja, memorija neće biti rezervirana i u pokazivaču će se naći vrijednost NULL.

POLJA U FUNKCIJAMA

Kada funkcija kao argument prima polje, u deklaraciji se obično navodi da funkcija prima pokazivač. Time se zapravo označava da se prima adresa prvog elementa u polju, a pri pozivu se kao argument navodi samo naziv polja. Budući da funkcija prima polje u obliku pokazivača, ona ne može znati koliko je to polje veliko, pa je najčešće potrebno poslati i dimenziju polja kao argument:

Page 11: 4.1 polja

// funkcija koja ispisuje float poljevoid ispisi_polje(float *polje,int dim){ int i;

for (i = 0;i < dim;i++) cout << polje[i] << endl;}

int main(){ float polje_float[20];

// ovdje se na neki nacin popuni polje

ispisi_polje(polje_float,20); return 0;}

Pri pozivu funkcije ispisi_polje se kao prvi argument navodi samo naziv polja. Drugim argumentom se funkciji “kaže” da ispiše prvih 20 elemenata polja. Bitno je uočiti da sama funkcija koristi pokazivač polje kao da je riječ o polju. Zapravo, zaista i je riječ o polju (kao što je objašnjeno u prethodnom poglavlju).

Budući da funkcija polje prima na taj način da primi adresu prvog elementa, funkcija prima “original” polja. Drugim riječima, funkcija preko argumenta može izmjeniti sadržaj polja koje je primila, što je ilustrirano slijedećim primjerom:

// funkcija koja ucitava float poljevoid ucitaj_polje(float *polje,int dim){ int i;

for (i = 0;i < dim;i++) cin >> polje[i];}

int main(){ float polje_float[20];

ucitaj_polje(polje_float,20); return 0;}

U prethodnom primjeru je bitno uočiti da funkcija ucitaj_polje ne traži nikakvu referencu jer čim preko pokazivača prima adresu prvog elementa, funkcija može pristupiti “originalu” polja.

Ako funkcija mora rezultat vratiti u obliku polja, to se obično radi tako da funkcija kao argument primi adresu prvog elementa polja (može se reći da funkcija primi polje) u koje će upisati rezultat..

Page 12: 4.1 polja

Ako funkcija ne mijenja sadržaj polja, moguće je to deklarirati na slijedeći način:

// zbrajanje svih elementa u poljufloat zbroji(const float *polje, int n){ float suma = 0; int i;

for (int i = 0;i < n;i++) suma += polje[i];

return suma;}

int main(){ float polje_float[20];

// ucitavanje polja

cout << zbroji(polje_float, 20); return 0;}

Jedina razlika u odnosu na prethodne primjere je što funkcija za argument polje navodi da je tipa const float *, umjesto float *. Time se zapravo funkcija obavezuje da neće ni na koji način promijeniti sadržaj polja.

POLJA OBJEKATA

Dozvoljeno je, a često i vrlo korisno napraviti polje objekata, što je ilustrirano slijedećim primjerom:

class Radnik{ // deklaracije članova};

int main(){ Radnik radnici[20];

// upotreba polja}

Ovom deklaracijom je napravljeno polje od dvadeset elemenata, pri čemu je svaki element zapravo objekt klase radnik. Upotreba elementa je ista kao i upotreba samostalnog objekta. Tako, npr. ako u klasi Radnik postoji metoda set_placa, ona se može pozvati na slijedeći način:

// postavljanje plaće nultom radniku na 10.3radnici[0].set_placa(10.3);

Važno je napomenuti da se pri deklaraciji polja objekata za svaki objekt uvijek poziva podrazumijevani (default) konstruktor. Nije moguće pri deklaraciji tražiti da se pozove neki drugi konstruktor. Kao posljedica toga, nije moguće napraviti polje objekata klase koja nema podrazumijevani konstruktor. Da se podsjetimo, klasa ima podrazumijevani konstruktor (konstruktor bez argumenata) u dva slučaja. Ukoliko u klasi nije napisan niti jedan konstruktor, tada prevodilac u klasu ugrađuje podrazumijevani konstruktor koji ne radi ništa.

Page 13: 4.1 polja

Ukoliko u klasi postoji jedan ili više konstruktora, tada je potrebno samostalno napisati podrazumijevani konstruktor. Ovo je ilustrirano slijedećim primjerima:

class Radnik1{ public: // članovi klase (bez konstruktora)

// ako nema konstruktora, prevodilac ugrađuje // podrazumijevani konstruktor koji ništa ne radi};

class Radnik2{ public: // deklaracijom konstruktora se gubi podrazumijevani // konstruktor Radnik2(float poc_placa);};

class Radnik3{ public: Radnik2(float poc_placa); Radnik2(); // podrazumijevani konstruktor};

// definicije metoda svih klasa

int main(){ Radnik1 polje1[10]; // ispravno Radnik2 polje2[10]; // greška Radnik3 polje3[10]; // ispravno

// ostatak programa}

Deklaracija polja objekata klase Radnik2 nije dozvoljena jer dotična klasa nema ugrađen podrazumijevani konstruktor.

POLJA U OBJEKTIMA

Upotreba polja u objektima je dozvoljena i sasvim normalna. Moguće je unutar klase deklarirati polje kao člansku varijablu. Upotreba je ista kao i upotreba polja u funkcijama koje se nalaze van klasa. Pristupne metode set i get se mogu napraviti npr. tako da primaju jedan dodatni argument koji označava indeks elementa polja kojemu se pristupa. Ovo je ilustrirano slijedećim primjerom:

class Ucenik{ private: int ocjene[20]; public:

void set_ocjena(int vrijednost, int indeks) { ocjene[indeks] = vrijednost;

Page 14: 4.1 polja

}

int get_ocjena(int indeks) const { return ocjene[indeks]; }};

int main(){ Ucenik ivo;

// ucitavanje ocjena for (i = 0;i < 20;i++) { int unos; cin >> unos; ivo.set_ocjena(unos,i); }

// ispis ocjena for (i = 0;i < 20;i++) cout << ivo.get_ocjena(i) << endl; return 0;}

U tipičnim situacijama, neće svaki objekt koristiti jednak broj elemenata u svom polju. Za prethodni primjer se može reći da neće svaki učenik imati 20 ocjena. Stoga je potrebno ugraditi mogućnost postavljanja dimenzije. To se može napraviti na slijedeći način:

class Ucenik{ private: int ocjene[20]; int n; public:

void set_n(int novi_n) { n = novi_n; }

int get_n() const { return n; } void set_ocjena(int vrijednost, int indeks) { ocjene[indeks] = vrijednost; }

int get_ocjena(int indeks) const { return ocjene[indeks]; }

Page 15: 4.1 polja

};

int main(){ Ucenik ivo; int n;

// ucitavanje ocjena

cout << "Koliko ocjena: "; cin >> n;

ivo.set_n(n);

for (i = 0;i < n;i++) { int unos; cin >> unos; ivo.set_ocjena(unos,i); }

// ispis ocjena n = ivo.get_n(); for (i = 0;i < n;i++) cout << ivo.get_ocjena(i) << endl; return 0;}

U nekim situacijama je pogodnije upotrebljavati pokazivač umjesto polja. U tom slučaju potrebno je ugraditi ispravno zauzimanje i oslobađanje memorije. Dodatno je potrebno ugraditi konstruktor kopiranja i operator pridruživanja koji rade duboku kopiju:

class Ucenik{ private: int *ocjene; int n; public:

Ucenik(int br_ocj);

~Ucenik() { delete [] ocjene; } int get_n() const { return n; } void set_ocjena(int vrijednost, int indeks); int get_ocjena(int indeks) const; Ucenik (const Ucenik &original);

Page 16: 4.1 polja

const Ucenik &operator =(Ucenik &desni); };

Ucenik::Ucenik(int br_ocj){ n = br_ocj; ocjene = new int[n];

if (ocjene == NULL) n = 0;}

void Ucenik::set_ocjena(int vrijednost,int indeks){ if (indeks >= 0 && indeks < n) ocjene[indeks] = vrijednost;}

int Ucenik::get_ocjena(int indeks) const{ if (indeks >= 0 && indeks < n) return ocjene[indeks]; else return 0;}

Ucenik::Ucenik(const Ucenik &original){ delete [] ocjene;

n = original.get_n(); ocjene = new int[n];

if (ocjene != NULL) { for (int i = 0;i < n;i++) ocjene[i] = original.get_ocjena(i); } else n = 0;}

const Ucenik &operator= (const Ucenik &desni){ if (this != &desni) { delete [] ocjene; n = original.get_n(); ocjene = new int[n];

Page 17: 4.1 polja

if (ocjene != NULL) { for (int i = 0;i < n;i++) ocjene[i] = original.get_ocjena(i); } else n = 0; }

return *this;}

int main(){ int n;

// ucitavanje ocjena

cout << "Koliko ocjena: "; cin >> n;

Ucenik ivo(n);

for (i = 0;i < n;i++) { int unos; cin >> unos; ivo.set_ocjena(unos,i); }

// ispis ocjena n = ivo.get_n(); for (i = 0;i < n;i++) cout << ivo.get_ocjena(i) << endl; return 0;}

Dodatno je u set i get metode klase Ucenik ugrađena provjera indeksa, čime se spriječava pisanje po memoriji koja nije dio polja.

DINAMIČKA PROMJENA BROJA PODATAKA U POLJU

Već je ranije spomenuto da je veličina polja uvijek fiksna i određena je stvarnom dimenzijom polja. Također je spomenuto da se ne moraju upotrijebiti svi elementi polja, te je prikazan jednostavan način kako omogućiti korisniku programa da utječe na broj elemenata u polju. Korisnik je unosio “prividnu” dimenziju n, te su se sve operacije izvodile nad prvih n elemenata. Naravno, u većini situacija, korisnik programa ne može unaprijed znati koliko će podataka biti. Vrlo često je potrebno omogućiti korisniku programa da dodaje jedan po jedan podatak po potrebi. Također je potrebno omogućiti da korisnik po želji ukloni jedan podatak ili eventualno izmjeni neki podatak. Takva dinamička promjena broja podataka se može jednostavno napraviti upotrebom običnog polja. Osnovni način na koji je to moguće napraviti će biti prikazan u nastavku.

Page 18: 4.1 polja

DODAVANJE ELEMENTA U POLJE

Prije samog dodavanja, potrebno je odrediti kako će polje biti prikazano. Osnovna deklaracija (za ove primjere) će imati slijedeći oblik:

float polje[MAX];int n = 0;

Pri čemu je MAX neka brojčana konstanta ranije definirana sa direktivom #define. Varijabla n označava koliko je stvarno elementa u polju (prividna dimenzija polja). Ta varijabla ne smije poprimiti iznos veći od MAX. Već je ranije prikazano da se većina operacija nad poljima izvodi tako da se u petlji izvodi operacija nad svakim elementom tog polja. Za ovako deklarirano polje, petlja će imati slijedeći oblik:

for (i = 0;i < n;i++) // ovdje se obavljaju operacije nad svakim elementom

Početno je vrijednost varijable n postavljena na nula. Time je zapravo početno označeno da je polje “prazno”. Ovdje je važno primjetiti da polje zapravo nikad nije prazno. Ono uvijek ima točno MAX elemenata. Međutim, korisniku programa (a i programeru) važno je samo prvih n elemenata polja. Ako n iznosi nula, polje ne sadrži nikakve podatke bitne za korisnika (i programera).

Osnovna ideja dodavanja je jednostavna. Ako je upotrijebljeno prvih n elemenata, tada zadnji element ima indeks n – 1. To znači da prvo slobodno mjesto u polju za unos novog elementa ima indeks n. Kada se dodaje novi element u polje, potrebno je učitati njegovu vrijednost te ga upisati na mjesto sa indeksom n. Nakon toga, potrebno je uvećati vrijednost broja n za jedan (time je zapravo stvarno dodan novi element, jer tek tada novi element postaje zadnji u polju). Prije samog dodavanja, potrebno je samo provjeriti postoji li mjesta za novi element u polju (u polju ne može biti više od MAX elemenata). Ovako definirana operacija dodavanja, može se sada (za prethodno deklarirano polje) napisati na slijedeći način:

// dodavanje novog elementaif (n < MAX) // ako ima mjesta u polju{ float novi;

cin >> novi; polje[n] = novi; n++;}else cout << “Nema više mjesta za dodavanje novog zapisa!\n”;

Naravno, da bi ovaj postupak bio upotrebljiv, potrebno je opciju dodavanja na neki način izvoditi u petlji. Najčešće se opcija dodavanja ugrađuje kao opcija u izborniku programa, tako da korisnik može odlučiti kada će dodati novi zapis. Dodatno, najčešće je potrebno kao opciju izbornika ugraditi i mogućnost ispisa svih elemenata (kako bi se moglo provjeriti da su podaci zaista dodani u polje).

BRISANJE ELEMENTA IZ POLJA

Još jednom je potrebno naglasiti da je polje u memoriji uvijek fiksno. Fizički se element ne može obrisati iz polja, ali se zaobilazno može ukloniti. Nakon operacije brisanja, korisnik programa jednostavno taj element ne vidi. Brisanje se može obaviti na dva jednostavna načina koji će biti prikazani u nastavku.

Page 19: 4.1 polja

Izravna metoda brisanja može se izvesti na slijedeći način. Pretpostavimo da se želi obrisati element sa indeksom x. Tada je sve elemente koji su desno od njega (tj. sve elemente čiji indeks je veći od x) potrebno pomaknuti za jedno mjesto ulijevo. Nakon toga je samo potrebno smanjiti broj elemenata n za jedan. Kada se vrijednost u n smanji za jedan, tek tada se zapravo ukloni jedan element iz polja (i to zadnji element). To je zato što svaki prolaz po svim elementima polja ide do uključivo n – 1. Brisanje se sada može izvesti na slijedeći način:

if (n > 0){ int x;

cin >> x; // indeks elementa koji se briše

for (int i = x;i < n – 1;i++) polje[i] = polje[i + 1];

n--;}else cout << “Polje je prazno!\n”;

Bitno je uočiti da petlja kreće od elementa sa indeksom x sve do predzadnjeg elementa u polju. Petlja zapravo kaže na element sa indeksom i upiši vrijednost koja je u njegovom desnom susjedu. Time se zapravo svi elementi koji su desno od x-tog elementa pomjere za jedno mjesto u lijevo. Tek nakon toga se smanji broj n (broj elemenata u polju) za jedan.

Drugi način brisanja je jednostavnije napraviti, ali ima nedostatak da nakon brisanja elementi ne ostaju poredani na isti način. Osnovna ideja slijedi iz toga da se smanjenjem broja n, zapravo izravno briše zadnji element u polju. Drugim riječima, ako se želi brisati element sa indeksom i, tada je dovoljno na njegovo mjesto upisati zadnji element u polju te nakon toga smanjiti n za jedan:

if (n > 0){ int x;

cin >> x; polje[x] = polje[n – 1];

n--;}else cout << “Polje je prazno!\n”;

PROMJENA ELEMENTA

Promjena elementa je jednostavni postupak kojim se ne mijenja veličina polja. Pri promjeni elementa potrebno je dobiti indeks elementa čija se vrijednost mijenja, učitati novu vrijednost za taj element te upisati novu vrijednost na to mjesto:

int promj;

cin >> promj; // indeks elementa koji se mijenja

if (promj >= 0 && promj < n) // ako je indeks ispravan cin >> polje[promj]; // učitaj novu vrijednost

Page 20: 4.1 polja

Opisani algoritmi dodavanja, brisanja i promjenu predstavljaju najosnovniju verziju. Bitno je uočiti da je za brisanje i promjenu elementa u polju potrebno imati indeks elementa koji se briše ili mijenja. U tipičnim programima, korisniku indeks elementa u polju nema smisleno značenje. Umjesto toga, najčešće se softiciranija verzija brisanja i promjene izvodi tako da se korisniku omogući da unese vrijednost elementa koji se briše. Npr. ako su elementi polja objekti klase Radnik korisnik bi mogao unijeti identifikacijski broj radnika koji se briše. Takvo brisanje se obavlja na sličan način. Jedino je potrebno prvo pronaći element polja koji sadrži traženu vrijednost. Za ovaj primjer, to znači da je potrebno pronaći radnika čiji identifikacijski broj je jednak traženom. Pod pojmom pronaći, znači da je u neku varijablu potrebno zapisati indeks tog elementa. Nakon što je pribavljen indeks traženog elementa, operacije brisanja i promjene se izvode na ranije prikazan način.

VIŠEDIMENZIONALNA POLJA (MATRICE)

Dosad opisana polja su se sastojala od jedne dimenzije. Intuitivna predožba polja bila je retkovna struktura. Drugim riječima, polje smo zamišljali kao jedan redak od MAX elemenata, pri čemu MAX predstavlja (jedinu) dimenziju polja.

Matrice su u načelu proširenja polja na taj način da postoji više od jedne dimenzije. Najčešće se upotrebljavaju 2D matrice (matrice sa dvije dimenzije), ali broj dimenzija može biti i proizvoljno veći. U nastavku će ukratko biti opisane 2D matrice.

2D matricu možemo zamisliti kao pravokutnu strukturu koja se sastoji od određenog broja redaka, pri čemu se svaki redak sastoji od određenog broja stupaca. Najčešće oznake su M za broj redaka i N za broj elemenata u svakom retku (tj. broj stupaca). Tada se kaže da je matrica dimenzija M*N. Deklaracija ovakve matrice se obavlja na sličan način kao i za polja:

float mat[3][10];

Ovime je deklarirana matrica mat koja se sastoji od 3 retka, pri čemu se svaki redak sastoji od 10 float elemenata (stupaca). Ovo se slikovito može prikazati na slijedeći način:

a[0][0] a[0][1] a[0][9]a[1][0] a[1][1] a[1][9]a[2][0] a[2][1] a[2][9]

Nazivi elemenata su prikazani unutar tablice. Pristup elementima se ostvaruje analogno pristupu elementima u polju, pri čemu je potrebno navesti dva indeksa (prvo redak, a zatim stupac). Kao i kod polja, indeksi se kreću od nule. Može se također reći da je matrica zapravo polje pri čemu je jedan element tog polja, opet polje. Tj. jedan redak matrice je zapravo klasično jednodimenzionalno polje. Za ovaj primjer matrica se sastoji od 3 polja, pri čemu je svako polje dimenzije 10.

Operacija i algoritama sa matricom ima nebrojeno puno te ovdje neće biti prikazani. Kao i kod polja, većina postupka nad matricama se svode na petlju koja prolazi po svim elementima. Kao jednostavan primjer te petlje, biti će prikazano učitavanje i ispis matrice:

#define M 2#define N 3

int main(){ float mat[M][N]; int i,j;

Page 21: 4.1 polja

for (i = 0;i < M;i++) for (j = 0;j < N;j++)

{ cout << “Unesite element na poziciji (“ << i << “,” << j << “): “;

cin >> mat[i][j];}

for (i = 0;i < M;i++) { for (j = 0;j < N;j++)

cout << mat[i][j];

cout << endl; }}

Budući da je riječ o 2D strukturi, potrebno je koristiti petlju unutar petlje da bi se prošlo po svim elementima. Vanjska petlja prolazi po svim retcima, dok unutrašnja prolazi po svakom elementu i-tog retka. Pri ispisu je u vanjsku petlju ugrađena naredba ispisa novog reda iz čisto kozmetičkih razloga, kako bi se dobio vizualno jasniji prikaz matrice.