32
SVEUČILIŠTE U ZAGREBU FAKULTET ORGANIZACIJE I INFORMATIKE VARAŽDIN SEMINARSKI RAD IZ KOLEGIJA „STRUKTURE PODATAKA“ TEMA: POKAZIVAČI LEDA LINK 38180/09-R

Leda Link - Pokazivači

Embed Size (px)

DESCRIPTION

seminarski rad s 2.god iz struktura podataka

Citation preview

Page 1: Leda Link - Pokazivači

SVEUČILIŠTE U ZAGREBU

FAKULTET ORGANIZACIJE I INFORMATIKE VARAŽDIN

SEMINARSKI RAD IZ KOLEGIJA „STRUKTURE PODATAKA“

TEMA: POKAZIVAČI

LEDA LINK

38180/09-R

U Varaždinu, prosinac, 2010.

Page 2: Leda Link - Pokazivači

SVEUČILIŠTE U ZAGREBU

FAKULTET ORGANIZACIJE I INFORMATIKE VARAŽDIN

Ime i prezime: Leda Link

Redovni student

Broj indeksa: 38180/09-R

SEMINARSKI RAD IZ KOLEGIJA „STRUKTURE PODATAKA“

TEMA: POKAZIVAČI

Nositelj kolegija: prof.dr.sc. Mirko Čubrilo

Asistent: Tihomir Orehovački, mag.inf.

U Varaždinu, prosinac, 2010.

Page 3: Leda Link - Pokazivači

Sadržaj

1. Pokazivači – što se odvija u memoriji..............................................................................................4

2. Uvodno o pokazivačima...................................................................................................................5

3. Dinamička alokacija.........................................................................................................................7

3.1. Naredba „new“ i stvaranje dinamičkih varijabli.......................................................................7

3.2. Naredba „delete“ i brisanje dinamičkih varijabli....................................................................11

4. Dinamički nizovi............................................................................................................................12

5. Pokazivači na pokazivače...............................................................................................................17

6. Polja i pokazivači............................................................................................................................19

7. Vezana lista i pokazivači................................................................................................................21

8. Strukture i pokazivači.....................................................................................................................23

Zaključak................................................................................................................................................25

Literatura................................................................................................................................................26

Page 4: Leda Link - Pokazivači

1. Pokazivači – što se odvija u memoriji

Kako bismo temeljitije objasnili sam pojam pokazivača, potrebno je objasniti što se i

na koji način odvija u memoriji. Naime, memorija je kontinuirani niz bajtova što bi značilo da

svaki bajt ima svoju adresu, odnosno, pobliže, redni broj. Definiranjem varijable će se

rezervirati određeni memorijski prostor na nekim adresama. Za varijablu ćemo reći da je na

nekoj adresi ukoliko je prvi bajt sadržaja varijable pohranjen na toj adresi.

Primjer:

Primjerice, ako tražimo adresu varijable c, pogledat ćemo prvo mjesto koje ona zauzima, a to

će biti adresa 82564. Za varijablu c su rezervirana 4 bajta, počevši od adrese 82564.

Page 5: Leda Link - Pokazivači

2. Uvodno o pokazivačima

Za adresu se često koristi pojam pokazivač (eng. pointer) jer upravo adresa pokazuje

na neki element u memoriji. Kada pokazivač sadrži adresu neke varijable može se reći da

„pokazuje“ na neku varijablu. Dakle, definicija pokazivača bi mogla biti: pokazivač je

varijabla koja sadrži adresu neke druge varijable.

Pokazivači se deklariraju na način da se definira tip podatka na koji pokazivač

pokazuje, te da se ispred imena varijable postavlja operator diferencijacije * koji omogućuje

pokazivaču da pristupi varijabli na koju pokazuje. Operator & daje adresu varijable, a

operator * daje sadržaj lokacije na koju pokazivač pokazuje. Pokazivač može biti deklariran

tako da pokazuje na bilo koji tip podatka. Ipak, adrese koje pokazivač sadrži moraju biti

istoga tipa.

Za razliku od ostalih varijabli, za pokazivače možemo definirati da ne sadrže nikakvu

memorijsku adresu. Nepostojeća se memorijska adresa definira pomoću konstante NULL.

NULL je konstanta iz biblioteke „cstdlib“, a predstavlja naziv za broj 0. Poslijednji element

unutar vezane liste će uvijek imati vrijednost NULL. Također se koristi i naredba „new“

kojom se dinamički alocira prostor. Nadalje, koristi se i naredba „delete“ kojom se dealocira

memorijski prostor na koji pokazuje određeni pokazivač.

Sintaksa:

tip podataka *ime;

Tip podataka je kod pokazivača potreban iz razloga jer je važno znati kolika je

veličina varijable u memorijskom prostoru na koji pokazivač pokazuje, tj. koliko se memorije

treba alocirati.

Pokazivači su često u upotrebi u pisanju programskog koda iz razloga što omogućuju

optimiziranje programskog koda.

Dinamičke varijable mogu mijenjati svoju strukturu na bilo kojem mjestu unutar

programa. Takve dinamičke strukture mogu biti: vezane liste, binarna stabla, stogovi. One se

