|
Mage Wars NEX - Dark Age Scavangers Seaside The Megagrip II Epic Marketing Crystal Software Dafel Bloodline Update: Napalm The World Foundry phase5 Settlers II Delsyd Software Update: Amiga JoyRide Amiga AMP v2.1 Starbirds The Strangers Tiny Troops IOBlix VGP Demo Delsyd Software 1000% Mala škola C-a www.xrage.com Amiga99 Show Mama, I wanna be a coder! Otvorena pisma (??) Nagradna igra DOSTA !!!! |
Pointeri, strukture i još poneke sitniceU prošlom broju su se potkrale neke "sitne" greške, ali zahvaljujući pažljivom čitatelju Mladenu Milinkoviću one su uočene i sada ih ispravljam. Dakle, radi se o operatorima AND, OR, NOT i EOR. U tablici s aritmetičkim operatorima pogrešno je napisano tj. da su logički:
& logičko AND
Dakle, navedene operacije mogu biti LOGIČKE i označavaju se sa "&&", "||" i
"!" (AND, OR i NOT respektivno) i ARITMETIČKE koje se označavaju sa "&",
"|", "~" (tilda) i "^" (EOR).
A tablica istinitosti treba da izgleda ovako:
Toliko što se ispravki tiče, a sada pozabavimo se pointerima.
Pointer je
varijabla koja sadrži adresu varijable. Čemu ta "komplikacija"?
Kao drugo, čak i kada bi pozvane funkcije mogle da mijenjaju vrijednosti
pozivajućih funkcija postavljalo bi se pitanje brzine dobijenih programa, jer
zamislite da je funkciji potrebno proslijediti veću matricu. Kao što smi vidjeli važznost pointera je ogromna. Ne može se napisati nijedan iole ozbiljniji program bez njihovog korištenja. Nažalost, rad s pointerima nije baš jednostavan i dosta početnika kada se prvi put sretne s njima se razočara i odustane od učenja C-a. Kao što rekosmo, pointer je varijabla koja sadrži adresu neke druge varijable i to točno određenog tipa. Da vidimo kako bi to izgledalo u praksi:
int a; // najobičnija varijabla int tipa. Kod definiranja se pointeri označavaju zvjezdicom ispred imena."p" je pointer na varijablu int tipa. Da bi zaista sadržao adresu neke varijable ("a", koja mu odgovara po tipu) trebamo dodati: p=&a; Ako bi sada zeleli da i pointer "b" sadrži adresu varijable "a" možemo slično napisati: b=&a; ali možemo i b=p; ,što je potpuno isto. Sadržaj pointera p (adresa varijable "a") se jednostavno prekopira u pointer b. Sada je p "napunjen" adresom (unarni operator "&" označava da se radi o adresi varijable, a ne o sadržaju iste). "&" ima smisla koristiti samo s varijablama (i funkcijama), a ne s izrazima, pošto rezultati izraza nemaju stalno mjesto u memoriji. Sada, pošto pointer "p" pokazuje na varijablu "a" za sve operacije koje bi radili sa varijablom postoji ekvivalent sa pointerom: *p=*p+1; je isto što i a=a+1; ali a++; nije isto što i *p++; zbog toga što "++" ima veci prioritet od "*", pa bi se to tumačilo kao: *(p++); ,a ne kao: (*p)++; pa zato vodite računa i stavljajte zagrade kad god niste sigurni. Znači princip vrijedi, ali treba obratiti pažnju na prioritete operatora. Evo sada primjera kako napisati funkciju koja ce mijenjati sadržaje svojih parametara:
Source: primjer11.c
Red: void swap(int *a,int *b); prijavljuje funkciju swap, čiji su parametri dva pointera na int varijable. Zatim se definiraju varijable a i b, dodijeljuju im se početne vrijednosti i poziva se funkcija swap. Njoj se proslijeđuju adrese na kojima se čuvaju a i b varijable, a ne vrijednosti tih varijablih. Ako se vrijednost varijable "a" nalazi u memorijskoj lokaciji 1000, a varijable "b" na lokaciji 1004 (ako INT tip zauzima 4 bajta) onda će se funkciji swap proslijediti baš ti brojevi: 1000 i 1004. U funkciji swap sada imamo adrese varijabli, ali koje su one - nama nije ni bitno. Mi samo koristimo sadržaje sa tih adresa, pa red: t=*a; smiješta u privremenu varijablu "t" sadrzaj memorijske lokacije koji se sadrži u pointeru "a" (1000), a red:*a=*b; stavlja sadržaj sa adrese varijable. a (iz glavnog programa) na adresu varijable b (također iz glavnog programa) i poslije ove operacije "b" varijabla dobija istu vrijednost kao i varijabla "a". Imena varijabli iz glavnog programa (main-a) i iz funkcije swap naravno ne moraju biti ista. Da vidimo sada kako stoje stvari s nizovima. Ako imamo niz i pointer:
int niz[10]; možemo pointer "p" usmjeriti na bilo koji element niza: p=&niz[7]; i "p" će sadržavati adresu sedmog elementa niza. No i samo ime niza je sinonim za adresu (prvog člana) niza, pa je tako: p=&niz[0]; isto što i: p=niz; S druge strane, nekom elementu niza se umjesto na "klasičan" način: niz[3] može pristupiti i ovako: p[3]; ili: *(p+3); Pri izračunavanju izraza, "p" je pokazivao prvo na nulti element niza, pa je onda povećan za 3 i pokazivao na treći element i tom trecem se pristupa. A eto i objašnjenja zašto pointer mora imati tip - da bi kompajler znao za koliko zaista da uveća "p". Pošto je "p" pointer na int tip, koji u memoriji zauzima 4 bajta i ako recimo "p" sadrzi 1000, p+3 ce sadrzati 1000+3*4=1012. No, o ovome mi ne moramo brinuti i uvijek možemo biti sigurni da *(p+3) se odnosi na treci element niza, bio on Byte, Word, Long, struktura... Da napomenem još jednu stvar u vezi stringova (i nizova uopće). String možemo definirati ovako: char poruka1[]="Vrijeme je novac"; ali i ovako: char *poruka2="Vreme je novac"; Ima li razlike? U prvom slučaju definiramo niz tipa "char": kompajler broji koliko ima znakova pod navodnicima i negdje u memoriji rezervira toliko bajtova, plus jedan za označavanje kraja niza ( "\0"). U drugom primjeru se isto alocira memorija, isto smještaju bajtovi poruke, ali se stvara pointer koji pokazuje na prvo slovo poruke. Taj pointer kasnije se može iskoristiti da pokazuje na neku drugu poruku, ali poruka1 je uvek isti niz. Sadržaj tog niza se može mijenjati, ali on će se uvijek nalaziti na istom mjestu u memoriji. Kako bi još bolje shvatili pointere evo još jednog programa:
Source: primjer12.c
Program se sastoji iz glavnog dijela koji stvara niz, puni ga slučajnim brojevima (onim koje navedemo), ispisuje niz, poziva "sort" funkciju i ponovo ispisuje sve elemente niza. Funkcija se poziva tako što se proslijeđuje adresa niza. Kao što je navedeno samo ime niza bez zagrada daje adresu (prvog clana) niza. Sama funkcija koristi najjednostavniji "bubble" sort. Jednostavno se poredi svaki element sa svakim i kad god je veći ispred manjeg (ispred su oni s manjim indeksom) ta dva elementa jednostavno zamjene mjesta. To se izvodi ugnježđenim petljama. Čak i da vam nije sasvim jasno na koji način sort radi obratite pažnju na to kako se pristupa članovima niza unutar funkcije. Svugdje gdje se koristilo "niz[i]" i "niz[j]" moglo se staviti "*(niz+i)" i "*(niz+j)" respektivno, što bi dalo iste rezultate, ali ovako je preglednije. A sada nešto o strukturama. Često pišući programe nailazimo na grupe podataka različitog tipa koje karakteriraju jednu stvar. Recimo da pravimo bazu o radnicima nekog poduzeća. Baza o svakom radniku sadrži ime, prezime, godinu rođenja, adresu i prosječnu plaću. Neki od podataka su cjelobrojni (int), neki realni (float), a neki tekstualni (char).
Međutim, zgodno je imati ih dostupne sve odjednom o jednom radniku,
proslijeđivati ih funkcijama zajedno itd. Na scenu stupaju strukture!
struct radnik Deklariramo tip strukture ključnom riječi "struct" koji će se zvati "radnik". Dakle, tip strukture će se zvati radnik, a ne sama struktura!!! Taj tip sadrži: niz od 10 karaktera (char) pod imenom "ime", niz od 20 karaktera pod imenom "prezime", jedan int podatak pod imenom "rođen", još jedan niz karaktera i jedan broj tipa float (plaća). Novi tip strukture (radnik) koji smo sada definirali sadrži sve ove podatke. Međutim, do sada je stvoren samo tip, a ne postoji ni jedna struktura tog tipa, jos uvijek ni bajt memorije nije rezerviran. Tip strukture se na neki način može uporediti sa tipovima int, float, itd, samo što je ovaj kompleksniji, jer se sastoji od više tipova. Analogno tome, recimo, tip int postoji, ali dok se ne najavi neka varijabla tog tipa ne rezervira se memorija. Kada želimo da stvorimo neku strukturu tipa "radnik" dodati ćemo: struct radnik prvi; Tek poslije ovog reda stvorena je struktura s imenom "prvi", koja je tipa radnik. Kada je tip definiran možemo stvarati strukture tog tipa koliko želimo, mozemo praviti nizove struktura tog tipa, čak možemo praviti i složenije strukture koje kao jedan od članova sadrže i našu strukturu. Sve što nije zabranjeno, dozvoljeno je!
Za sada je stvoren tip (radnik), prijavljena je struktura tog tipa (prvi),
ali elementi strukture jos uvijek nisu dobili svoje prave vrijednosti. prvi.rođen=1965; Ime strukture (ne tipa!), točka pa ime člana, vrlo jednostavno. Plaću bi popunili isto tako: prvi.plata=1046.376; Međutim, kako ovo nije BASIC ne bi mogli napisati nešto kao: prvi.ime="Pero"; Ovi bi značilo da zahtijevamo da se odjednom prenese 5 karaktera (ime + '\0'), sto je moguce u BASIC-u. U C-u bi morali da popunimo slovo po slovo:
prvi.ime[0]='P'; ili da koristimo neku funkciju za kopiranje stringa (strcpy), već prema snalažljivosti. Da vidimo kako bi izgledao cijeli program:
Source: primjer13.c
Deklariramo novi tip strukture - "radnik". Zatim se definira struktura "prvi" tipa "struct radnik". U istom redu se novonastala struktura ("prvi") inicijalizira - dodijeljuju se početne vrijednosti njenim članovima. Zatim se u više redovaa ispisuju članovi strukture. Tu vidimo kako se tim članovima pristupa. Deklaracija i definicija strukture se mogu spojiti:
struct radnik i tako uštedjeti nešto prostora, ali značenje je isto. Ako imamo tvrtku od 100 radnika potrebno je napraviti niz struktura. Recimo da je tip "radnik" deklariran na isti način niz struktura bi napravili ovako: struct radnik čovjek[100]; Sada ćemo pojedinim članovima pristupati: a=čovjek[55].rođen; U "a" smještamo godinu rođenja 55. radnika (odnosno 56. jer postoji i nulti!!). Ako bi htjeli da prilikom definiranja i inicijaliziramo niz struktura morali bi (satima) da pišemo:
struct radnik čovjek[100]= Zgodno kod nizova struktura je to što strukture, kao i sami podaci jedne strukture grupiraju u memoriji jedni iza drugih po istom redoslijedu po kom su deklarirani, pa je moguće vidjeti adresu neke strukture u nizu (množenjem dužine i broja strukture) i tu jednostavno učitati podatke iz nekog fajla. Pored toga što nije zgodno brojati koliko bajtova zauzima struktura nije preporučljivo zbog još jednog razloga: tip int npr. može zauzimati 4 bajta ili 2 bajta, u memoriji (kod Amige) mora počinjati na parnoj adresi, pa će zato kompajler ako se pogodi da je isti na neparnoj ubaciti jedan "prazan" bajt ispred njega , koga mi nebi izbrojali i doslo bi do greške. Zato treba koristiti: sizeof (struktura); ili sizeof (struct tip_strukture) "sizeof" nije funkcija, već unarni operator koji kompajler u vrijeme kompajliranja zamijenjuje veličinom odgovarajuće strukture. Evo instant primjera koji iz niza struktura ispisuje onu sa najmanjim članom (starost):
Source: primjer14.c
Na početku se deklarira nova struktura "data" koji ima samo dva člana: niz "ime" od 10 char-a i int "starost". Deklaracija se morala izvršiti prijee najavljivanja funkcije "najmlađi", jer je ono (najavljivanje) koristi - logično. Zatim se u main-u definira niz "podaci" tipa "struct data" (niz struktura) i inicijalizira se. Pitanje za naprednije: zašto je ubačena ključna riječ static? Ako se ona ne stavi onda se za lokalne varijable koristi stek što samo po sebi i nije loše, ali se pored alokacije steka moraju inicijalizirati članovi strukture - odnosno prekopirati s neke fiksne lokacije na stek. Sa static ispred niz "podaci" postaje statičan, pa je za njegovu inicijalizaciju dovoljno samo ubaciti blok memorije sa inicijaliziranim podacima. Na ovaj način se postiže veća brzina i smanjenje dužine programa. U ovom primjeru ušteda i nije velika, ali da ima vise podataka bila bi. Ako vam baš nije najjasnije o čemu se radi jednostavno preskočite ovaj dio i zanemarite riječ "static", da vas ne bi bunila. Zatim se poziva funkcija "najmlađi", kojoj se proslijeđuju: pointer na početak niza struktura (sjetite se da je samo ime niza njegova adresa) i broj članova niza. Broj članova niza se izračunava tako što se dužina strukture podijeli s dužinom tipa strukture: duzina= sizeof(podaci) / sizeof(struct data); sizeof(struct data) iznosi 14: 10 char-a i jedan int (4 bajta),a sizeof(podaci) je (14*5=) 70, jer niz ima 5 članova. Dijeljenjem se dobija 70/14=5 članova. Vrlo zgodno, jer ako promijenimo dužinu niza ne moramo mijenjati granice petlji i sl. Funkcija vraća indeks najmlađeg i onda se ispisuje njegovo ime i starost. varijabla "broj" u funkciji se umanjuje za jedan zato što je prvi element 0. , a ne 1., pa će zato zadnji biti 4. , a ne 5. Dakle, petlja ide od 4 do 0, a ne 5 do 1. Indeks najmlađeg se "računa" tako što se za najmlađeg proglasi zadnji, a onda upoređuje sa svima ostalim. Ako se otkrije da je netko mlađi od onog s indeksom "min" : pod[broj].starost < pod[min].starost varijabli "min" se dodijeljuje njegov index. min=broj; Kada se svi elementi niza ispitaju funkcija vraća indeks najmlađeg. Zapazimo da je za svako ime rezervirano po 10 char-a (bajta), a da su imena kraća, što predstavlja rasipanje memorije. Da bi se to izbjeglo dovoljno je pri deklariranju strukture "data" umjesto: char ime[10]; staviti: char *ime;
Dakle, ne niz od fiksnih deset članova, već pointer na niz (4 bajta) + broj
slova po imenu. Na ovaj nacin dužina imena nije ograničena i memorija se
koristi racionalno, i 4 bajta je mala cijena za to. Ovaj način se može
koristiti samo kada se struktura popunjava konstantama (kada kompajler zna
koliko memorije da alocira) ili kada sami alociramo memoriju za to. (*pointer).član no može se koristiti i skraćeni način obilježavanja: pointer->član Strelica ("minus" i "je veće" znaci) označavaju da "pointer" nije struktura, vec pointer na nju. Ovaj način obilježavanja se vrlo često koristi i poželjno je zapamtiti ga.
Kreiranjem nizova i matrica nije uvijek najzgodniji način alociranja
memorije. Ako koristimo niz koji zauzima 10-ak KB za isto toliko će biti duži
i naš program. Osim toga, ponekad tek u toku izvršavanja programa saznajemo
koliko nam je memorije potrebno, pa je neophodno alcirati memoriju po
potrebi (npr. ucitavamo sliku i sl.).
Source: primjer15.c
"malloc" i "free" su deklarirane u "stdlib.h" (što objašnjava drugi red programa), a dio su standardne C biblioteke, pa će tako programi koje pišete biti portabilni. Pored ovih funkcija postoje i Amiga specifične funkcije (exec.lib) za istu namjenu...
Deklariramo pointer na int i pozivamo malloc. Njen parameter je dužina
memorije koju želimo da alociramo u bajtovima. Funkcija vraća pointer na niz
tražene dužine, čija se adresa smješta u p. Ne dopustite da vas zbuni to što je poziv funkcije smješten unutar if naredbe, jer se takav način pisanja često sreće. Posle poziva "p" dobija neku vrijednost može biti točna (TRUE , != 0) ili netočna (FALSE, == 0) pa od nje zavisi dalje grananje programa. I na kraju objasnimo liste. Ako mislite da je ovo do sada bilo teško, uvidjeti ćete da je spram listi mačji kašalj. Najozbiljnije, liste su vrlo teške za razumijevanje, pogotovo početnicima. Zato ćemo ovdjee obraditi najjednostavniju jednostruko povezanu listu bez ikakvih "komplikacija" (sortiranja, umetanja i sl.). Čemu zapravo služe liste? Zamislite da morate napisati program koji koristi više struktura. U toku izvršenja programa morate predvidjeti mogućnost dodavanja novih, brisanja onih koje vam više ne trebaju, a uz maksimalnu uštedu memorije. Dakle, ne dolazi u obzir niz struktura s limitiranim maksimalnim, brojem elemenata niza. Ideja je da svaka struktura pored potrebnih podataka sadrzi i pointer na slijedeću i/ili predhodnu, kako je već potrebno. Mi ćemo čuvati samo pointer na prvu strukturu u listi, a kada nam bude potrebna slijedeća struktura iz prve (predhodne) uzeti ćemo pointer na nju i tako joj pristupiti. Pointer na slijedeću strukturu zadnje strukture u listi sadržavati će NULL, što označava kraj liste. Kroz listu se znači moramo kretati sekvencijalno, odnosno po jedan element naprijed/nazad. Postoje i kompliciranije organizacije listi (dvostruko povezane iste, binarna stabla ), ali kako je ovo ipak "mala škola" C-a neću vas više plašiti time. Evo prvo samo primjera:
Source: primjer16.c
A zatim isto to, ali sa objašnjenjima, jer primjer baz objašnjenja ne znači puno, a s objašnjenjima je ogroman i tesko je pratiti tok programa.
#include <stdio.h> Za ovaj put toliko (možda i previše). Izvinjavam se što sam negdje previše detaljirao u objašnjavanju i neke stvari objašnjavao više puta, ali pointeri i strukture su u C programiranju vrlo važni i ako mislite da se time makar malo ozbiljnije bavite sve gore napisano mora vam biti jasno. No ne očekujte da se to desi iz prvog čitanja. Neke od navedenih stvari (pogotovo liste) su vrlo složene i potrebno je vrijeme da se shvate. Budite uporni, pročitajte tekst više puta, sve dok vam ne postane jasan i pišite programe. To je jedini način za učenje C-a. Ne zavaravajte se da je samo čitanje dovoljno! U slijedećem nastavku biti će riječi o pozivanju funkcija Amiga OS-a, pa živi bili pa vidjeli. |
||||||||||||||||||||||||||||||||||||||||||||||||||||