You are on page 1of 18

PROGRAMIRANJE 2

LABORATORIJSKE VJEBE

LV10
Strukture podataka i dinamika
alokacija memorije

Elektrotehniki fakultet Osijek


Kneza Trpimira 2b
www.etfos.unios.hr
1 UVOD

Do sada smo u vie vjebi pokrili rad sa strukturama, njihovo kreiranje, prosljeivanje preko
pokazivaa funkcijama, upisivanje u datoteku i itanje iz datoteke te openito svrhu
struktura podataka i rad s njima.

U ovoj vjebi obradit emo kako se moe dinamiki alocirati memorija za spremanje
strukture podataka te koja je svrha toga. Potrebna e biti predznanja iz prethodnih vjebi
gdje smo obradili rad sa strukturama te vjebi gdje se obraivala dinamika alokacija
memorije.
2 DINAMIKA ALOKACIJA MEMORIJE ZA STRUKTURU

Do sada smo dinamiku alokaciju memorije koristili samo za alociranje memorije za


osnovne tipove podataka. Podsjetimo se kako izgleda primjer programa koji e alocirati
memoriju za polje znakova:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
int n = 100;
char orig[] = "Ovo je string koji cemo kopirati u dinamicki alocirani";
char *p;
p = (char*)malloc(n*sizeof(char));
strcpy(p,orig);
puts(orig);
puts(p);
return 0;
}

Funkcija koja u gornjem primjeru stvarno alocira memoriju za ovo polje je funkcija malloc,
kojoj moramo predati koliki broj byteova elimo alocirati, a ona nam vraa adresu u
memoriji raunala na kojoj je taj eljeni broj byteova alociran, ili NULL ako nije uspjela
alokacija.

Kada elimo dinamiki alocirati memoriju za strukturu, koristit emo istu funkciju kao i kod
osnovnih tipova podataka, razlika je jedino u castanju rezultata poziva funkcije. Budui da
funkcija malloc vraa pokaziva tipa void (to znai da vraa samo adresu, a ne zna koji tip
podataka e biti spremljen na toj adresi) mi moramo eksplicitno, kao u gornjem primjeru,
castati tu adresu na tip podatka za koji smo alocirali memoriju. Budui da smo alocirali
memoriju za tip podatka char, moramo adresu eksplicitno castati na pokaziva na char,
prije nego adresu pridijelimo pokazivau.

Na isti nain emo castati i adresu koju nam vrati malloc pri alokaciji memorije za
strukturu:

#include <stdio.h>
#include <stdlib.h>

typedef struct tocka {


float x;
float y;
float z;
} Tocka;

int main(void)
{
Tocka t= { 3.0, 2.0, 1.0 };
Tocka *d;

d = (Tocka*)malloc(sizeof(Tocka));
d->x = t.x;
d->y = t.y;
d->z = t.z;

printf("%.2f %.2f %.2f", d->x, d->y, d->z);

free(d);
return 0;
}

U ovom primjeru vidimo kako je deklarirana struktura struct tocka kojoj kao tipu podatka
dali ime Tocka. Tako pri alokaciji memorije moramo castati adresu koju vrati malloc na
pokaziva na tip podatka Tocka. Vidimo da kao i u prijanjim vjebama, kada pristupamo
elementima strukture preko pokazivaa, moramo koristiti operator strjelicu.

Takoer, mogua je alokacija memorije za polje struktura, gdje je ponovno sintaksa same
alokacije jako slina prijanjim primjerima:

int main(void)
{
int n=10;
Tocka *polje;
polje = (Tocka*)malloc( n * sizeof(Tocka) );
for (int i = 0; i < n; i++) {
scanf("%f%f%f", &polje[i].x, &polje[i].y, &polje[i].z);
printf("-->%f %f %f<--\n", polje[i].x, polje[i].y, polje[i].z);
}

free(polje);
return 0;
}

Primijetimo kako se u ovom sluaju sintaksa alokacije jako malo mijenja, jedina promjena je
broj elemenata kojim mnoimo sizeof funkciju. Ponovimo, to je zato to funkcija sizeof
vraa veliinu u bajtima odreenog tipa podatka (u ovom sluaju strukture Tocka), te tu
veliinu mnoimo s brojem elemenata koje e polje imati. Takoer, bitno je naglasiti kako u
ovom sluaju lanovima strukture pristupamo s operatorom toka, jer operacijom
pristupanja elementu polja odraujemo dereferenciranje pokazivaa polje, pa je rezultat te
operacije struktura, a ne adresa. Bitno je pripaziti kada se kako pristupa kojem elementu.

2.1 RAD S FUNKCIJAMA

Promotrimo slijedei primjer:

#include <stdio.h>
#include <stdlib.h>
typedef struct tocka {
int x;
int y;
int z;
} Tocka;

Tocka static_add(Tocka p1, Tocka p2) {


Tocka res;
res.x = p1.x + p2.x;
res.y = p1.y + p2.y;
res.z = p1.z + p2.z;
return res;
}
Tocka* dynamic_add(Tocka p1, Tocka p2) {
Tocka* res = (Tocka*)malloc(sizeof(Tocka));
res->x = p1.x + p2.x;
res->y = p1.y + p2.y;
res->z = p1.z + p2.z;
return res;
}

int main(void)
{
Tocka a={1,2,3}, b={2,3,4};
Tocka p;
Tocka *q;

puts("static add: ");


p = static_add(a, b);
printf("rezultat: %d, %d, %d; adresa: %p \n", p.x, p.y, p.z, p);
p = static_add(a, b);
printf("rezultat: %d, %d, %d; adresa: %p \n", p.x, p.y, p.z, p);

puts("dynamic add: ");


q = dynamic_add(a, b);
printf("rezultat: %d, %d, %d; adresa: %p \n", q->x, q->y, q->z, q);
q = dynamic_add(a, b);
printf("rezultat: %d, %d, %d; adresa: %p \n", q->x, q->y, q->z, q);
return 0;
}

Rezultat izvoenja:

static add:
rezultat: 3, 5, 7; adresa: 0x7ffeb2d61ea0
rezultat: 3, 5, 7; adresa: 0x7ffeb2d61ea0
dynamic add:
rezultat: 3, 5, 7; adresa: 0x97c010
rezultat: 3, 5, 7; adresa: 0x97c030

U ovom sluaju definirane su dvije funkcije za zbrajanje struktura. Jedna je funkcija


static_add koja stvara novu lokalnu varijablu res, u tu varijablu zbraja vrijednosti lanova
dvije predane toke te vraa kopiju podatka u strukturu p. Bitno je naglasiti kako ova
funkcija jednostavno prekopira rezultat svog izvoenja u varijablu p. To znai da varijabla p
mora biti definirana kao strukturna varijabla, te prostor za nju mora ve biti alociran.
Varijabla res unutar funkcije je lokalna varijabla, koja se nakon izvoenja unitava te podaci
spremljeni u njoj nestaju. Mehanizam rada ove funkcije trebao bi biti jasan s prijanjih
vjebi.

Pogledajmo nasuprot nje funkciju dynamic_add. Ova funkcija vraa adresu strukture koja
e sadravati rezultat izvoenja. Za razliku od prole funkcije, kada je funkcija vraala kopiju
podataka, ova funkcija vraa adresu gdje se u memoriji nalaze podaci. Specifinost ove
funkcije u odnosu na funkcije koje smo do sada obraivali je u injenici da ova funkcija
alocira memoriju za rezultat koji vraa. U ovoj funkciji se takoer kreira lokalna varijabla res
koja e u ovom sluaju sadravati adresu gdje je u memoriji alocirano mjesto za spremanje
rezultata zbrajanja. Ovdje je bitno naglasiti kako je varijabla res i u ovom sluaju lokalna
varijabla, te se nakon zavretka izvravanja ove funkcije ona brie. Ipak, ono to je jako
razliito od prethodnog primjera je injenica da je ova varijabla pokaziva, a kako je
memorija za strukturu na koju pokazuje alocirana dinamiki, nakon zavretka izvoenja ove
funkcije ona se nee dealocirati. Pri ponovnom pozivu ove funkcije alocirat e se novo
memorijsko mjesto za spremanje strukture, te e se vratiti adresa gdje se u memoriji nalazi
ta nova alokacija.

To moemo vidjeti u ispisu gornjeg primjera, gdje se nakon izvoenja svakog od poziva
funkcija ispisuje adresa gdje se u memoriji nalazi taj podatak. Vidimo kako dva uzastopna
poziva funkcije static_add spremaju rezultat na istu adresu, dok dva uzastopna poziva
funkcije dynamic_add spremaju rezultat na razliite adrese. Takoer je bitno naglasiti da
drugim pozivom funkcije dynamic_add prepisuje se adresa spremljena u pokazivau q, ali
se alocirana memorija ne oslobaa i podaci ostaju zapisani u memoriji. Ova memorija moe
se osloboditi samo pozivom funkcije free. Kako e sada u pokazivau q biti spremljena
adresa nove strukture, prethodna memorijska adresa je nepovratno izgubljena. Vie nije
mogue doi do podataka koji su spremljeni na toj memorijskoj adresi, jer vie niti jedna
varijabla u programu ne pamti tu adresu, ali ti podaci i dalje postoje i zauzimaju memoriju.
Ukoliko bi takav postupak ponovili puno puta, rezultat bi bio velik broj memorijskih lokacija
koje je na program zatraio od operacijskog sustava i zauzeo, ali ih vie ne moe osloboditi
jer vie ne zna adrese tih memorijskih lokacija i ne moe ih osloboditi.