od statičkih varijabli razlikuju u tome što za njih nećemo rezervirati unaprijed memorijski

prostor, već ćemo ga rezervirati unutar programa po potrebi, odnosno ovisno o količini

podataka koji će se unositi tijekom izvršavanja programa. Dinamičke varijable nemaju

Page 6: Leda Link - Pokazivači

vlastito ime u programu, već imaju definiran samo tip podatka. Da bi se s takvim varijablama

moglo baratati potrebne su nam pokazivačke varijable koje pokazuju na dinamičku varijablu.

U ovom seminarskom radu ću pokazivače prikazati unutar programskog jezika C++ uz par

poveznica s programskim jezikom C.

3. Dinamička alokacija

C++ je znatno osuvremenio mehanizme za dinamičku alokaciju memorijskog prostora

u odnosu na programski jezik C. Primjerice, u C programskom jeziku se dinamička alokacija

ostvarivala pozivom funkcije „malloc“ ili neke slične funkcije iz biblioteke „cstdlib“. Iako

takav način alokacije radi i u programskom jeziku C++, isti nudi puno fleksibilnije načine od

ovog, tako da se u praksi izbjegava korištenje naslijeđenih funkcija iz C programskog jezika.

Također, potreba za dinamičkom alokacijom na takav način je značajno pala nakon uvođenja

dinamičkih tipova podataka poput „vector“ i „string“. Ipak, dinamički tipovi podataka su

izvedeni tipovi podataka koji su definirani u standardnoj bibiloteci jezika C++ koji su

implementirani pomoću dinamičke alokacije memorije, što znači da da ne postoji dinamička

alokacija memorije da ne bi bilo moguće niti kreiranje tipova podataka poput „vector“ i

„string“.

3.1. Naredba „new“ i stvaranje dinamičkih varijabli

U programskom jeziku C++ način za dinamičku alokaciju memorijskog prostora je

pomoću operatora „new“. Postoji nekoliko oblika istog, a što se osnovnog tiče, iza njega treba

slijediti ime nekoga tipa. Nakon toga će taj operator potražiti slobodno mjesto unutar

memorije u koje bi se mogao smjestiti podatak odabranog tipa. Ukoliko se to mjesto pronađe,

operator „new“ će kao rezultat vratiti pokazivač na pronađeno memorijsko mjesto. To mjesto

će se potom označiti kao zauzeto, tako da se taj prostor više neće moći upotrijebiti za neku

drugu namjenu. Ukoliko se dogodi situacija da je cijela memorija zauzeta, C++ će izbaciti

izuzetak, dok se u programskom jeziku C i ranijim verzijama C++ vraćao NULL pokazivač.

Page 7: Leda Link - Pokazivači

Osnovnu upotrebu operatora „new“ možemo prikazati na sljedećem primjeru:

int *pok;

Ovom deklaracijom je definirana pokazivačka varijabla „pok“ koja treba pokazati na

tip podataka „int“, odnosno cijeli broj. Stanje memorije nakon ove deklaracije izgleda ovako:

Potom izvršavamo naredbu pok=new int;

u kojoj operator „new“ traži slobodno mjesto u memoriji koje je dovoljno veliko da prihvati

jedan cijeli broj. Ukoliko se takvo mjesto pronađe, njegova adresa će biti vraćena kao rezultat

operanda „new“ i dodijeljena pokazivaču „pok“, tako da će on u ovom primjeru pokazivati na

adresu 9. Nakon toga će stanje u memoriji izgledati ovako:

Pored toga, lokacija 9 postaje zauzeta, i to u smislu da će ova lokacija biti rezervirana

za upotrebu od strane programa, i ona do daljnjeg neće biti korištena za neku drugu namjenu.

Iz tog je razloga sigurno pristupiti sadržaju iste putem pokazivača „pok“.

Sadržaj dinamičkih varijabli je nakon njihovog deklariranja tipično nedefiniran, sve

dok se ne izvrši prva dodjela vrijednosti putem pokazivača. Preciznije, sadržaj zauzete

lokacije zadržava svoj raniji sadržaj, jedino što se lokacija smatra zauzetom. Ipak, moguće je

inicijalizirati dinamičku varijablu odmah na početku, tijekom njenog stvaranja, tako što iza

oznake tipa u operatoru „new“ u zagradama navedemo izraz koji predstavlja željeno inicijalnu

vrijednost dinamičke varijable.

Page 8: Leda Link - Pokazivači

Primjerice, sljedeća naredba će stvoriti novu cjelobrojnu dinamičku varijablu, te inicijalizirati

njezin sadržaj na vrijednost 5 i postaviti pokazivač „pok“ na nju:

pok=new int(5);

Stanje u memoriji je prikazano na sljedećoj slici:

Razumljivo je da se rezultat operatora „new“ mogao odmah iskoristiti za inicijalizaciju

pokazivača „pok“ već prilikom njegove deklaracije, kao naprimjer:

int *pok=new int(5);

int *pok (new int(5));

Također, za dinamičku varijablu je, kao i za svaku drugu varijablu, moguće vezati

referencu, čime možemo indirektno dodijeliti ime novostvorenoj dinamičkoj varijabli. Tako,

