8

NAJAVE

The Prophet
Mage Wars
NEX - Dark Age
Scavangers
Seaside

NOVOSTI

Blue Black Solution
Superfrog
The Megagrip II
Epic Marketing
Crystal Software
Dafel Bloodline
Update: Napalm
The World Foundry
phase5
Settlers II
Delsyd Software
Update: Amiga
JoyRide

OPISI

Napalm
Amiga AMP v2.1
Starbirds
The Strangers
Tiny Troops
IOBlix
VGP Demo

INTERVIEW

Lambda Team
Delsyd Software

ZZNIC NEWS

303tracker

DEMO SCOPE

Wildlife
1000%

AS SPECIAL

Pisma čitatelja
Mala škola C-a
www.xrage.com
Amiga99 Show
Mama, I wanna be a coder!
Otvorena pisma (??)
Nagradna igra
DOSTA !!!!

AS HELP

Dungeon Master


Mala škola C-a (4. nastavak)



Pointeri, strukture i još poneke sitnice

U 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
|   logičko OR
    .  .  .

,a oni su aritmetički.

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).
Logičke operacije kao rezultat daju TRUE ili FALSE, što u C-u odgovara vrijdnostima 1 i 0, dok aritmetičke daju stvarni računski rezultat. To znači da je i tablica bila pogrešna i da je trebala izgledati ovako:

= dodijeljivanje
+ zbrajanje
- oduzimanje
* množenje
/ dijeljenje
% ostatak pri dijeljenju
++ inkrementacija
-- dekrementacija
& aritmetičko AND
| aritmetičko OR
~ aritmetičko NOT
^ aritmetičko ekskluzivno OR (EOR)
<< pomijeranje u lijevo
>> pomijeranje u desno


A tablica istinitosti treba da izgleda ovako:

    0101  0101    0101
&1100 |1100 ~1100 ^1100
---- ---- ---- ----
    0100  1101 0011    1001



Toliko što se ispravki tiče, a sada pozabavimo se pointerima. Pointer je varijabla koja sadrži adresu varijable. Čemu ta "komplikacija"?
Pa, kao prvo, kada se funkciji prosljeđuju parametri, funkcija ustvari dobija privremene kopije originalnih varijabli sa mjesta odakle je pozvana. Znači, ako bi mijenjali te "kopije" varijabli u našoj funkciji, originali bi ostajali isti. Dakle, da bi funkcija mogla da mijenja originalne varijable iz funkcije koja je poziva, mora se izvršiti proslijeđivanje adresa tih varijabli. Tada će naša funkcija mijenjati sadržaje memorijskih lokacija čije su adrese date (i na kojima se čuvaju originali).

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.
Ovako, sa pointerima dovoljno je samo obavijestiti pozvanu funkciju gdje se u memoriji nalazi određena matrica (proslijediti njenu adresu, pointer na nju) i stvar je rješena. Čak i da matrica zauzima vise stotina KB dovoljno je proslijediti samo jedan broj!

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.
int *p,*b;    // p i b su pointeri na varijable tipa int.

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

#include <stdio.h>
void swap(int *a,int *b);
main()
{
        int a=10,b=5;
        swap(&a,&b);
        printf("Sada je: a=%d, b=%d\n",a,b);
}
void swap (int *a,int *b)
{
        int t;
        t=*a;
        *a=*b;
        *b=t;
}

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];
int *p;

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

#include <stdio.h>
void sort(int *niz); // Najava funkcije "sort" koja prima pointer na int
main()
{
        int a[10]={4,5,7,2,8,1,3,9,15,3}; // Pravi se niz i puni se s brojevima
        int i; // varijabla za potrebe for petlje
        printf("Niz je na početku izgledao ovako:\n");
        for (i=0;i<10;i++) // Ispis početnog stanja niza
        {
            printf ("%d ",a[i]);
        }
        sort(a); // Poziv funkcije "sort". Moglo je i: sort(&a[0]);
        printf("\nA sada izgleda ovako:\n");
        for (i=0;i<10;i++) // Potpuno isti ispis novonastalog stanja
        {
            printf ("%d ",a[i]);
        }
        printf ("\n");
}
void sort(int *niz)
{
        int i,j,tmp; // "i" i "j" su indeksne varijable, a "tmp" služi za privremeni
        for (i=0;i<9;i++) // smještaj vrijednosti prilikom zamjene sadržaja
        {
            for(j=i+1;j<10;j++) // Uspoređuje se svaki sa svakim elementom
            {
                if(niz[i]>niz[j]) // Ako je veci ispred manjeg
                {
                    tmp=niz[i]; // zamjenjuju mjesta
                    niz[i]=niz[j];
                    niz[j]=tmp;
                }
            }
        }
}

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!
Struktura je skup od jedne ili više varijabli, koje mogu biti različitih tipova, grupiranih zajedno pod jednim imenom radi lakseg rukovanja. Ovo kaze teorija, a kako to izgleda u praksi?