Ovakav sluaj naziva se curenje memorije (eng. Memory leak), te predstavlja velik problem
pri radu s pokazivaima i dinamikom alokacijom memorije. Operacijski sustav takoer ne
zna koje memorijske adrese smije osloboditi dok mu to eksplicitno ne javi program, jer
operacijski sustav pamti sve memorijske adrese koje su dodijeljene nekom programu, ali ne
provjerava koristi li taj program te memorijske lokacije ili ne (jer to ni nije u nadlenosti
operacijskog sustava). Tako zauzeta memorija moe se osloboditi jedino tako to e
operacijski sustav prekinuti izvoenje programa, te samim time osloboditi sve resurse koje
je program zauzeo. Ovaj problem je neprimjetan kod ovako malih programa kao to je
gornji primjer, ali je ovaj problem vrlo est u svim kompleksnijim programima pisanim u C i
C++ jezicima, a pogotovo u programima koji moraju raditi due vrijeme i zauzimaju puno
memorije (web serveri, baze podataka, raunalne igre) te predstavljaju vrlo est razlog
ruenja programa i usporavanja cijelog sustava.

Posao je programera pobrinuti se da su sve dinamiki zauzete memorijske lokacije (bile ono
strukture ili osnovni tipovi podataka) osloboene pozivom naredbe free nakon to
programu vie nisu potrebne.

U naem primjeru to bi izveli dodavanjem naredbe free(q) prije ponovnog poziva funkcije
dynamic_add.

q = dynamic_add(a, b);
printf("rezultat: %d, %d, %d; adresa: %p \n", q->x, q->y, q->z, q);
free(q);
q = dynamic_add(a, b);
2.2 POKAZIVA NA STRUKTURU KAO LAN STRUKTURE

Do sada smo spominjali pokazivae na strukture koji su bili lanovi neke druge strukture.
Primjer iz prolih vjebi je bio onaj s tokama i trokutima. Ipak, do sada nismo imali primjer
gdje je lan strukture bio pokaziva na strukturu istog tipa. Bitno je jo jednom napomenuti
kako lan strukture ne moe biti istog tipa kao i struktura:

struct kupac {
char ime[100];
struct kupac next; //nemogue
} prvi;

Ipak, lan strukture moe biti pokaziva na isti tip podatka kao to je i sama struktura:

struct kupac {
char ime[100];
struct kupac *next; //mogue
} prvi;

Ovakav tip podatka nazivamo samoreferentna struktura, a koristi se za kreiranje sloenih


podatkovnih struktura kao to su stabla, povezane liste i slino. U ovom sluaju, svaka
struktura tipa kupac ima lan next koji je pokaziva na strukturu tipa kupac. Na taj nain
mogue je povezati istovrsne podatke u listu tako to e next pokazivati na slijedeeg
kupca u listi. Posljednji element u listi kupaca imat e u lanu next upisanu vrijednost
NULL, pa na taj nain znamo gdje zavrava lista. Pogledajmo slijedei kod:

struct kupac {
char *ime;
int id;
struct kupac *next;
};

int main(void) {
struct kupac prvi;
struct kupac *novi;
char buff[] = "Petar Kresimir Cetvrti";
char buff2[]= "Zvonimir Zadnji";

//prvi je strukturna varijabla pa mu pristupamo s tockom


prvi.ime = (char*)malloc(sizeof(buff));
strcpy(prvi.ime, buff);
prvi.id = 100;
prvi.next = NULL;

//novi je pokazivac pa mu pristupamo sa strjelicom


novi = (struct kupac*) malloc ( sizeof(struct kupac));
novi->ime = (char*) malloc(sizeof(buff2));
strcpy(novi->ime, buff2);
novi->id = 200;
novi->next = NULL;

prvi.next = novi;
puts( prvi.ime);
puts( prvi.next->ime);
printf("adresa prvog: %p\n", &prvi);
printf("adresa novog: %p\n", prvi.next);
printf("adresa slijedeeg: %p\n", prvi.next->next);

return 0;
}

Rezultat izvoenja koda:

Petar Kresimir Cetvrti


Zvonimir Zadnji
adresa prvog: 0x7ffe55664eb0
adresa novog: 0x137e030
adresa slijedeeg: (nil)