ukoliko je „pok“ pokazivačka varijabla koja pokazuje na novostvorenu dinamičku varijablu,

kao u prethodnom primjeru, moguće je izvršiti sljedeću deklaraciju:

int &dinamicka=*pok;

Na ovaj način je kreirana referenca „dinamicka“ koja je vezana za novostvorenu

dinamičku varijablu, i koju, prema tome, možemo smatrati kao alternativno ime te dinamičke

varijable. Drugi način na koji smo mogli napraviti isto je vezivanjem reference direktno na

dereferencirani rezultat operatora „new“ (bez posredstva dinamičke varijable). Rezultat

operatora „new“ je pokazivač, dereferencirani pokazivač je l-vrijednost, a na svaku l-

vrijednost se može vezati referenca odgovarajućeg tipa:

int &dinamicka=*new int(5);

Ovakva formulacija se ne koristi često, ali može poslužiti jer se dereferencirani

rezultat operatora „new“ može prenijeti u funkciju kao parametar po referenci.

Page 9: Leda Link - Pokazivači

Prilikom korištenja operatora „new“ treba voditi računa o tome da uvijek postoji

mogućnost da se dogodi izuzetak (ukoliko nema dovoljno memorije). Iako je vjerojatnost da u

memoriji neće biti pronađen prostor za jedan cijeli broj (kao u ovom elementarnom primjeru)

vrlo mala, treba paziti na to jer primjerice u nizovima trebamo dinamički alocirati puno više

memorije za koje je vrlo lako moguće da nema dovoljno memorije. Stoga je moguće izvoditi

svaku dinamičku alokaciju unutar „try“ bloka. Tip izuzetka koji će prikazati je tip

„bad_alloc“. Ovo je izvedeni tip podataka, definiran u biblioteci „new“. Tako da, ukoliko

želimo specificirati izuzetke takvoga tipa, možemo koristiti sljedeće:

try {

int *pok=new int(5);

...

}

catch (bad_alloc) {

cout<<“Problem, nema dovoljno memorije.“;

}

Kreiranje individualnih dinamičkih varijabli nije optimalno ukoliko se kreiraju

varijable prostih tipova (npr. cjelobrojne varijable), tako da će kreiranje individualnih

dinamičkih varijabli postati zanimljivo tek kada se razmotre kompleksniji tipovi podataka

poput struktura i klasa. Ipak, pomoću dinamičke alokacije se mogu indirektno kreirati nizovi

čija veličina nije unaprijed zadana. Za tu svrhu se koristi također operator „new“ iza kojeg

ponovo slijedi ime tipa (koje ovaj put predstavlja tip elementa niza), nakon čega u uglatim

zagradama slijedi broj elemenata niza koji kreiramo. Pri tome, traženi broj elemenata niza ne

mora biti konstanta, već može biti proizvoljan izraz.

Operator „new“ će tada potražiti slobodno mjesto u memoriji u koje bi se mogao

smjestiti niz tražene veličine navedenog tipa, i vratiti kao rezultat pokazivač na pronađeno

mjesto u memoriji, ukoliko takvo postoji (u suprotnom će se ispisati izuzetak tipa

„bad_alloc“). Naprimjer, ako je varijabla „pok“ deklarirana kao pokazivač na cijeli broj

(prethodni primjeri), tada će sljedeća naredba potražiti prostor u memoriji koji je dovoljan za

Page 10: Leda Link - Pokazivači

prihvaćanje 5 cjelobrojnih vrijednosti, i u slučaju da pronađe takav prostor, dodijelit će

njegovu adresu pokazivaču „pok“:

pok=new int[5];

3.2. Naredba „delete“ i brisanje dinamičkih varijabli

Dinamičke varijable se mogu ne samo stvarati, već i brisati na zahtjev. Onog trenutka

kada zaključimo da nam dinamička varijabla na koju pokazuje pokazivač više nije potrebna,

možemo ju obrisati primjenom operatora „delete“, iza kojeg se navodi pokazivač koji

pokazuje na dinamičku varijablu koju želimo obrisati. Primjer naredbe:

delete pok;

Ovim primjerom narebe ćemo obrisati dinamičku varijablu na koju pokazivač „pok“

pokazuje. Pri tome je važno spomenuti da će pokazivač „pok“ i dalje pokazivati na istu adresu

na koju je pokazivao i prije, ali će se izbrisati evidencija o tome da je ta lokacija zauzeta.

Dakle, ta lokacija može nakon brisanja dinamičke varijable biti iskorištena od strane nekog

drugog (npr. operacijskog sustava) za neku drugu svrhu (npr. sljedeća primjena operatora

„new“ može ponovo iskorisiti taj isti prostor za stvaranje neke druge dinamičke varijable).

Nakon toga više nije sigurno pristupati sadržaju na koji pokazuje „pok“. Pokazivači

koji pokazuju na objekte koji su obrisani iz bilo kojeg razloga nazivaju se viseći pokazivači

(eng. dangling pointers). Jedan od mogućih načina da kreiramo viseći pokazivač je

eksplicitno brisanje sadržaja na koji pokazivač pokazuje pozivom operatora „delete“, ali