struct radnik
{
        char ime[10];
        char prezime[20];
        int rođen;
        char adresa[30];
        float plaća;
};

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.
Elementima strukture se jednostavno pristupa:

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';
prvi.ime[1]='e';
prvi.ime[2]='r';
prvi.ime[3]='o';
prvi.ime[4]='\0';

ili da koristimo neku funkciju za kopiranje stringa (strcpy), već prema snalažljivosti. Da vidimo kako bi izgledao cijeli program:

Source: primjer13.c

#include <stdio.h>
struct radnik
{
        char ime[15];
        char prezime[20];
        int rođen;
        char adresa[30];
        float plaća;
};

main()
{
        struct radnik prvi={"Pero","Perić",1965,"Ujedinjenih naroda 99",1046.376};

        printf("Radnik:%s %s\n", prvi.ime, prvi.prezime);
        printf("Rođen:%d godine\n", prvi.rođen);
        printf("Adresa:%s\n", prvi.adresa);
        printf("S prosječnom plaćom %f DM\n", prvi.plaća);

}

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
{
        char ime[15];
        char prezime[20];
        int rođen;
        char adresa[30];
        float plaća;
} prvi={"Pero","Perić",1965,"Ujedinjenih naroda 99",1046.376};

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]=
{
        {"Pero","Perić",1965,"Ujedinjenih naroda 99",1046.376},
        {"Milan","Golubović",1974,"Zrtava fasizma 43",966.599},

                                .         .          .

        {"Davor","Rivić",1981,"Franca Prešerna 28a",1576.203}
}

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

#include <stdio.h>
struct data
{
        char ime[10];
        int starost;
};

int najmlađi(struct data *pod,int broj);

main()
{
        int adolescent;
        static struct data podaci[]=
        {
            {"Pero",34},
            {"Mika",24},
            {"Đoko",34},
            {"Franjo",33},
            {"Sanjin",12}
        };

        adolescent=najmlađi (podaci,sizeof(podaci)/sizeof(struct data));

        printf("Najmlađa osoba:%s\n",podaci[adolescent].ime);
        printf("I ima samo %d godina.\n",podaci[adolescent].starost);

}

int najmlađi(struct data *pod, int broj)
{
        int min;
        broj--;
        for (min=broj;broj>=0;broj--)
        {
            if(pod[broj].starost < pod[min].starost)
              min=broj;
        }
        return min;
}

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.
Ako imamo pointer na strukturu članovima te strukture bi pristupali:

(*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.).
Za to cemo koristiti funkciju "malloc"(memory alloc), a za oslobađanje iste memorije funkciju "free". Sva alocirana memorija se po završetku programa mora vratiti sistemu. Evo programa koji će alocirati memoriju za niz od 50 int-a (200 bajta), ispisati sadržaj te memorije i vratiti memoriju sistemu.

Source: primjer15.c

#include <stdio.h>
#include <stdlib.h>
main()
{
        int *p;
        int i;
        if(p=malloc(50*sizeof(int)))
        {
            for (i=0;i<50;i++)
            printf ("%d,",p[i]);
            printf("\n");
            free(p);
        }
}

"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.
Ako nema dovoljno memorije i naš zahtjev za alociranjem ne može biti ispunjen "malloc" će vratiti nulu (0, NULL).
Zato se i poziv nalazi unutar if naredbe, koja obezbjeđuje da se ostatak programa izvrši samo ako je alociranje uspješno izvršeno. Nastala bi greška ako bi sa "free" pokušali da oslobodimo memoriju koja nije alocirana.
Ispisujemo sadrzaj te memorije (kao da se radi int o nizu) i oslobađamo memoriju.

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

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

struct list {
        char *s;
        struct list *next;
        };
        struct list * dodaj(struct list *,char *);
        void pisi(struct list *);
        void brisi(struct list *);

