Mala škola C-a
(3. dio)


U prošlom broju su tipovi podataka u C-u samo ukratko spomenuti. Da bi se (što prije) krenulo sa programiranjem bilo je dovoljno poznavati "int" tip. No, potrebno je i ostalim tipovima posvetiti dužnu pažnju.

Najprije napomenimo da se u slučaju da "int" tip ne obezbjeđuje dovoljno veliki opseg može koristiti tip "long int". Također, postoji tip "short int", koji ima manji opseg od običnog "int"-a. U suštini "short int" zauzima 16 bita, "int" 16 ili 32 bita, a long najmanje 32 bita. Uvijek vrijedi da je "short" <= "int" <= "long". Kod SAS C-a "short" je 16-obitan, a "int" i "long" su 32-bitni. Primjer:

short int mali=5;
long int veliki;

Za rad s racionalnim (s pokretnim zarezom) koristi se tip "float" ili, kada je potrebna još veća preciznost tip "double". Točni opseg ovih tipova može se pogledati u "float.h" fajlu. Rad sa racionalnim brojevima je dosta sporiji nego rad sa cijelim brojevima. Ako imate FPU situacija se donekle popravlja, ali i dalje su cjelobrojni višestruko brži. Zato je poželjno koristiti ih kad god je to moguće. Napišimo sada neki programčić:

#include <stdio.h>
main()
{
        float a,b;

        a=123e2; // 123*10^2
        b=34.746;

        printf (" Zbroj je: %f ",a+b);
}

"%f" upozorava "printf" funkciju da se radi o parametru tipa "float". Ako želimo da preciziramo oblik u kome treba ispisati broj stavljamo: "%a.bf", gdje "a" označava ukupan broj znamenaka koje će se ispisati (računajući i decimalnu tocku!), a "b" broj decimala koje treba prikazati. Znači red

printf ("Broj %10.3f\n",12345.766443);

će ispisati:

Broj 12345.766

Kompajlirajte sada program. Može se desiti da se kompajler pobuni protiv programa. To se dešava zato što za rad sa racionalnim brojevima postoji više biblioteka. Da bi to ispravili startajte "scopts", idite u meni "code options" i podesite korištenje odgovarajuće biblioteke (Math=...). Pošto je sve proradilo kao što treba idemo dalje...

Imamo zatim tip "char". Zauzima jedan bajt. Može biti predznačen (piše se samo "char") ili nepredčnacen ("unsigned char"). Prvi ima opseg od -128 do +127, a drugi 0 do 255. Taj opseg je dovoljan za pamćenje znakova iz loaklnog karakter seta, pa se uglavnom za to i koristi, mada je na varijable tipa "char" moguće primjeniti i matematićke operacije. Dodijeliti vrijednost varijabli ovog tipa možemo na više načina:

#include <stdio.h>
main()
{
        char c,d;
        c=65;
        d='A';
        printf ("c=%c kod c=%d d= %c kod d=%d\n",c,c,d,d);
}

Dakle, najavili smo dvije varijable tipa "char", prvoj dodijelili vrijednost 65, a drugoj smo dodijelili ASCII kod slova A (koji je također 65). Međutim, ako se ovaj dio programa izvrši na nekom računalu koji ne koristi ASCII skup znakova 'A' neće biti 65. Zato ako radimo sa znakovima bolje je koristiti drugi nacin, jer je 'A' svuda 'A', a 65 nije svuda 'A'. Svrhu jednostrukog navodnika ste naravno shvatili: između jednostrukih navodnika se stavlja znak čiji nam je kod potreban. Podsjetimo se da "\n" i slične oznake predstavljaju samo jedan znak (konkretno "\n"=10).

Ako želimo da funkcija "printf" (a isto vrijedi i za "scanf") varijable tipa char tretira kao cijele brojeve u format stringu stavljamo "%d", a ako želimo da ih ispisuje kao znakove stavljamo "%c".

Varijable svih tipova mogu graditi i nizove, matrice (dvodimenzionalne i višedimenzionalne). Da vidimo kako bi to izgledalo. Recimo želimo da u nizu od 12 elemenata zapamtimo broj dana u ekvivalntnom mjesecu:

int mjeseci[12]; //niz pod imenom "mjeseci" od 12 elemenata

Sada su nam dostupni elementi niza sa indeksima od 0 da 11. Ako želimo da popunimo niz možemo napisati:

mjeseci[0]=31;
mjeseci[1]=28;
    .
    .
    .
mjeseci[11]=31;

Na isti način ćemo koristiti te elemente:

zbroj=mjeseci[0]+mjeseci[1]+ ... +mjeseci[11];

Niz smo mogli popuniti i ovako:

int mjeseci[12]={31,28,.......31};

Učinak je potpuno isti, samo što smo uštedjeli nešto prostora, a i source je čitljiviji. Čak smo kod "mjeseci[12]" mogli da izostavimo broj 12 - tada bi kompajler sam prebrojao elemente niza.
Kod višedimenzionalnih polja princip je isti kao i kod nizova. Recimo:

#include <stdio.h>
main()
{
        int
        mat[5][5]={{1,1,1,1,1},
             {2,2,2,2,2},
             {3,3,3,3,3},
             {4,4,4,4,4},
             {5,5,5,5,5}};

        printf("Na mjestu 3,3 nalazi se %d\n",mat[3][3]);
}

Kao rezultat dobit će se broj 4. Popunjavanje matrice je moglo da se napiše u jednom redu, ali je ovako preglednije.

U memoriji su elementi matrice smješteni na slijedeći način:

mat[0][0],mat[0][1],mat[0][2],.. mat[0][4],mat[1][0], ... mat[4][4]

Najbrže se mijenja prvi indeks s desna.

A sada jedno UPOZORENJE: kompajler ne provjerava da li navedeni član niza zaista postoji. Ako rezervirate 5 elementa, a pristupate 10-om kompajler neće prijaviti nikakvu grešku (kao recimo u Basic-u). Zašto? Zato što bi takvo provjeravanje višestruko usporilo pristup elementima niza i iz još mnogo drugih razloga. Zato dobro vodite računa o tome!!!

Nizovi i matrice se koriste na isti način i kod ostalih tipova podataka. Nizovi tipa "char" mogu se tretirati i kao stringovi. U C-u se string (niz znakova) piše između dvostrukih navodnika. Da bi se označio kraj stringa na kraju mu se dodaje znak '\0'.

#include <stdio.h>
main()
{
        char c[6]="Amiga";

        printf("%s ",c);   // %s - jer se radi o stringu
}

Riječ "Amiga" ima 5 slova plus znak '\0' je 6 i u memoriji se pamti:

'A','m','i','g','a','\0'

Da ne bi brojali koliko slova ima string jednostano se broj elemenata može izostaviti:

char c[]="Amiga rules!!";

Stringovi se ne mogu porediti ili kopirati/nadovezivati kao, recimo, u Basic-u:

char a[]="Amiga";
char b[]="rules";
char c[20];

c=a+b;   // Ovako neće moći!!

"a","b" i "c" zaista postoje. Varijabla "a" sadrži adresu prvog elementa u nizu - a[0]. Ali nema smisla zbrajati adrese.

Zato standardna C biblioteka obezbjeđuje dosta funkcija za rad sa stringovima. Potrebno je samo include-ovati "string.h" header na početku programa i one će vam postati dostupne. Pogledajte SAS C-ov (ili bilo koji drugi) help, bilo koju knjigu da bi vidjeli koje su to funkcije i čemu služe. Evo male ilustracije:

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

main()
{
        char a[]="Amiga";
        char b[]=" rules!!";
        char c[20];   // 20 znakova će biti dovoljno.
        strcpy (c,a);   // Prvo kopiramo niz a u niz c.
        strcat (c,b); // A zatim nizu c nadovezujemo niz b.

        /* Postoji i varijante "strncpy" i "strncat" koje koriste najviše n znakova pa je tako moguće izbjeći prekoračenje */

        printf("%s\n",c);   // %s pošto se radi o stringu.
        printf("String je dug %d znaka.\n",strlen(c));

}

Vrlo često je potrebno konvertirati jedan tip broja u drugi (npr. funkcija traži da joj se proslijedi "int" tip, a mi imamo potrebnu vrijednost u varijabli "float" tipa). To se radi tako što se ispred izraza koji se dodijeljuje (ili kojim se poziva funkcija) u zagradi stavlja tip u koji se izraz želi konvertirati. Evo primjera:

int a;
float b=56.8223;

a=(int)b;   // "a" dobija vrijednost 56 jer se zaokružuje na manju vrijednost.

printf (" b=%f int(b)=%d\n",b,(int)b);   // funkciji se proslijeđuje "float" >>>int tip<<<