postoje i drugi načini, primjerice, da iz funkcije kao rezultat vratimo pokazivač na neki

lokalni objekt unutar funkcije, koji prestaje postojati po završetku funkcije. Treba paziti na

viseće pokazivače jer su u pravilu vrlo čest uzročnik grešaka u programu koje se načelno vrlo

teško otkrivaju.

Sve dinamičke varijable se automatski brišu po završetku programa. Međutm, bitno je

naglasiti da ni jedna dinamička varijabla neće biti obrisana sama od sebe prije završetka

programa, osim ukoliko je eksplicitno ne obrišemo primjenom operatora „delete“. Pri tome se

one bitno razlikuju od automatskih varijabli, koje se same automatski brišu na kraju bloka u

kojem su definirane.

Page 11: Leda Link - Pokazivači

4. Dinamički nizovi

Za najkvalitetnije razumijevanje pokazivača koristit ću dinamičke nizove jer se

elementima unutar njih može pristupati upravo pomoću pokazivača.

Dinamičke nizove ćemo kreirati na sljedeći način: s obzirom da dinamički nizovi

nemaju imena njima se može pristupati samo pomoću pokazivača koji pokazuju na njihove

elemente. Tako, ukoliko izvršimo sljedeću deklaraciju:

int *dinamicki_niz=new int[100];

stvaramo dva objekta: dinamički niz od 100 cijelih brojeva koji nema ime, te pokazivač

„dinamicki_niz“ koji je inicijaliziran tako da pokazuje na prvi element ovako stvorenog

dinamičkog niza.

Za razliku od običnih nizova koji se mogu inicijalizirati pri deklaraciji, i običnih

dinamičkih varijabli koje se mogu inicijalizirati pri stvaranju, dinamički nizovi se ne mogu

inicijalizirati u trenutku stvaranja (tj. njihov je sadržaj nakon stvaranja nepredvidljiv).

Naravno, inicijalizaciju je moguće uvijek naknadno izvršiti ručno. Također, nije problem

napisati funkciju koja će stvoriti niz, inicijalizirati ga, i vratiti kao rezultat pokazivač na

novostvoreni i inicijalizirani niz. Tada tako napisanu funkciju možemo koristiti za stvaranje

nizova koji će odmah po kreiranju biti i inicijalizirani.

Kada nam neki dinamički niz više nije potreban, možemo ga također obrisati (tj.

osloboditi prostor koji je zauzimao) pomoću operatora “delete”, samo uz neznatno drugačiju

sintaksu u kojoj se koristi par uglatih zagrada. Tako, ukoliko pokazivač “pok” pokazuje na

dinamički niz, brisanje dinamičkog niza realizira se pomoću naredbe:

delete[] pok;

Neophodno je napomenuti da su uglate zagrade važne. Naime, postupci dinamičke

alokacije običnih dinamičkih varijabli i dinamičkih nizova interno se obavljaju na potpuno

drugačije načine, tako da ni postupak njihovog brisanja nije isti. Iako postoje situacije u

kojima bi se dinamički nizovi mogli obrisati primjenom običnog operatora “delete” (bez

uglatih zagrada), takvo brisanje je uvijek veoma rizično, pogotovo ukoliko se radi o nizovima

čiji su elementi složeni tipovi podataka poput struktura i klasa (na primjer, brisanje niza

pomoću običnog operatora “delete” sigurno neće biti obavljeno kako treba ukoliko elementi

niza posjeduju tzv. destruktore). Stoga se treba držati pravila: dinamički nizovi se uvijek

Page 12: Leda Link - Pokazivači

moraju brisati pomoću konstrukcije “delete[]”. Ovdje treba biti posebno oprezan zbog

činjenice da nas kompajler neće upozoriti ne upotrijebimo li uglaste zagrade, s obzirom na

činjenicu da kompajler ne može znati na šta pokazuje pokazivač koji se navodi kao argument

operatoru “delete”.

Već je rečeno da bi se dinamička alokacija memorije trebala vršiti unutar “try” bloka, s

obzirom da se može dogoditi slučaj da alokacija ne uspije. Stoga, sljedeći primjer, koji alocira

dinamički niz čiju veličinu zadaje korisnik, a zatim unosi elemente niza s tipkovnice i ispisuje

ih u obrnutom poretku, ilustrira kako se ispravno treba raditi s dinamičkim nizovima:

try {

int n;

cout << "Koliko želite brojeva? ";

cin >> n;

int *niz = new int[n];

cout << "Unesite brojeve:\n";

for(int i = 0; i < 10; i++) cin >> niz[i];

cout << "Niz brojeva ispisan naopako glasi:\n";

for(int i = 9; i >= 0; i--) cout << niz[i] << endl;

delete[] niz;

}

catch(...) {

cout << "Nema dovoljno memorije!\n";

}

Obavljanje dinamičke alokacije memorije unutar “try” bloka je posebno važno kada se

vrši dinamička alokacija nizova. Naime, alokacija sigurno neće uspjeti ukoliko se zatraži