Definirali smo strukturnu varijablu tipa kupac naziva prvi i pokaziva na strukturu tipa
kupac naziva novi. Budui da je prvi strukturna varijabla, za nju je memorija ve alocirana i
ne moe se mijenjati lokacija gdje se u memoriji sprema. Isto tako, kada spremamo podatke
u varijablu prvi njegovim elementima pristupamo s operatorom toka. Primijetimo kako
struktura kupac ima lanove ime, id i next, gdje je ime pokaziva na polje znakova, id je
cjelobrojna vrijednost, dok je next pokaziva na strukturu tipa kupac. Zbog toga pri
upisivanju imena u strukturu moramo koristiti naredbu strcpy() kako bi mogli upisati ime
kupca. Naravno, prvo je potrebno alocirati memoriju za ime, to inimo funkcijom malloc na
ve poznati nain. Adresu na koju pokazuje next postavljamo na NULL, to oznaava da je
varijabla prvi i prvi i zadnji lan liste kupaca.

Nakon toga alociramo memoriju za novu strukturu tipa kupac, iju adresu spremamo u
pokaziva novi. Na isti nain kao i do sada postavljamo podatke u strukturu na koju
pokazuje novi. Postavljanjem vrijednosti prvi.next na adresu koja je spremljena u
pokazivau novi, uspjeno smo kreirali povezanu listu. Sada podacima spremljenim u
novostvorenoj strukturi osim preko pokazivaa novi moemo pristupiti i preko varijable
prvi.

Prema rezultatu ispisa vidimo na kojim su adresama spremljene strukture. Vidimo kako je
adresa spremljena u pokazivau next druge strukture jednaka NULL, to znai da je taj
element liste posljednji u listi.

Bitno je naglasiti da se ovako povezanoj listi ne moe direktno pristupiti i-tom elementu
liste, jer ne znamo njegovu lokaciju. Svaki element liste je dinamiki alociran i elementi liste
su 'razbacani' po memoriji. Zbog toga kada elimo pristupiti bilo kojem elementu liste,
moramo prolaziti kroz sve elemente liste od prvog elementa. Pogledajmo slijedei kod:

// pretpostavimo postojanje povezane liste s upisanim podacima,


// ciji je prvi element spremljen u pokazivacu prvi
struct kupac *iter = NULL;
for (iter = prvi; iter != NULL; iter = iter->next) {
puts(iter->ime);
printf("%d", iter->id );
}
Na ovaj nain prolazimo kroz sve elemente liste dok ne doemo do kraja liste. Kraj liste je
oznaen uvjetom kada iterator iter dobije vrijednost NULL. Prema tome, povezana lista e
izgledati kao na slijedeoj slici:

Prema tome, kada elimo dodati novi element u listu, dinamiki emo alocirati memoriju za
taj element, te emo njegovu adresu spremiti u pokaziva next unutar zadnjeg elementa u
listi. Kako bi mogli jednostavno dodavati elemente u listu, moemo napisati funkciju koja e
nam omoguiti dodavanje novog elementa na kraj liste.

Promotrimo sada slijedeu funkciju:

void append(struct kupac *prvi) {


puts("Dodavanje novog kupca");
struct kupac *novi = NULL;
struct kupac *iter = NULL;
if(prvi->ime == NULL) { //prazna lista
//ucitaj podatke i postavi next zadnjeg elementa na NULL
printf("ime: ");
prvi->ime = read_name();
printf("id: ");
scanf("%d", &prvi->id);
getchar();
prvi->next = NULL;
}
else { //pronadji kraj liste
novi = (struct kupac*) malloc(sizeof(struct kupac));
for (iter = prvi; iter->next != NULL; iter = iter->next) {}; //prazan for
//postavi zadnji element liste na novi
iter->next = novi;
//ucitaj podatke i postavi next zadnjeg elementa na NULL
printf("ime: ");
novi->ime = read_name();
printf("id: ");
scanf("%d", &novi->id);
getchar();
novi->next = NULL;
}
}

U ovom sluaju pretpostavljamo da je memorija za prvi element liste ve alocirana, te u


funkciju prosljeujemo adresu na kojoj se nalazi taj element. Kako je lan ime unutar kupca
pokaziva na polje znakova, ovisno o tome je li memorija za to ime alocirana moemo
utvrditi jesu li podaci za nekog kupca ve upisani u prvi element. Ukoliko je ime==NULL
tada se radi o praznom lanu, pa emo upisati podatke o kupcu u predani element liste.
Ukoliko neko ime kupca ve postoji, znai da su neki podaci ve upisani, te se tada trai
zadnji element liste. Dinamiki se alocira memorija za novi element liste, pa se pokaziva
next zadnjeg elementa liste usmjerava na novi dinamiki alocirani lan koji tada postaje
zadnji element liste.