Napomenimo samo još da indeksi pomoću kojih se pristupa nizu moraju biti cijeli brojevi.


Do sada smo koristili operaciju zbrajanja brojeva. Nabrojmo operacije dostupne u C-u:

Operacija Uloga
= dodijeljivanje
+ zbrajanje
- oduzimanje
* množenje
/ dijeljenje
% ostatak pri dijeljenju
++ inkrementacija
-- dekrementacija
& logičko AND
| logičko OR
! logičko NOT
^ logičko ekskluzivno OR
<< pomijeranje u lijevo
>> pomijeranje u desno

Da objasnimo one koji nisu baš najjasniji:

%
Kada cijelobrojno dijelimo 13 na 5 dobijamo 2 i ostatak 3:

i=13;
printf("%d ",i%5);

Ne treba miješati sa postotkom u format stringu.

++ i --
Služe za povećavanje/smanjivanje varijable za jedan. Ako se pišu ispred varijable prvo se izvrši inkrementacija/dekrementacija, pa sa tom novom vrijednošću varijable se računa dalje. A ako su navedeni poslije varijable prvo se izvrše proračunavanja u tom redu, a zatim inkrementacija/dekrementacija:

int a=b=10;   // Može se i ovako dodijeljivati.
int c;

c=a++;   // "c" će dobiti vrijednost 10, pa će se nakon toga "a" uvećati za 1
c=--b;   // umanjit će se "b" za 1, pa će se nova vrijednost dodijeliti varijabli "c"
.

Isto bi bilo da smo napisali:

int a=b=10;
int c;

c=a;
a=a+1;   // moglo je samo a++ ili ++a, pošto je to jedina operacija redoslijed nije bitan

b=b-1;
c=b;

&, |, !, ^, >>, <<
Operacije nad bitovima. "&" ne treba mijesati s onim za unarnim operatorom "&" za dobijanje adrese varijable. Evo i primjeera:

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


10100011<<3 = 00011000     10100011>>3 = 00010100 Pomijeranje (šiftovanje) u lijevo za jedno mjesto je ekvivalentno množenju sa 2, samo je brže.
Šiftovanje na desno je ekvivalentno dijeljenju sa dva. No, dobar kompjaler, kakav je SAS sam uvodi optimizacije i kad god je moguće spore operacije množenja i dijeljenja zamijenjuje šiftovanjem.

Pored "++" i "--" skraćenog označavanja C posjeduje još vrsti optimizacija u pisanju. Pri računanju novog sadržaja varijable on zavisi može se koristiti skraćeni oblik. Npr:

a=a+1;

može se pisati:

a+=1;

Varijabli "a" se dodaje iznos izraza desno od znaka "+=". Isto vrijedi i za "/=", "*=" itd.

a*=3*b+17; je isto što i a=a*(3*b+17);

Nadam se da je jasno. Sada ćemo se pozabaviti naredbama za kontrolu toka programa. Naime, znamo da se svi dijelovi programa ne izvršavaju isti broj puta - neki se izvrše i više stotina tisuća puta, a neki ponekad i nikad.
Da vidimo kako se kontrola toka ostvaruje u C-u:


         if-else iskazi

Kada želimo da se neki blok naredbi izvrši (ili ne izvrši) u zavisnosti od vrijednosti nekog izraza koristimo ovu "naredbu". Struktura je slijedeca:

if (uslov)
   naredba

Znači, testira se uslov (mora biti u zagradama), pa ako je ispunjen izvršava se naredba iza iskaza, a ako nije naredba iza se preskače (ne izvršava). Ako ima više naredbi koje treba uslovno izvršiti stavljaju se unutar velikih (vitičastih) zagradi. Evo primjera:

#include <stdio.h>
main()
{
        int a;
        printf(" Unesi šifru: ");
        scanf("%d",&a);
        if (a==5766)
          printf("\nTočno. Sistem vam je na raspolaganju.\n");
        else
          printf("\nŠifra je netočna! Uljez!!\n");

}

Unosimo broj, koji se smješta u varijablu "a" i "if" iskazom provjeravamo da li je to broj 5766 (točan broj). Ako jeste ispisuje se jedna poruka, a ako nije druga. Zapazite da se umjesto "=" koristi dupli znak jednakosti! Jednostruki se koristi kod dodijeljivanja, pa da ne bi došlo do zabune kada je potrebno provijeriti točnost nekog iskaza koristi se "==". Da je pisalo:

if(a=5766)

varijabla "a" bi dobila vrijednost 5766 i cijeli iskaz bi bio istinit. Usvari, kod svih uslovnih iskaza se sračuna vrijednost izraza u zagradama, pa se na osnovu te vrijednosti određuje šta će se izvršiti. Ako je vrijednost 0 iskaz je netočan, a ako je bilo šta drugo točan je. Npr.

if (5)
        naredba

Naredba će se uvijek izvršiti jer je vrijednost izraza (5) različita od nule.
Ili npr. if(a) znaci isto što i if (a!=0).
"!=" znaci razlicito (od).

Pored uslova "==" koriste se i ovi uslovi:

!= različito
> veće
< manje
>= veće ili jednako
<= manje ili jednako
&& logičko "i"
|| logičko "ili"



Primjer:

if (a==5 && b>7)
        naredba

Naredba će se izvršiti samo ako je a=5 i ako je b>7. Ako makar jedan uslov nije zadovoljen nareda se ne izvršava. Da je pisalo:

if (a==5 || b>7)

bilo bi dovoljno da je makar jedan od oba uslova ispunjen (ili oba dva) pa da se naredba izvrši.


U gornjem primjeru je upotrijebljena proširena verzija "if" iskaza. Ona se koristi kada, recimo, jednu naredbu treba izvršiti kada je uslov zadovoljen, a drugu kada nije, pa da se ne bi pisale dvije "if" strukture i dva puta provjeravao uslov, piše se jedna "if else" struktura. Dakle:

if (uslov)
        naredba 1
else
        naredba 2

Uvijek se izvršava samo jedna od dvije naredbe i to prva ako je uslov ispunjen, a druga ako nije.

Postoji jos širi oblik "if" iskaza. On se koristi kada je potrebno provijeriti više iskaza od kojih je samo jedan točan u zavisnosti od toga koji će (ne)izvrsiti neke naredbe. Oblik je slijedeći:

if (uslov1)
        naredba 1
else if (uslov2)
        naredba2
    .
    .
    .
else
        naredba5

Dakle, provijerava se "uslov1". Ako je točan izvršava se "naredba1" i izlazi se iz cijelog "if" iskaza (ostale provjere se i ne vrše). Ako nije, provijerava se "iskaz2", pa ako je on točan izvršava se "naredba2" i tako redom. Ako ni jedan od uslova nije ispunjen izvršava se naredba iza riječi "else". "else" je moguće i izostaviti iz "if" konstrukcije. Primjer:

if(a==1)
        printf("Broj jedan, hmmm...\n");
else if(a==2)
{
        printf("Broj dva?!?\n");     //Pošto ima više naredbi moraju se staviti između vitičastih zagrada
        printf("Mrzim broj dva\n");
}
else if (a==3)
        printf("Odlicno, broj tri!\n");
else
        printf("Broj nepoznat\n");

Znači, provijera se da li je a=1. Ako jeste. ispisuje se prva poruka, a ako nije provijerava se da li je 2, pa ako jeste druga itd. Ako nije ni 1 ni 2 ni 3 izvršava se naredba poslije "else".


"switch" naredba ima slijedeći oblik:

switch (a)
{
        case b:
            naredba1
        case c:
            neredba2
    .
    .
    .
        default:
            naredbaN
}

U prvom redu u zagradi se stavlja izraz (varijabla) koji se provjerava. Zatim se navode vrijednosti za koje se treba odraditi neka naredba. Kada se vrijednost poslije riječi "case" poklopi sa vrijednošću izraza (var.) koji se provjerava izvršavaju se SVE ostale naredbe u "switch" strukturi. Ako želimo da se izvrši samo jedan dio (između dva "case"-a) na kraju bloka naredbi staviti ćemo "break". "switch" blok je između srednjih velikih zagrada. Ako izraz ne dobije ni jedno od ispitivanih vrijednosti izvršava se naredba (blok naredbi) posle riječi "default".

Recimo da varijabla smijer sadrži jedan od brojeva: 1,2,4 ili 8 i da oni označavaju stranu na koju je povučen joystick. Za svaku smijer trebamo da pozovemo funkciju koja obrađuje taj slučaj:

switch (smijer)
{
        case 1:
            gore();
            break;
        case 2:
            dolje();
            break;
        case 4:
            lijevo();
            break;
        case 8:
            desno();
}