alokacija niza koji zauzima više prostora nego što iznosi količina slobodne memorije (npr.

prethodni primjer će sigurno izbaciti izuzetak u slučaju da zatražite alociranje niza od recimo

100000000 elemenata).

Treba napomenuti da se rezervacija memorije za čuvanje elemenata vektora također

interno realizira pomoću dinamičke alokacije memorije i operatora “new”. Drugim riječima,

prilikom deklaracije vektora također može doći do izbacivanja izuzetka (tipa “bad_alloc”)

ukoliko količina raspoložive memorije nije dovoljna da se kreira vektor odgovarajućeg

kapaciteta. Zbog toga bi se i deklaracije vektora načelno trebale nalaziti unutar “try” bloka.

Stoga, ukoliko bismo u prethodnom primjeru željeli izbjeći eksplicitnu dinamičku alokaciju

memorije, i umjesto nje koristiti tip “vector”, modificirani primjer trebao bi izgledati ovako:

Page 13: Leda Link - Pokazivači

try {

int n;

cout << "Koliko želite brojeva? ";

cin >> n;

vector<int> niz(n);

cout << "Unesite brojeve:\n";

for(int i = 0; i < 10; i++) cin >> niz[i];

cout << "Niz brojeva ispisan naopako glasi:\n";

for(int i = 9; i >= 0; i--) cout << niz[i] << endl;

}

catch(...) {

cout << "Nema dovoljno memorije!\n";

}

Izuzetak tipa “bad_alloc” također može biti izbačen kao posljedica operacija koje

povećavaju veličinu vektora (poput “push_back” ili “resize”) ukoliko se ne može udovoljiti

zahtjevu za povećanje veličine (zbog nedostatka memorijskog prostora). Možemo primijetiti

jednu suštinsku razliku između primjera koji koristi operator “new” i primjera u kojem se

koristi tip “vector”. Naime, u primjeru zasnovanom na tipu “vector” ne koristi se operator

“delete”. Očigledno, lokalne varijable tipa “vector” se ponašaju kao i sve druge automatske

varijable – njihov kompletan sadržaj se briše nailaskom na kraj bloka u kojem su definirane

(uključujući i oslobađanje memorije koja je bila alocirana za potrebe smještanja njihovih

elemenata).

Slično običnim dinamičkim varijablama, i dinamički nizovi se brišu tek na završetku

programa, ili eksplicitnom upotrebom operatora “delete[]”. Stoga, pri njihovoj upotrebi

također treba voditi računa da ne dođe do curenja memorije, koje može biti znatno ozbiljnije

nego u slučaju običnih dinamičkih varijabli. Naročito treba paziti da dinamički niz koji se

alocira unutar neke funkcije preko pokazivača koji je lokalna varijabla obavezno treba i

obrisati prije završetka funkcije, inače će taj dio memorije ostati trajno zauzet do završetka

programa, i nitko ga neće moći osloboditi (izuzetak nastaje jedino u slučaju ako funkcija

vraća kao rezultat pokazivač na alocirani niz – u tom slučaju onaj tko poziva funkciju ima

mogućnost osloboditi zauzetu memoriju kada ona više nije potrebna). Višestrukim pozivom

takve funkcije (npr. unutar neke petlje) možemo veoma brzo nesvjesno zauzeti svu

raspoloživu memoriju. Dakle, svaka funkcija bi prije svog završetka morala osloboditi svu

memoriju koju je dinamički zauzela (osim ukoliko vraća pokazivač na zauzeti dio memorije),

Page 14: Leda Link - Pokazivači

i to bez obzira kako se funkcija završava: nailaskom na kraj funkcije, naredbom “return”, ili

izbacivanjem izuzetka. Naročito se često zaboravlja da funkcija, prije nego što izbaci

izuzetak, također treba za sobom “počistiti” sve što je krivo napravila, što uključuje i

oslobađanje dinamički alocirane memorije. Još je veći problem ukoliko funkcija koja

dinamički alocira memoriju pozove neku drugu funkciju koja može baciti izuzetak.

Promotrimo, na primjer, sljedeći isječak:

void F(int n) {

int *pok = new int[n];

G(n);

delete[] pok;

}

U ovom primjeru, funkcija “F” zaista briše kreirani dinamički niz po svom završetku,

ali problem nastaje ukoliko funkcija “G” koju ova funkcija poziva baci izuzetak. Kako se taj

izuzetak ne hvata u funkciji “F”, ona će također biti prekinuta, a zauzeta memorija neće biti

oslobođena. Naravno, prekid funkcije “F” dovodi do automatskog brisanja automatske

lokalne pokazivačke varijable “pok”, ali dinamički niz na čiji početak “pok” pokazuje nikada

se ne briše automatski, već samo eksplicitnim pozivom operatora “delete[]”. Stoga, ukoliko se

operator “delete[]” ne izvrši eksplicitno, zauzeta memorija neće biti oslobođena. Ovaj

problem se može riješiti na sljedeći način:

void F(int n) {

int *pok = new int[n];

try {

G(n);

}

catch(...) {

delete[] pok;

throw;

}

delete[] pok;

}

Page 15: Leda Link - Pokazivači

U ovom slučaju u funkciji “F” izuzetak koji eventualno izbacuje funkcija “G” hvatamo