Takoer, budui da ne moemo pristupiti i tom elementu liste direktno, mogue je


pretraivati prolaskom kroz listu. Pogledajmo slijedee dvije funkcije:

struct kupac* find_by_name(struct kupac *prvi, char *name) {


struct kupac *iter;
for( iter=prvi; iter != NULL; iter=iter->next) {
if( strcmp(iter->ime, name) == 0 )
return iter;
}
return NULL;
}

struct kupac* find_by_id(struct kupac *prvi, int id) {


struct kupac *iter;
for( iter=prvi; iter != NULL; iter=iter->next) {
if( iter->id == id )
return iter;
}
return NULL;
}

U ovom sluaju definirali smo dvije funkcije koje e proi kroz listu, testirati svaki element
prema nekom odreenom uvjetu, te vratiti adresu elementa liste ukoliko odgovara uvjetu,
a NULL ako ne odgovara uvjetu. Ovdje imamo dva uvjeta koja testiramo: je li id trenutno
gledanog elementa liste jednak predanom id-u, te je li ime trenutno gledanog elementa
liste jednako predanom imenu.

Na slian nain mogue je implementirati i brisanje elemenata iz liste, kao i dodavanje na


neko drugo mjesto u rasporedu, umjesto na kraj. Takoer, ovakve liste mogue je i sortirati.
Ipak, u ovoj vjebi neemo se baviti takvim funkcionalnostima, one e se obraivati na
kolegiju Algoritmi i strukture podataka.
3 RIJEENI PRIMJERI

Primjer 1.

Ovaj primjer pokazuje cjelokupnu implementaciju povezane liste opisanu u prethodnom


potpoglavlju:

1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 struct kupac {
6 char *ime;
7 int id;
8 struct kupac *next;
9 };
10
11 char* read_name() {
12 int i;
13 char *name;
14 char buff[100];
15 fgets(buff, 100, stdin);
16 for ( i=0; buff[i] != '\n'; i++) {} //prazan for da se dodje na kraj polja
17 buff[i] = '\0';
18 name = (char*) malloc( strlen(buff) * sizeof(char) );
19 strcpy(name, buff);
20 return name;
21 }
22
23 struct kupac* init_list() {
24 struct kupac *novi = (struct kupac*) malloc(sizeof(struct kupac));
25 novi->ime = NULL;
26 novi->next = NULL;
27 return novi;
28 }
29
30 struct kupac* find_by_name(struct kupac *prvi, char *name) {
31 struct kupac *iter;
32 for( iter=prvi; iter != NULL; iter=iter->next) {
33 if( strcmp(iter->ime, name) == 0 )
34 return iter;
35 }
36 return NULL;
37 }
38
39 struct kupac* find_by_id(struct kupac *prvi, int id) {
40 struct kupac *iter;
41 for( iter=prvi; iter != NULL; iter=iter->next) {
42 if( iter->id == id )
43 return iter;
44 }
45 return NULL;
46 }
47
48 void append(struct kupac *prvi) {
49 puts("Dodavanje novog kupca");
50 struct kupac *novi = NULL;
51 struct kupac *iter = NULL;
52 if(prvi->ime == NULL) { //prazna lista
53 //ucitaj podatke i postavi next zadnjeg elementa na NULL
54 printf("ime: ");
55 prvi->ime = read_name();
56 printf("id: ");
57 scanf("%d", &prvi->id);
58 getchar();
59 prvi->next = NULL;
60 }
61 else { //pronadji kraj liste
62 novi = (struct kupac*) malloc(sizeof(struct kupac));
63 for (iter = prvi; iter->next != NULL; iter = iter->next) {}; //prazan for
64 //postavi zadnji element liste na novi
65 iter->next = novi;
66 //ucitaj podatke i postavi next zadnjeg elementa na NULL
67 printf("ime: ");
68 novi->ime = read_name();
69 printf("id: ");
70 scanf("%d", &novi->id);
71 getchar();
72 novi->next = NULL;
73 }
74 }
75
76 int main(void) {
77 struct kupac *prvi = init_list();
78 struct kupac *iter = NULL;
79 int n = 5;
80 int i;
81
82 for (i = 0; i < n; i++) {
83 append(prvi);
84 }
85
86 for( iter=prvi; iter != NULL; iter=iter->next) {
87 puts(iter->ime);
88 }
89
90 while(1) {
91 char b[100];
92 gets(b);
93
94 iter = find_by_name(prvi, b);
95 if( iter != NULL )
96 printf(" Nadjen %s \n", iter->ime);
97 else
98 printf(" Nije nadjen! \n");
99 }
100 return 0;
101 }