Da nismo stavljali break i da je, recimo, smijer imao vrijednost 1 pozvale bi se sve funkcije u "switch" strukturi. Pošto je "case 8" zadnja provjera nema potrebe stavljati "break".


Petlje se koriste kada neki broj naredbi treba ponoviti više puta.

for petlja ima oblik:

for (izraz1; izraz2; izraz3)
        tijelo petlje

Izrazi 1 i 3 su najčešće izrazi dodijeljivanje, a izraz 2 je najčešće neko poređenje. Petlja se "vrti" (tijelo petlje se ponavlja) dok god je izraz 2 istinit. Izraz 1 se izvršava samo jednom na početku, a izraz 3 pri svakom prolazu. Recimo da želimo ispisati kvadrate prvih 20 brojeva:

#include <stdio.h>
main()
{
int i;
        for(i=1;i<=20;i++)
        {
            printf("i=%3d ",i);
            printf("i^2=%d\n",i*i);
        }
}

Varijablu "i" postavljamo na vrijednost 1 (izraz1), provjeravamo da li je i<=20 (izraz2), pošto jeste izvršava se prvi prijelaz i povećava "i" za 1 (izraz3).
Bez obzira da li stoji i++ ili ++i taj dio će se izvršiti poslije izvršenja tijela petlje.

while petlja je jednostavnija. Kod nje se samo navodi uslov:

while (uslov)
        tijelo petlje

Kod nje, za razliku od for petlje ne mora postojati specijalna indeksna varijabla.
Recimo da želimo da prebrojimo koliko elemenata u nekom nizu ima prije pojavljivanja prve nule:

#include <stdio.h>
main()
{
        int niz[]={5,2,7,77,4,32,66,43,0,0,0};
        int i=0;

        while (niz[i]!=0)
            i++;
        printf("Prva %d elemenata nisu nule.\n",i);
}

Svaki element poredimo sa nulom, pa ako je različit prelazimo na slijedeći (tj. uvećavamo "i"). Kada se dostigne element sa vrijednošću nula brojanje se prekida.
Postoji i oblik while petlje kod koga je poređenje na kraju petlje, pa se tijelo petlje uvijek izvrši makar jednom:

do
        tijelo petlje
while (uslov)

Sve navedeno za while vrijedi i za do-while.

C obiluje skraćenicama svakakve vrste. Evo još jedne koja mijenja blok naredbi, a vraća jednu od dvije moguće vrijednosti u zavisnosti od navedenog uslova:

uslov ? vrijednost za točno : vrijednost za netočno

Vrijednosti za točno i netočno mogu biti izrazi, funkcije...

#include <stdio.h>
main()
{
        int a;
        int b;
        int max;

        printf("Unesi 2 broja:");
        scanf ("%d %d",&a,&b);

        max=a<b ? b : a;

        printf("Veći je broj %d\n",max);
}

"max" dobija vrijednost varijable "b" ako je uslov ispunjen (a<b), a vrijednost "a" ako nije.


Za kraj evo i jednog "malo većeg" programa. Nije komentiran, ali sam siguran de će te ga lako shvatiti (ako ste pratili predhodne nastavke). Da bi vijedeli šta radi morate ga iskompajlirati i startati. Ustvari i ne morate, jer ne treba neka mudrost da bi se to ukapiralo iz listinga. :)

#include <stdio.h>
main()
{
        int vrh=1000, dno=0, i=1;
        char c,s[10];

        printf("\fZamisli jedan broj između 0 i %d, a ja ću ga pogoditi iz najviše deset
                      \npokušaja.\n",vrh);
        printf("Odgovaraj sa veći, manji ili točno (i nemoj lagati).\n\n\n");
        do
        {
        printf("Jel' to možda broj %d ?", (vrh+dno)/2);
        scanf("%s",s);
        c=s[0];

        if(c=='m' || c=='M')
        {
            vrh=(vrh+dno)/2-1;
            i++;
        }
        else if(c=='v' || c=='V')
        {
            dno=(vrh+dno)/2+1;
            i++;
        }
        else if(c!='t' && c!='T')
            printf("Molim?\n");
        }
        while (c!='t' && c!='T');

        printf("URA!!! \nPogodio sam iz samo %d puta!!!\n",i);
}


Do slijedećeg nastavka vježbajte i budite spremni, jer neće biti lako kao do sada. ;>>>>>

Milan Golubovic