samo da bismo mogli izvršiti brisanje zauzete memorije, nakon čega uhvaćeni izuzetak

prosljeđujemo dalje, navođenjem naredbe “throw” bez parametara. Iz ovog primjera vidimo

da je bitno razlikovati sam dinamički niz od pokazivača koji se koristi za pristup njegovim

elementima. Ovdje je potrebno ponovo napomenuti da se opisani problemi ne bi pojavili

ukoliko bismo umjesto dinamičke alokacije memorije koristili automatsku varijablu tipa

“vector” – ona bi automatski bila uništena po završetku funkcije “F”, bez obzira da li je do

njenog završetka došlo na prirodan način, ili bacanjem izuzetka iz funkcije “G”. U suštini, kad

god možemo koristiti tip “vector”, njegovo korištenje je jednostavnije i sigurnije od korištenja

dinamičke alokacije memorije. Stoga, tip “vector” treba koristiti kad god je to moguće –

njegova upotreba sigurno neće nikada dovesti do curenja memorije. Jedini problem je u tome

što to nije uvijek moguće. Na primjer, nije moguće napraviti tip koji se ponaša slično kao tip

“vector” (ili sam tip “vector”) bez upotrebe dinamičke alokacije memorije i dobrog

razumijevanja kao dinamička alokacija memorije funkcionira.

Iz svega što je do sada rečeno može se zaključiti da dinamička alokacija memorije

sama po sebi nije komplicirana, ali da je potrebno preduzeti dosta mjera predostrožnosti da ne

bi došlo do curenja memorije.

Ponašanje operatora “delete” je nedefinirano (i može rezultirati krahom programa)

ukoliko se primijene na pokazivač koji ne pokazuje na prostor koji je zauzet u postupku

dinamičke alokacije memorije. Naročito česta greška je primijeniti operator “delete” na

pokazivač koji pokazuje na prostor koji je već obrisan (tj. na viseći pokazivač). Ovo se, na

primjer može dogoditi ukoliko dva puta uzastopno primijenimo ove operatore na isti

pokazivač (kojem u međuvremenu između dvije primjene operatora “delete” nije dodijeljena

neka druga vrijednost). Da bi se izbjegli ovi problemi veoma dobra ideja je eksplicitno

dodijeliti NULL-pokazivač svakom pokazivaču nakon izvršenog brisanja bloka memorije na

koju on pokazuje. Na primjer, ukoliko “pok” pokazuje na prvi element nekog dinamičkog

niza, brisanje tog niza najbolje je izvesti sljedećom konstrukcijom:

delete[] pok;

pok = 0;

Eksplicitnom dodjelom “pok = 0” zapravo postižemo dva efekta. Prvo, takvom

dodjelom eksplicitno naglašavamo da pokazivač “pok” više ne pokazuje ni na što. Drugo,

ukoliko slučajno ponovo primijenimo operator “delete” na pokazivač “pok”, neće se dogoditi

ništa, jer je on sada NULL-pokazivač.1

1 u poglavlju o dinamičkim nizovima je dosta korišten web dokument sa stranice: http://www.scribd.com/doc/11542041/C-Pokazivaci3

Page 16: Leda Link - Pokazivači

5. Pokazivači na pokazivače

U jeziku C++ pokazivači na pokazivače pretežno se koriste za potrebe dinamičke

alokacije dvodimenzionalnih nizova, dok su se u jeziku C dvojni pokazivači intenzivno

koristili u situacijama u kojima je u jeziku C++ prirodnije upotrijebiti reference na pokazivač

(tj. reference vezane za neki pokazivač). Na primjer, pomoću deklaracije

int *&ref = pok;

deklariramo referencu “ref” vezanu na pokazivačku varijablu “pok” (tako da je “ref” zapravo

referenca na pokazivač). Sada se “ref” ponaša kao alternativno ime za pokazivačku varijablu

“pok”. Važno je obratiti pažnju na redoslijed znakova “*” i “&”. Ukoliko bismo zamijenili

redoslijed ovih znakova, umjesto reference na pokazivač pokušali bismo deklarirati pokazivač

na referencu, što nije dozvoljeno u jeziku C++ (postojanje pokazivača na referencu omogućilo

bi pristup internoj strukturi reference). Inače, deklaracije svih pokazivačkih tipova mnogo su

jasnije ako se čitaju s desna na lijevo (tako da uz takvo čitanje, iz prethodne deklaracije jasno

vidimo da “ref” predstavlja referencu na pokazivač na cijele brojeve).

Reference na pokazivače najčešće se koriste ukoliko je potrebno neki pokazivač

prenijeti po referenci kao parametar u funkciju, što je potrebno npr. ukoliko funkcija treba

izmijeniti sadržaj samog pokazivača (a ne objekta na koji pokazivač pokazuje).

Na primjer, sljedeća funkcija vrši razmjenu dva pokazivača koji su joj proslijeđeni kao stvarni

parametri:

void RazmjenaPok(double *&x, double *&y) {

double *pomocna = x; x = y; y = pomocna;

}

Kako u jeziku C nisu postojale reference, sličan efekt se mogao postići jedino