Kratko obrazloenje [Primjer 1]: U funkciji main za prvi element liste pozivamo funkciju
init_list(), iji je zadatak alocirati memoriju za prvi element liste i njegove lanove
postaviti na NULL. Razlog tome je jer u funkciji append() na nain opisan u prethodnom
potpoglavlju testiramo je li lista prazna ili nije. Jo jedna posebnost je funkcija
read_name(), koja vraa adresu polja u koje je spremljeno ime. Funkcija omoguuje upis
imena, te budui da koristi funkciju fgets(stdin) koja upisuje i '\n' u polje ona brie taj
'\n' iz polja, dinamiki alocira memoriju za upisani tekst, kopira ga u dinamiki alociranu
memoriju te adresu te memorije vraa kao rezultat. Zato se u pokaziva ime elementa liste
jednostavno spremi povratni rezultat te funkcije, koji je adresa upisanog stringa.
Primjer 2. Zamislimo kako elimo napisati program koji e pronai dvije najvie toke i
izraunati njihovu udaljenost. Znamo kako je udaljenost u trodimenzionalnom prostoru
izmeu dvije toke definirana slijedeom formulom:

= (2 1 )2 + (2 1 )2 + (2 1 )2

Zadatak: Napisati program koji e omoguiti unos prirodnog broja n koji predstavlja broj
toaka, a nakon njega unos n toaka u polje struktura tocke. Broj n nije ogranien (osim
veliinom tipa podatka int i dostupnom memorijom raunala). Dinamiki alocirajte memoriju
za upis toaka. Pretpostavite kako upisane toke predstavljaju toke terena u 3d prostoru.
Pronai i na ekran ispisati udaljenost dva najvia vrha tog terena.

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <math.h>
4
5 typedef struct tocka {
6 float x;
7 float y;
8 float z;
9 } Tocka;
10
11 void inputData(Tocka *tocke, int n) {
12 int i;
13 for (i = 0; i < n; i++) {
14 printf("Upisi x, y i z za %d tocku: ", i+1);
15 scanf("%f%f%f", &tocke[i].x, &tocke[i].y, &tocke[i].z);
16 }
17 }
18
19 void findTopTwo(Tocka *p, int n, Tocka *max[]) {
20 int i;
21 max[0] = &p[0];
22 max[1] = &p[1];
23 for (i = 2; i < n; i++) {
24 if(max[0]->z < p[i].z || max[1]->z < p[i].z) {
25 if(max[0]->z < max[1]->z)
26 max[0] = &p[i];
27 else
28 max[1] = &p[i];
29 }
30 }
31 }
32
33 float len3d(Tocka *p1, Tocka *p2) {
34 return sqrt( pow(p2->x - p1->x, 2) + pow(p2->y - p1->y,2) + pow(p2->z - p1->z,
2));
35 }
36
37 void printTocka(Tocka *p) {
38 printf("%.2f, %.2f, %.2f \n", p->x, p->y, p->z);
39 }
40
41 int main(void) {
42 Tocka *tocke;
43 Tocka *max[2];
44 int n;
45 float D;
46
47 printf("Upisi broj tocaka: ");
48 scanf("%d", &n);
49 tocke = (Tocka *) malloc ( n * sizeof(Tocka));
50 inputData(tocke, n);
51
52 for (int i = 0; i < n; i++) {
53 printf("%.2f %.2f %.2f\n", tocke[i].x, tocke[i].y, tocke[i].z);
54 }
55
56 findTopTwo(tocke, n, max);
57
58 D = len3d(max[0], max[1]);
59
60 printf("Udaljenost najvisih vrhova je %.2f\n", D);
61 printf("a njihove koordinate su:\n");
62 printTocka(max[0]);
63 printTocka(max[1]);
64 return 0;
65 }

Kratko obrazloenje [Primjer 2]: U ovom zadatku definirano je nekoliko osnovnih funkcija
koje sadre funkcionalnost programa.

Funkcija inputData prima kao argument pokaziva na dinamiki alocirano polje struktura
tipa Tocka i broj toaka koje treba upisati, a zadatak koji obavlja je upis koordinata toaka u
predano polje struktura tipa Tocka.

Funkcija findTopTwo prima kao argumente polje struktura tipa Tocka, broj elemenata u
polju, te pokaziva na polje pokazivaa tipa Tocka. Funkcija ne vraa nikakvu vrijednost.
Zadatak funkcije je pronai u predanom polju toaka dvije toke koje imaju najviu
vrijednost Z koordinate, te njihove adrese spremiti u polje pokazivaa na strukturu Tocka,
koje je predano kao trei parametar. Ovakav pristup koristimo jer nije mogue iz funkcije
vratiti vie od jednog podatka, a u naem sluaju elimo vratiti dvije adrese koje
predstavljaju lokacije dvije najvie toke. Kako ne moemo odjednom vratiti dvije adrese,
koristimo pristup u kojem emo predati funkciji polje pokazivaa, te emo direktno u njega
spremiti adrese najviih toaka. Mogue je primijetiti kako bi se ovakav pristup mogao lako
generalizirati tako to e traiti proizvoljan broj najviih toaka (za razliku od dvije u
trenutnom sluaju), gdje bi se taj broj toaka koji se trai trebao predati kao etvrti
argument funkciji.