main()
{
        struct list *prva=NULL;
        struct list *zadnja=NULL;
        prva=zadnja=dodaj (prva,"Prva struktura liste.");

        zadnja=dodaj(zadnja,"Druga struktura liste.");
        zadnja=dodaj(zadnja,"Treća struktura liste.");

        pisi(prva);
        brisi(prva);

}

struct list * dodaj(struct list *list,char *string)
{
        struct list *tmp;
        tmp=malloc(sizeof(struct list));
        tmp->s=malloc(strlen(string)+1);
        strcpy(tmp->s,string);
        tmp->next=NULL;

        if (list)
        {
            list->next=tmp;
        }
        return tmp;

}
void brisi(struct list *list)
{
        struct list *tmp;

        while(list)
        {
            tmp=list->next;
            free(list->s);
            free(list);
            list=tmp;
        }
}

void pisi(struct list *list)
{
        struct list *tmp;

        while(list)
        {
            tmp=list->next;
            printf("%s\n",list->s);
            list=tmp;
        }
}

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>
#include <stdlib.h>        // zbog malloc i free
#include <string.h>        // zbog strlen i strcpy

struct list {
        char *s;        // struktura sadrži samo pointer na string i pointer
        struct list *next;        // na slijedeću strukturu
};

struct list * dodaj(struct list *,char *);

/*
Funkciji "dodaj" se proslijeđuje pointer na strukturu na koju se treba nadovezati nova struktura i string (pointer na char niz) za tu novu strukturu. Funkcija vraća pointer na novopridodatu strukturu listi.
*/

void pisi(struct list *);        // Pokazuje kako se pristupa listi ispisujući sve članove 
                                                // liste (tekstove)
void brisi(struct list *);        // Memorija rezervirana za listu mora se osloboditi
main()
{
        struct list *prva=NULL;        // pointer na prvu strukturu u listi
        struct list *zadnja=NULL;        // pointer na posljednju dodatu strukturu
        prva=zadnja=dodaj (prva,"Prva struktura liste.");
/* Dodaje se prva struktura liste, koja je na početku ujedno i zadnja */

        zadnja=dodaj(zadnja,"Druga struktura liste.");        // Još jedna
        zadnja=dodaj(zadnja,"Treća struktura liste.");        // Pa još jedna

        pisi(prva);        // Ispis
        brisi(prva);        // Oslobađanje memorije

}

struct list * dodaj(struct list *list,char *string)
{
        struct list *tmp;        // Privremeni pointer na strukturu

        tmp=malloc(sizeof(struct list));

/*
Alocira se memorija za novi član liste (novu strukturu) i pointer na nju se smješta u tmp. Zbog pojednostavljenja ne provjerava se da li ima dovoljno memorije - da li je poziv malloc-a uspješan . Inače tu provjeru je neophodno izvršiti!!!!
*/

        tmp->s=malloc(strlen(string)+1);


/*
Rezervira se memorija za string (pošto struktura sadrži samo pointer na string, a ne i sam string) i on se smješta gdje treba: tmp->s
*/

        strcpy(tmp->s,string);

/*
Kopira se proslijeđeni string u novorezerviranu memoriju.
*/

        tmp->next=NULL;

/*
Pošto je ovo najnoviji element liste sljedećeg nema
*/

        if (list)
        {
            list->next=tmp;
        }

/*
Ako upravo dodati član liste nije prvi u listi on se nadovezuje na predhodni, u suprotnom - ništa. Element je prvi ako je kao pointer na početak liste funkciji proslijeđena nula (NULL)
*/

        return tmp;        // Vraća se pointer na novi član liste!

}
void brisi(struct list *list)
{
        struct list *tmp;

        while(list)        // Vrti dok nije NULL - do zadnjeg člana liste
        {
            tmp=list->next;        // Zapamti adresu slijedećeg člana liste
            free(list->s);        // Oslobodi memoriju trenutne strukture
            free(list);        // i memoriju za string
            list=tmp;        // slijedeća postaje trenutna, pa 'ajde ponovo
        }
}

void pisi(struct list *list)
{
        struct list *tmp;

        while(list)
        {
            tmp=list->next;
            printf("%s\n",list->s);

/*
Potpuno isto kao gore, samo što se ne oslobađa mem. nego se ispisuje član strukture
*/

            list=tmp;
        }
}


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.