upotrebom dvojnih pokazivača, kao u sljedećoj funkciji

void RazmjenaPok(double **p, double **q) {

double *pomocna = *p;

*p = *q; *q = pomocna;

}

Naravno, prilikom poziva ovakve funkcije “RazmjenaPok”, kao stvarne argumente

morali bismo navesti adrese pokazivača koje želimo razmijeniti (a ne same pokazivače), pri

čemu nakon uzimanja adrese dobivamo dvojni pokazivač. Drugim riječima, za razmjenu dva

pokazivača “p1” i “p2” (na tip “double”) morali bismo izvršiti sljedeći poziv:

RazmjenaPok(&p1, &p2);

Page 17: Leda Link - Pokazivači

Očigledno, upotreba referenci na pokazivače (kao uostalom i upotreba bilo kakvih

referenci) oslobađa korisnika funkcije potrebe da eksplicitno razmišlja o adresama. Svi do

sada prikazani postupci dinamičke alokacije matrica nisu vodili računa o tome da li su

alokacije zaista uspjele ili nisu. U realnim situacijama moramo i o tome voditi računa, tako da

bismo dinamičku alokaciju matrice realnih brojeva sa “n” redova i “m” kolona zapravo trebali

realizirati ovako:

try {

double **a = new int*[n];

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

try {

for(int i = 0; i < n; i++) a[i] = new int[m];

}

catch(...) {

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

delete[] a;

throw;

}

}

catch(...) {

cout << "Problemi s memorijom!\n";

}

6. Polja i pokazivači

Do sada smo duljinu polja uvijek definirali fiksno, unaprijed. Pokazivači nam

omogućuju da duljinu polja definiramo ovisno o parametru u programu tako da alociramo

točno onoliko memorijskog prostora koliko nam je potrebno. U programskom jeziku C i C++

Page 18: Leda Link - Pokazivači

postoji čvrsta veza između pokazivača i polja. Bilo koja operacija koju možemo obaviti preko

indeksa polja, može se obaviti i preko pokazivača. Primjer polja:

int a[10];

definira polje veličine 10, što će predstavljati skup od deset objekata a[0], a[1], a[2]....a[9].

Pri tome a[i] odgovara i-tom elementu polja. Ako je pok pokazivač na cjelobrojnu vrijednost,

deklariran kao

int *pok;

tada naredba

pok=&a[0];

dodjeljuje varijabli pok vrijednost koja je ekvivalentna adresi prvog elementa polja a.

Sad naredba

x=*pok;

prebacuje sadržaj a[0] u x. Ako pok pokazuje na određeni element polja, onda po definiciji

pok+1 pokazuje na sljedeći element, pok+i pokazuje na i-ti element poslije prvog, dok pok-1

pokazuje na i-ti element prije prvog. Stoga ako pok pokazuje na a[0],

*(pok+1)

se odnosi na sadržaj od a[1], pok+i je adresa od a[i], a *(pok+i) je sadržan u a[i].

Page 19: Leda Link - Pokazivači

Na ovaj način je prikazano da tehnika rada s pokazivača nalikuje na rad s indeksima polja.

Ipak, kao što je već unutar ovog seminarskog rada pokazano, mogućnosti pokazivača su

svakako veće.

7. Vezana lista i pokazivači

Svaki element vezane liste sadrži pokazivač s adresom slijedećeg elementa liste.

Pokazivač zadnjeg elementa u listi ima vrijednost NULL, što će pokazati da je na tom mjestu

kraj liste. Ukoliko dodajemo novi element unutar liste, tada ćemo njegovu adresu pridruživati

pokazivaču do zadnjeg elementa liste. To nam pokazuje da broj elemenata nije unaprijed

zadan (zato se vezana lista i smatra dinamičkom strukturom), već se mogu po potrebi dodavati

novi elementi sve dok ima raspoloživog memorijskog prostora.

Vezana lista je vrlo slična polju.

Razlike između liste i polja:

– U polju se elementima pristupa izravno, preko indeksa, dok se elementima

vezane liste pristupa slijedno

– Polju mora broj elemenata biti zadan prije korištenja polja dok se broj

elemenata vezane liste dinamički mijenja

Kod dodavanja novog elementa u vezanu listu trebamo alocirati memorijski prostor na

kojem će se taj element nalaziti. Treba napomenuti da taj memorijski prostor nije ni u kakvoj

Page 20: Leda Link - Pokazivači

vezi s memorijskim prostorom koji je dodjeljen pri alokaciji prethodno dodanih elemenata

liste. Upravo zbog toga je potrebno da elemente vezane liste povezujemo pokazivačima. Jasno

je da će pokazivač nakon dodavanja novog elementa morati pokazivati na upravo dodani

element. No prije nego što mu pridružimo adresu novog elementa, potebno je povezati novi

element ostatkom liste. Ako to nebismo učinili, ostatak liste bi bio izgubljen, te više ne bismo

znali na kojem se on mjestu nalazi. To ćemo učiniti tako da vrijednost pokazivača zapišemo u

slog novododanog elementa liste. Na taj će način pokazivač iz novododanog elementa

pokazivati na sljedeći element vezane liste. Tek nakon što to učinimo možemo preusmjeriti