Funkcija len3d prima adrese dvije toke u memoriji, a vraa vrijednost koja predstavlja
udaljenost te dvije toke.

Funkcija printTocka prima adresu toke koju treba ispisati, a ne vraa nikakvu vrijednost
ve ispisuje koordinate toke na ekran.

Unutar main funkcije se uitava podatak koliko toaka treba upisati, te se alocira memorija
za toliko toaka. Za ostale funkcionalnosti samo se pozivaju gore navedene funkcije.
Primjer 3. Zamislimo kako elimo napisati program koji e pronai koji od trokuta ima
najveu udaljenost od ishodita koordinatnog sustava. Znamo kako je udaljenost u
trodimenzionalnom prostoru izmeu dvije toke definirana slijedeom formulom:

= (2 1 )2 + (2 1 )2 + (2 1 )2

U ovom sluaju ishodite koordinatnog sustava ima koordinate (0, 0, 0) te formula za


udaljenost toke od ishodita glasi:

= 2 + 2 + 2

Zadatak: Napisati program koji e omoguiti uitavanje N toaka u polje struktura tipa tocka i
N trokuta u polje struktura tipa trokut. Program treba pronai trokut koji je najudaljeniji od
ishodita. Za mjeru udaljenosti trokuta uzeti prosjenu udaljenost njegovih toaka:

(1 +2 +3 )
= 3
.

1 #include <stdio.h>
2 #include <math.h>
3 #include <stdlib.h>
4
5 typedef struct tocka {
6 float x;
7 float y;
8 float z;
9 } Tocka;
10 typedef struct trokut {
11 struct tocka *t1;
12 struct tocka *t2;
13 struct tocka *t3;
14 } Trokut;
15
16 void inputData(Tocka *p, Trokut *t, int n, int m) {
17 int i;
18 int ind1, ind2, ind3;
19
20 for (i = 0; i < n; i++) {
21 printf("Upisi x, y i z za %d tocku: ", i);
22 scanf("%f%f%f", &p[i].x, &p[i].y, &p[i].z);
23 }
24 for (i = 0; i < m; i++) {
25 printf("Upisi indexe tocaka za %d trokut: ", i);
26 scanf("%d%d%d", &ind1, &ind2, &ind3);
27 t[i].t1 = &p[ind1];
28 t[i].t2 = &p[ind2];
29 t[i].t3 = &p[ind3];
30 }
31 }
32
33 float len3d(Tocka p) {
34 return sqrt( p.x*p.x + p.y*p.y + p.z*p.z);
35 }
36
37 float distTrokut(Trokut t) {
38 float D = 0.0;
39 D = len3d(*t.t1) + len3d(*t.t2) + len3d(*t.t3);
40 D /= 3;
41 return D;
42 }
43
44 int main(void)
45 {
46 Tocka *tocke;
47 Trokut *trokuti;
48 Trokut *max;
49 int i, n, m;
50 float Dtrenutni=0, Dmax=0;
51
52 printf("Upisi broj tocaka: ");
53 scanf("%d", &n);
54 tocke = (Tocka *) malloc ( n * sizeof(Tocka));
55
56
57 printf("Upisi broj trokuta: ");
58 scanf("%d", &m);
59 trokuti = (Trokut *) malloc ( m * sizeof(Trokut));
60
61
62 inputData(tocke, trokuti, n, m);
63
64 for (i = 0; i < m; i++) {
65 Dtrenutni = distTrokut(trokuti[i]);
66 if (Dtrenutni > Dmax)
67 {
68 Dmax = Dtrenutni;
69 max = &trokuti[i];
70 }
71 }
72
73 printf("Najudaljeniji trokut je udaljen %.2f od ishodita,\n", Dmax);
74 printf("a njegove koordinate su:\n");
75 printf("(%.2f, %.2f, %.2f)\n", max->t1->x, max->t1->y, max->t1->z);
76 printf("(%.2f, %.2f, %.2f)\n", max->t2->x, max->t2->y, max->t2->z);
77 printf("(%.2f, %.2f, %.2f)\n", max->t3->x, max->t3->y, max->t3->z);
78
79 return 0;
80 }