pokazivač tako da pokazuje na novododani element.

Kod brisanja elemenata s početka liste pokazivač treba preusmjeriti tako da pokazuje

na drugi element u vezanoj listi. No i pri ovoj operaciji, kao i pri dodavanju elementa, treba

paziti na redoslijed. Naime, ako jednostavno preusmjerimo pokazivač na drugi element u

vezanoj listi, onda će se izgubiti adresa elementa kojeg smo upravo izbacili. Taj element nam

više nije potreban, pa nam to izbacivanje nije važno. Međutim, memorijski prostor koji je taj

element zauzimao još uvijek ostaje zauzet, iako se više ne koristi. Takav odnos može

uzrokovati brzo zauzimanje raspoložive memorije. Iz tog razloga je vrlo važno da se svaki

memorijski prostor koji je dinamički alociran nakon korištenja vrati operacijskom sustavu,

odnosno dealocira. Zbog toga se prije nego što se pokazivač preusmjeri, njegova vrijednost,

odnosno adresa prvog elementa u vezanoj listi mora zapisati u pomoćni pokazivač, kako bi se

nakon premještanja pokazivača na sljedeći element liste prostor koji je obrisani element

zauzimao mogao dealocirati.

8. Strukture i pokazivači

Pokazivači se mogu koristiti kao članovi strukture, a deklariraju se na isti način kao i

ostali pokazivači koji nisu dio strukture – operatorom diferencijacije *.

Primjer:

struct koordinate {

int *x;

int *y;

} struktura1;

Ove naredbe će definirati i deklarirati strukturu čija su dva člana pokazivači na tip

podataka int. Kao i kod svih pokazivača, njihovo deklariranje nije dovoljno; moraju im se

pridružiti i adrese varijabli da bi ih se moglo inicijalizirati da pokazuju negdje:

Page 21: Leda Link - Pokazivači

struktura1.x = &lijevogore;

struktura2.y = &desnodolje;

Sad kad su pokazivači inicijalizirani, može se rabiti operator *. Izraz struktura1.x dat

će vrijednost varijable lijevogore, a izraz struktura.y dat će vrijednost varijable desnodolje.

C program može deklarirati i rabiti pokazivače na strukture, kao i na sve ostale tipove

za pohranu podataka. Pokazivači na strukture često se rabe kada se struktura prosljeđuje

funkciji kao argument. Evo kako u programu može načiniti i rabiti pokazivači na strukture.

Prvo, definiranje strukture:

struct adresa {

char ulica[81];

char grad[81];

};

Sada se deklarira pokazivač na tip adresa:

struct adresa *p_adresa;

U ovom trenutku ne treba inicijalizirati pokazivač jer ni struktura još nije deklarirana, nego je

tek definirana. Deklaracija:

struct adresa osoba;

Sada se može inicijalizirati pokazivač:

p_adresa = &osoba; -ova naredba pridružuje adresu osoba u p_adresa.

Recimo, da bismo pridružili vrijednost "Ilica" članu osoba.ulica, napisat ćemo to ovako:

(*p_adresa).ulica = "Ilica";

Ovim primjerom je pokazano da se pokazivači mogu koristiti u kompleksnijim

tipovima podataka kao što su strukture, što je itekako važno jer pokazuje da je upotreba

pokazivača uistinu vrlo raširena.

Zaključak

U ovom seminarskom radu obradila sam temu pokazivača koja je u suštini vrlo

opširna. Iz tog razloga nisam mogla u potpunosti objediniti sve funkcionalnosti pokazivača na

jedno mjesto. Ipak, pokazala sam neke osnovne funkcionalnosti, te prikazala kako se

pokazivači ponašaju unutar polja, vezane liste, dinamičkih nizova, te struktura.

Page 22: Leda Link - Pokazivači

Pokazivači se unutar programskog jezika C i C++ koriste ponajviše iz razloga što

optimiziraju programski kod. Svakako je važno za svaki programski jezik da je omogućen

način kako smanjiti količinu linija koda jer većina zahtjevnijih programskih kodova ima

nemali broj linija. Iz tog razloga su pokazivači ipak vrlo često korišteni.

U programskom jeziku C++, najvažnije primjene pokazivača su:

- prijenos argumenata funkciji po referenci

- implementacija nizova

- implementacija struktura

Literatura

http://grube.web.srk.fer.hr/marcupic/COsnove/Pokazivaci.html

http://grube.web.srk.fer.hr/marcupic/COsnove/Pokazivaci.html

http://www.scribd.com/doc/11542041/C-Pokazivaci3

http://www.scribd.com/doc/11542034/C-Pokazivac-Na-Pokazivac

http://www.cplusplus.com/doc/tutorial/pointers/

http://www.augustcouncil.com/~tgibson/tutorial/ptr.html

http://www.linuxconfig.org/c-understanding-pointers

http://www.exforsys.com/tutorials/c-plus-plus/c-plus-plus-pointers.html

http://en.wikipedia.org/wiki/Pointer_%28computing%29

Page 23: Leda Link - Pokazivači

- svim poveznicama je pristupljeno do 20.12.2010.