Kratko obrazloenje [Primjer 3]: Program prvo nudi unos n toaka, a zatim unos N trokuta
definiranih tim tokama. Ovaj unos se obavlja pozivom funkcije inputData, kojoj se
predaju polja gdje e se spremati toke i trokuti, te broj n koji govori koliko toaka i trokuta
treba uitati.

U funkciji distTrokut rauna se udaljenost predanog trokuta od ishodita. Argument koji


prima ova funkcija je kopija podataka predanog trokuta, a ne pokaziva na konkretan
trokut. Ovakva implementacija je jednostavnija i nema nedostataka u sluajevima kada se
radi o malim strukturama (koje zauzimaju malo memorije pa e i njihova kopija biti mala) te
kada je povratni tip funkcije nekakva brojana vrijednost ( int ili float). U ovom sluaju
funkcija vraa float vrijednost, pa zbog toga predavanje konkretne vrijednosti strukture
umjesto pokazivaa nee stvarati problem. Povratni podatak je prosjena udaljenost svake
od toaka trokuta, koja se rauna pozivanjem funkcije len3d kako bi se dobila udaljenost
pojedine toke. Potrebno je obratiti pozornost na nain pozivanja funkcije len3d iz funkcije
distTrokut: budui da funkcija len3d prima kao parametar strukturu, a ne pokaziva na
strukturu, pri pozivu funkcije iz distTrokut funkcije potrebno je dereferencirati
pokazivae na toku unutar trokuta, jer je u svakom trokutu spremljena adresa toke, a ne
podaci o toki.

U funkciji len3d rauna se udaljenost pojedine toke od ishodita. Funkcija prima kao
parametar strukturu, a ne pokaziva na strukturu. Kao i u gornjem primjeru, budui da je
povratna vrijednost float, ovakav ulazni argument u funkciju nema nekih nedostataka.

U funkciji main upisujemo broj toaka i trokuta, te alociramo potrebnu memoriju za


podatke, koje upisujemo u polja pozivom funkcije inputData. Nakon toga pretraujemo
trokute i pronalazimo najudaljeniji, te nakon toga jednostavno ispisujemo koordinate
toaka trokuta koji odgovara kriterijima.

Napomena: Primijetite kako su se funkcije koje odrauju funkcionalnost unosa podataka,


izrauna udaljenosti ili ispisa mijenjale jako malo (u sluaju inputData), ili se nisu uope
mijenjale u odnosu na vjebu 6, iako se ovdje koristi dinamika alokacija memorije za polja.
Razlog tome je jer polje u funkciju ionako predajemo kao adresu nultog elementa, a zbog
toga moramo predati i broj koliko u polju ima elemenata, jer funkcija ne moe znati kako je
to polje definirano u main funkciji. Upravo zbog toga nae funkcije rade nepromijenjene,
bez obzira imali mi 6 elemenata ili 600.000 elemenata, dok nam dinamika alokacija
omoguuje da se zauzme memorije upravo onoliko koliko je potrebno.
4 ZADACI

Pri rjeavanju zadataka potrebno je ispisati rezultate u tono odreenom formatu. Budui
da sustav automatski provjerava ispravnost rjeenja, sustav oekuje tekst REZULTATI: u
jednom redu, a nakon njega tono formatiran ispis rezultata na nain koji e biti naveden u
tekstu zadatka. Sav tekst koji se ispie prije teksta REZULTATI: sustav e ignorirati, kao i sav
tekst koji se ispie nakon oekivanog izlaza.

Zadatke je potrebno predati preko aktivnosti na Loomenu u koju e Vas uputiti nastavnici
na vjebama.

1. Napiite C program koji uitava toke i trokute iz datoteke model.txt. Program iz


datoteke uitava prvo broj toaka n, zatim broj trokuta m, a zatim toke i trokute.
Potrebno je dinamiki alocirati memoriju za unos toaka i trokuta. Pronai i ispisati
opseg trokuta koji ima najvei opseg u formatu %.2f.
2. Napiite C program koji e iz datoteke racun.txt uitati n artikala u raun. Datoteka
racun.txt je ve zadana, a sadri ime kupca, ime prodavaa, broj artikala u raunu i
podatke o svakom od artikala. Program treba ispisati ukupan iznos rauna na ekran
u obliku: Ukupan iznos racuna: %.2f. Postavljen je oblik struktura, primjetite
da je unutar struktura svu memoriju za polja (bilo znakova bilo struktura) potrebno
alocirati dinamiki. Kako je struktura zadana, na osnovu tipova podataka lanova
strukture i oblika podataka u ulaznoj datoteci moete zakljuiti kako trebate upisati
ulazne podatke.

You might also like