P o g l a v l j e 1 : OSNOVNE NAPOMENE Ponimo sa brzim uvodom u C. Cilj nam je da poka‚emo su„tinske elemente jezika kroz praktine programe, ali bez zadr‚avanja na detaljima, formalnim pravilima i izuzecima. U ovom poglavlju ne poku„avamo da budemo sveobuhvatni, niti sasvim precizni (podrazumeva se da su svi primeri ispravni). elimo da vas, „to je pre mogu†e, dovedemo do nivoa znanja na kome mo‚ete sami pisati korisne programe. Da bismo to uradili, moramo se koncentrisati na osnovne stvari: promenljive i konstante, aritmetiku, kontrolu toka, funkcije i najosnovnije poznavanje ulaza i izlaza nekog programa. Namerno smo izostavili iz ovog poglavlja one karakteristike C-a koje su od vitalnog znaaja za pisanje du‚ih programa. Tu spadaju pokazivai, strukture, najve†i deo bogatog seta C operatora, nekoliko iskaza za kontrolu toka i standardna biblioteka. Ovaj pristup, naravno, ima i svoje nedostatke. Najuoljiviji je da se ne mo‚e na†i prikaz neke karakteristike jezika u celini na samo jednom mestu. A kako primeri ne iskori„†avaju u potpunosti svu snagu C-a, to oni nisu tako sa‚eti i elegantni kako bi mogli da budu. Mi smo poku„ali da umanjimo ovaj efekat, ali imajte ga na umu. Slede†i nedostatak je taj „to se u kasnijim poglavljima neizbe‚no ponavljaju delovi iz ovog poglavlja. Nadamo se da †e ponavljanje vi„e pomo†i nego odmo†i. U svakom sluaju, iskusniji programeri bi trebalo da izvuku iz ovog poglavlja ono „to je njima potrebno. Poetnici bi trebalo da nakon proitanog poglavlja napi„u sami svoje male programe, ve† prema zadacima koji su dati za ve‚bu na kraju svake celine. Obe grupe mogu koristiti ovo poglavlje kao podlogu na kojoj †e se zasnivati detaljniji opisi koji poinju u Poglavlju 2. 1.1 POšETAK Jedini nain da se naui neki programski jezik je da se pi„u programi u njemu. Prvi program koji treba napisati je isti za sve jezike: od„tampaj hello, world Ovo je poetna prepreka: da bi se preskoila potrebno je da negde napi„ete tekst programa, da ga uspe„no kompajlirate (prevedete), uitate u memoriju, startujete i ustanovite gde se pojavio izlaz hello, world. U pore”enju sa ovim tehnikim detaljima, sve drugo je relativno lako. Naravno, prvo treba ovladati njima. U C-u, program za „tampanje hello, world izgleda ovako: #include main() { printf("hello, world\n"); } Kako †e se ovaj program startovati, zavisi od operativnog sistema koji koristite. Kao specifian primer, na UNIX operativnom sistemu mo‚ete otkucati tekst programa i datoteci u kojoj se nalazi dati nakon imena ekstenziju " .c ". Neka je program nazvan hello.c; kompajlirali bi ga komandom cc hello.c. Ako niste nigde pogre„ili u kucanju, kompajliranje †e prote†i bez upozorenja i formira†e se datoteka a.out. Ako startujete program a.out kucaju†i njegovo ime, on †e od„tampati hello, world Na drugim sistemima, postupak †e biti drugaiji. Slede neka obja„njenja o samom programu. C program, bilo koje veliine, sastoji se od jedne ili vi„e funkcija i od promenljivih. Funkcija se sastoji od iskaza koji odre”uju koje raunske operacije treba sprovesti, a promenljive sadr‚e vrednosti sa kojima se vr„e izraunavanja. C funkcije su sline funkcijama i potprogramima Fortrana ili procedurama i funkcijama Paskala.U na„em primeru, funkcija je main. U op„tem sluaju, imate slobodu da funkciji date bilo kakvo ime, ali je main specijalno ime - program se izvr„ava od poetka funkcije main. To znai da svaki program mora imati main negde. Funkcija main je obino pozvati druge funkcije koje obavljaju posao, od kojih †e neke biti u istom programu sa main , a druge †e im se prikljuiti iz biblioteke prethodno napisanih funkcija. Prva linija programa, #include govori kompjuteru gde da potra‚i podatke o ulazno/izlaznim funkcijama kakva je, recimo, printf. Podaci su sme„teni u biblioteci koja je definisana standardom: u ovom sluaju je to STanDard Input Output biblioteka. Detaljnije †e o njoj biti reeno u Dodatku B. Jedan od naina razmene podataka izme”u funkcija je pomo†u argumenata. Izme”u zagrada () koje slede iza imena funkcije navodi se lista argumenata sa kojima †e funkcija operisati. Ovde main nema argumente, „to se zakljuuje iz main(). Vitiaste zagrade { } obuhvataju iskaz ili grupu iskaza koji ine funkciju. Funkcija main sadr‚i samo jedan iskaz, printf("hello, world\n"); Funkcija se poziva navo”enjem njenog imena i liste argumenata koje koristi. Lista argumenata je navedena u zagradama (). Ne postoji naredba CALL kakvu ima Fortran, na primer. Zagrade () moraju biti prisutne ak i ako funkcija nema argumente. Linija printf("hello, world\n"); predstavlja poziv funkcije printf koja ima argument "hello, world\n". printf je funkcija iz biblioteke i ona „tampa argument na ekranu (ukoliko nije drugaije odre”eno). U ovom sluaju „tampa niz karaktera koji ini njen argument. Grupa znakova izme”u dvostrukih navodnika, kakva je hello, world\n zove se niz znakova ili string. U poetku †e jedina upotreba stringa biti u vidu argumenata funkcije printf i nekih drugih funkcija. Deo niza \n je u C-u oznaka za novi red, i oznaava da „tampanje karaktera posle njega treba poeti od poetka novog reda. Va‚e†i naziv za konstrukcije tipa \n je eskejp (escape) sekvenca. Ako izostavite \n (vredan eksperiment), otkri†ete da se nakon od„tampanog hello, world slede†a pozicija za „tampanje nije premestila na poetak slede†eg reda. Jedini nain da se to izvede je pomo†u \n, kako je ve† opisano. Ako poku„ate ne„to kao printf("hello, world "); C kompajler †e neljubazno prijaviti da nedostaju navodnici. Funkcija printf nikad ne obezbe”uje prelazak na poetak novog reda automatski, tako da uzastopni pozivi te funkcije formiraju izlaznu liniju postupno, a ne proizvode vi„e linija jednu ispod druge. Na„ prvi program je mogao biti napisan i ovako: #include main() { printf("hello"); printf(",world"); printf("\n"); } i proizveo bi istu izlaznu liniju. Primetite da \n predstavlja samo jedan znak. Konstrukcija kakva je \n predstavlja nain da se prika‚u i primene znakovi koji su nevidljivi ili se ne mogu otkucati. Izme”u ostalih, C obezbe”uje \t za tabulator, \b za backspace, \" za dvostruki navodnik, i \\ za obrnutu kosu crtu (backslash). Kompletna lista je data u odeljku 2.3. ž Ve‚ba 1-1 Startujte program "hello, world" na va„em kompjuteru. Eksperimenti„ite sa izostavljanjem delova programa da biste videli koje †ete poruke o gre„kama dobiti. ž Ve‚ba 1-2 Otkrijte „ta se de„ava kada niz koji printf treba da „tampa sadr‚i \x , gde je x neki znak koji nije me”u pomenutima. 1.2 PROMENLJIVE I ARITMETIšKI IZRAZI Slede†i program „tampa tabelu temperatura u Farenhajtovim stepenima i njihove ekvivalente u Celzijusovim stepenima, koriste†i formulu ųC = (5/9)(ųF - 32): 0 -17 20 -6.7 40 4.4 60 15.6 ... ... 260 126.7 280 137.8 300 148.9 Program se sastoji od same funkcije main. On je du‚i od onog koji „tampa "hello, world", ali ne i komplikovaniji. Uvodi se mnogo novih elemenata, ukljuuju†i komentare, deklaracije, promenljive, aritmetike izraze, petlje i formatizovani izlaz. #include /* „tampaj Fahrenheit - Celsius tabelu za fahr = 0, 20, ... , 300 */ main() { float fahr, celsius; int lower, upper, step; lower = 0; /* donja granica temp. tabele */ upper = 300; /* gornja granica */ step = 20; /* veliina koraka */ fahr = lower; while (fahr <= upper) { celsius = (5.0/9.0) * (fahr - 32.0); printf("%4.0f %6.1f \n", fahr, celsius); fahr = fahr + step; } } Prve dve linije predstavljaju komentar, koji u ovom sluaju ukratko obja„njava „ta program radi. Bilo koje znake izme”u /* i */ kompajler ignori„e; ova injenica se mo‚e slobodno iskoristiti da bi se napisao program koji je lak„i za razumevanje. Komentar se mo‚e pojaviti na bilo kom mestu na kom mo‚e i blanko, tabulator, ili znak za novi red. U C-u, sve promenljive moraju biti deklarisane pre upotrebe, naje„†e na poetku funkcije, pre bilo koje izvr„ne komande. Ako zaboravite deklaraciju, kompajler †e to prijaviti kao gre„ku. Deklaracija se sastoji od tipa i liste promenljivih koje imaju taj tip, kao u float fahr, celsius; int lower, upper, step; Tip int znai da su promenljive na listi celi brojevi; float stoji za pokretni zarez, tj. za realne brojeve koji mogu imati deo iza decimalne take. Tanost int i float zavisi od ma„ine koju koristite: na primer, 16- bitni int mo‚e predstaviti brojeve u opsegu od -32768 do +32767. Tipina du‚ina float broja je 32 bita (4 bajta), sa sedam znaajnih cifara i opsegom od 10^-38 do 10^+38. Pored int i float, C obezbe”uje jo„ nekoliko osnovnih tipova podataka : char znak - jedan bajt short mali ceo broj long veliki ceo broj double realan broj dvostruke tanosti Veliine ovih tipova tako”e zavise od raunara. Postoje jo„ i polja, strukture i unije osnovnih tipova, pokazivai na njih i funkcije koje ih vra†aju, a koje †emo pravovremeno sresti. Izraunavanje programa konverzije temperature poinje iskazima dodeljivanja : lower = 0; upper = 300; step = 20; fahr = lower; koji postavljaju promenljive na njihove poetne vrednosti. Svaki iskaz se zavr„ava sa ; (taka-zarez). Svaka linija u tabeli se izraunava na isti nain, pa smo zato upotrebili petlju koja se ponavlja jedanput za svaku liniju; to je svrha while iskaza: while (fahr <= upper) { ... } Ispituje se istinitost uslova u zagradama. Ako je istinit (fahr je manje ili jednako upper), telo petlje (svi iskazi unutar zagrada { i }) se izvr„ava. Zatim se uslov ponovo ispituje, i ako je istinit telo se izvr„ava jo„ jedanput. Kad uslov postane neistinit (fahr postane ve†e od upper), petlja se zavr„ava, telo se preskae i izvr„avanje se nastavlja iskazom koji sledi iza petlje. U na„em programu nema iskaza koji slede posle petlje, pa se program tu zavr„ava. Telo while petlje mo‚e biti jedan ili vi„e iskaza obuhva†enih zagradama { i }, ili samo jedan iskaz koji u tom sluaju ne mora biti naveden u zagradama, kao u while (i < j) i = 2 * i; U oba sluaja iskazi koji ine telo while petlje su pomereni za jednu tab poziciju udesno, tako da odmah jasno mo‚ete videti „ta je telo petlje. Ovo uvlaenje teksta udesno nagla„ava logiku strukturu programa. Iako C ne vodi rauna o tome na kom mestu je iskaz ispisan, pravilno pozicioniranje teksta i upotreba blanko znakova su izuzetno znaajni za itljivost programa. Preporuujemo vam pisanje samo jednog iskaza u jednoj liniji, i ostavljanje blanko znakova oko operatora. Pozicija zagrada je manje znaajna; izabrali smo jednu od nekoliko popularnih varijanti. Izaberite stil koji vam odgovara, a zatim ga se dosledno pridr‚avajte. Najve†i deo posla se obavi u telu petlje. Temperatura u ųC se izraunava i dodeljuje promenljivoj celsius kroz iskaz celsius = (5.0 / 9.0) * (fahr - 32.0) Razlog za kori„†enje 5.0 / 9.0 umesto jednostavnijeg 5 / 9 le‚i u prirodi C- a. Kao i kod mnogih drugih jezika, i ovde je rezultat deljenja dva cela broja ceo broj i ostatak koji se odbacuje. Tako bi 5 / 9 bilo nula, „to bi dalo sve temperature jednake nuli. Decimalna taka kod konstanti naznaava da je re o realnom broju, tako da je 5.0 / 9.0 jednako 0.555... , „to smo i ‚eleli. Tako”e smo napisali i 32.0 umesto 32 iako je jasno da je fahr tipa float. U ovom sluaju 32 †e biti automatski konvertovano u float tip (u 32.0) pre nego „to pone oduzimanje. Vi„e kao pitanje stila, savetujemo vam da pi„ete konstante (koje su realni brojevi) sa decimalnom takom i onda kada sadr‚e celobrojne vrednosti; to nagla„ava njihovu prirodu realnog broja kod onih koji itaju tekst. Detaljna pravila konverzije celih brojeva u realne je data u Poglavlju 2. Za sada, primetite da dodeljivanje fahr = lower; i test while (fahr <= upper) rade kao „to se i moglo oekivati - int promenljiva se konvertuje u float pre nego „to se operacija zapone. Ovaj primer, tako”e, malo bolje pokazuje nain na koji printf radi. Funkcija printf je izlazna funkcija op„te namene, do detalja opisana u standardnoj biblioteci. Njen prvi argument je niz znakova u kome svaki znak % pokazuje da umesto njega treba da se od„tampa njemu odgovaraju†i argument koji sledi posle tog niza, i to u formatu koji je tako”e uz njega naveden. Na primer, u iskazu printf("%4.0f %6.1f \n", fahr, celsius); Prvi argument je niz %4.0f %6.1f \n, drugi argument je fahr, a tre†i je celsius. Prvi deo niza, %4.0f, odnosi se na drugi argument tj. na fahr, a drugi deo niza, %6.1f, na njemu odgovaraju†i, tre†i argument tj. na celsius. Ovde je specifikacijom %4.0f odre”eno da se fahr „tampa kao realan broj od etiri cifre, bez cifara iza decimalne take. Specifikacijom %6.1f se od printf zahteva da promenljivu celsius od„tampa u formatu realnog broja sa „est cifara za celobrojni deo i jednom cifrom posle decimalne take. Neki delovi specifikacije se mogu izostaviti: %6f znai da argument treba da bude predstavljen sa „est cifara; %.2f zahteva dve cifre iza decimalne take, ali ne ograniava du‚inu celobrojnog dela; najzad, %f zahteva da se broj od„tampa u formatu realnog broja (sa decimalnom takom). Funkcija printf tako”e prepoznaje %d za decimalne cele brojeve, %o za oktalne, %x za heksadecimalne brojeve, %c za znak, %s za niz znakova, i %% za % (procenat). Svaka % konstrukcija u prvom argumentu funkcije printf je u paru sa njoj odgovaraju†im drugim, tre†im, itd. argumentom; ti argumenti moraju biti pore”ani istim redom kojim su pore”ane njihove odgovaraju†e % konstrukcije, u protivnom †ete dobiti besmislene izlaze. Uzgred, funkcija printf nije deo C jezika; ulaz i izlaz nisu definisani C-om. Nema nieg maginog u funkciji printf; ona je samo korisna funkcija koja je deo standardne biblioteke funkcija. Toj biblioteci mogu pristupati C programi. Da bismo se koncentrisali na sam C jezik, o ulazu i izlazu ne†emo mnogo govoriti do Poglavlja 7. Do tada †emo odlo‚iti bavljenje ulazom podataka. Ako morate da unosite brojeve, proitajte diskusiju o funkciji scanf. Funkcija scanf je slina funkciji printf, s tom razlikom „to unosi podatke umesto da ih „tampa. ž Ve‚ba 1-3 Izmenite program za konverziju temperature tako da „tampa zaglavlje iznad tabele temperatura. ž Ve‚ba 1-4 Napi„ite program za „tampanje tabele konvertovanih temperatura iz Celzijusovih stepeni u Farenhajtove stepene. 1.3 F O R ISKAZ Kao „to ste mogli oekivati, postoji mnogo razliitih naina da se napi„e program; poku„ajmo sa varijacijom programa za konverziju temperature. main() /* Fahrenheit - Celsius tabela */ { int fahr; for (fahr = 0; fahr <= 300; fahr = fahr + 20) printf("%4d %6.1f \n", fahr, (5.0 / 9.0) * (fahr - 32)); } Ovo daje iste rezultate, ali zaista izgleda drugaije. Jedna od glavnih izmena je eliminacija najve†eg broja promenljivih: ostala je samo promenljiva fahr, koja je sada tipa int (da bi pokazala %d konverziju u printf). Donja i gornja granica i korak se pojavljuju samo kao konstante u for iskazu, koji je sada konstruisan drugaije. Izraz koji izraunava temperaturu u Celzijusovim stepenima se sada pojavljuje kao tre†i argument funkcije printf umesto kao odvojen iskaz. Ova poslednja izmena je primer op„teg pravila u C-u : u bilo kom kontekstu u kome se mo‚e pojaviti promenljiva nekog tipa, mo‚e se pojaviti i itav izraz tog tipa. Kako tre†i argument funkcije printf mora biti realan broj da bi bio u skladu sa drugim delom %6.1f niza, to se na mestu tog argumenta mo‚e pojaviti bilo koji izraz float tipa. Iskaz for je petlja, uop„tenje petlje while. Ako for uporedite sa prethodnim while, njegovo funkcionisanje bi trebalo da bude jasno. Iskaz for sadr‚i tri dela me”usobno odvojena znakom ;. Prvi deo,inicijalizacija brojaa, fahr = 0; se obavlja jedanput, pre nego „to petlja uop„te pone. Drugi deo je uslov kojim se kontroli„e petlja: fahr <= 300; Ovaj uslov se proverava; ako je istinit, telo petlje (ovde je to samo jedan poziv printf funkcije) se izvr„ava. U tre†em delu petlje, reinicijalizaciji brojaa, fahr = fahr + 20; vr„i se uve†avanje brojaa fahr za korak 20. Posle ovoga, petlja se ponovo testira sa novim fahr i izvr„ava sve dok je uslov ispunjen. Kao i kod while petlje, telo petlje mo‚e biti samo jedan iskaz ili grupa iskaza navedena u vitiastim zagradama { i }. Inicijalizacija i reinicijalizacija mogu biti bilo koji izrazi. Izbor izme”u for i while je proizvoljan, zavisno od toga „ta je jednostavnije. Iskaz for se obino upotrebljava kod petlji u kojima su inicijalizacija i reinicijalizacija jednostavne konstrukcije i logiki povezane. U takvim sluajevima je for petlja kompaktnija nego while i sadr‚i kontrolu petlje na jednom mestu. ž Ve‚ba 1 - 5 Izmenite program za konverziju temperature tako da „tampa tabelu temperatura obrnutim redom, od 300 stepeni do 0. 1.4 SIMBOLIšKE KONSTANTE Evo konanog osvrta pre nego „to zauvek napustimo konverziju temperature. Lo„a praksa je da ubacujemo brojeve kao „to su 300 i 20 u programu. Oni prenose vrlo malo informacija onome ko bi kasnije morao da ita programe, i te„ko ih je izmeniti na sistematian nain. Sre†om, C obezbe”uje nain da se izbegne ubacivanje konkretnih brojeva u program. Sa #define konstrukcijom na poetku programa mogu†e je definisati simboliko ime ili simboliku konstantu tako da ona predstavlja odre”eni niz karaktera. To izgleda ovako: #define i m e tekst Nakon toga kompajler †e na svim mestima gde se polavljuje simbolika konstanta i m e izvr„iti njenu zamenu ekvivalentnim nizom znakova tekst. Izuzetak je sluaj kada je i m e navedeno pod dvostrukim navodnicima. Tada kompajler ne†e izvr„iti zamenu. Bitno je napomenuti da tekst mo‚e biti bilo kakvog oblika: on nije ogranien samo na brojeve. #include #define LOWER 0 /* donja granica tabele */ #define UPPER 300 /* gornja granica */ #define STEP 20 /* korak */ /* „tampaj Fahrenheit - Celsius tabelu */ main() { int fahr; for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) printf("%4d %6.1f \n", fahr, (5.0 / 9.0) * (fahr - 32)); Elementi LOWER, UPPER i STEP su konstante, pa se ne pojavljuju u deklaracijama. Simbolika imena se obino pi„u velikim slovima da bi se razlikovala od imena promenljivih koje se pi„u malim slovima. Primetite da ne postoji znak ; na kraju simbolike definicije. Po„to se svi znakovi posle simbolikog imena zamenjuju u tekstu, onda bi se, u sluaju kad bi simbolike definicije zavr„avali znakom ;, i u iskazu for pojavilo previ„e znakova ;. 1.5 ZNAKOVNI ULAZ I IZLAZ Sada †emo razmotriti familiju povezanih programa koji vr„e jednostavne operacije nad znakovnim podacima. Otkri†ete da su mnogi programi samo pro„irene verzije prototipa o kojima †emo mi diskutovati. Standardna biblioteka obezbe”uje funkcije za itanje i upisivanje znakova. Funkcija getchar() preuzima slede†i znak sa ulaza svaki put kad je pozvana, a vra†a u program vrednost koja odgovara tom znaku. To znai da posle c = getchar(); promenljiva c sadr‚i vrednost koja odgovara znaku koji je funkcija getchar preuzela sa ulaza. Znakovi uobiajeno dolaze sa tastature. Funkcija putchar je komplement funkcije getchar. Iskaz putchar(c); „tampa sadr‚aj promenljive c na nekom izlazu, naje„†e ekranu. Pozivi funkcija putchar i printf se mogu isprepletati; izlaz †e se pojavljivati onim redosledom kojim su funkcije pozivane. Kao „to je to bio sluaj sa funkcijom printf, ni funkcije getchar i putchar nisu ni„ta posebno. One nisu deo C jezika, ali im svaki C program mo‚e pristupiti. 1.5.1 Kopiranje datoteka Uz pomo† funkcija getchar i putchar mo‚ete napisati iznena”uju†e mnogo korisnih programa ne znaju†i ni„ta o ulazu i izlazu. Najjednostavniji primer je program koji kopira svoj ulaz na izlaz, znak po znak. Algoritam izgleda ovako: proitaj znak sa ulaza while(znak nije znak za kraj datoteke) po„alji na izlaz upravo proitani znak proitaj slede†i znak Napisan u C-u, program bi izgledao ovako: #include /* kopiranje ulaza na izlaz; prva verzija */ main() { int c; c = getchar(); while(c != EOF) { putchar(c); c = getchar(); } } Relacioni operator != znai "razliit od". Osnovni problem je pronala‚enje kraja ulaza. Po dogovoru, funkcija getchar vra†a u program vrednost koja ne odgovara nijednom znaku iz va‚e†eg skupa znakova im nai”e na kraj ulaza. Na taj nain programi mogu odrediti kada su stigli do kraja ulaznih podataka. Jedina ote‚avaju†a okolnost je ta „to postoje dva dogovora oko toga „ta predstavlja kraj datoteke. Mi smo odlo‚ili izbor uvode†i simboliku konstantu EOF umesto konkretne vrednosti, koja god ona bila. U praksi, EOF †e biti ili -1 ili 0, tako da †e program ispravno raditi ako se na njegovom poetku navede jedna od slede†e dve simbolike definicije : #define EOF -1 ili #define EOF 0 Koriste†i simboliku konstantu EOF za predstavljanje vrednosti koja se pojavljuje kada program nai”e na kraj ulaznih podataka, osigurali smo se da samo jedna stvar u programu zavisi od konkretne numerike vrednosti: to je simbolika definicija simbolike konstante EOF. Tako”e, c je u programu deklarisano kao int tip promenljive, a ne char tip, tako da mo‚e uvati vrednost koju funkcija getchar vra†a u program. Kao „to †emo videti u Poglavlju 2, ova vrednost je zapravo int tipa jer, pored svih mogu†ih znakova, ona mora predstavljati i EOF koji je int tipa. Program za kopiranje ulaza na izlaz bi iskusniji C programeri mogli napisati u mnogo sa‚etijem obliku. U C-u, bilo kakvo dodeljivanje vrednosti promenljivoj, kao na primer c = getchar(); mo‚e se upotrebiti u nekom izrazu. Tamo †e se vrednost izraza dodeljivanja jednostavno pridru‚iti promenljivoj na levoj strani, a zatim dalje operisati sa tom promenljivom. Ako se dodeljivanje vrednosti proitanog znaka promenljivoj c stavi u uslov while petlje, program za kopiranje ulaza u izlaz mo‚e biti napisan kao #include /* kopiranje ulaza na izlaz; druga verzija */ main() { int c; while( (c = getchar()) != EOF) putchar(c); } Program ita znak sa ulaza, dodeljuje njegovu vrednost promenljivoj c, a zatim proverava da li je to znak za kraj datoteke. Ako nije, telo while petlje se izvr„ava „tampaju†i znak na izlazu. Nakon toga se uslov while petlje ponovo proverava. Kada se konano do”e do znaka za kraj datoteke petlja while se okonava, a tako”e i funkcija main. Ova verzija centralizuje ulaz - sada postoji samo jedan poziv funkcije getchar - i sa‚ima program. Umetanje dodeljivanja vrednosti promenljivoj u neko testiranje je jedno od mesta gde C omogu†ava korisno sa‚imanje programa. Mogu†e je i dalje sa‚imanje programa, ali bi to rezultiralo neitljivom i nerazumljivom konstrukcijom, „to smo ‚eleli da izbegnemo. Veoma je va‚no primetiti da su zagrade ( i ) koje obuhvataju izraz dodeljivanja vrednosti promenljivoj c, zaista neophodne. Prioritet operatora != je vi„i od onog koji ima operator =, „to znai da bi se u odsustvu zagrada dogodilo slede†e: 1ų prvo bi bio testiran uslov "da li je funkcija getchar vratila u program znak EOF za kraj ulaza". Rezultat tog pore”enja bio bi 0 ili 1, zavisno od toga da li je pore”enje netano ili tano. 2ų Vrednost 0 ili 1 bi se operatorom = dodelila promenljivoj c, „to bi imalo ne‚eljen efekat. Dakle, iskaz c = getchar() != EOF; je ekvivalentan iskazu c = ( getchar() != EOF); ž Ve‚ba 1 - 6 Napi„ite program za „tampanje vrednosti EOF . 1.5.2 Brojanje znakova Slede†i program broji znake; to je mala razrada programa za kopiranje #include /* brojanje znakova na ulazu; prva verzija */ main() { long nc; nc = 0; while ( getchar() != EOF) ++nc; printf("%ld \n", nc); } Iskaz ++nc; uvodi novi operator, ++, koji znai pove†aj za jedan. Vi mo‚ete pisati i nc = nc + 1 , ali je ++nc mnogo sa‚etije i esto efikasnije. Postoji odgovaraju†i operator -- za smanjivanje za jedan. Operatori ++ i -- mogu biti ili prefiks operatori (++nc), ili sufiks operatori (nc++). Ove dve varijante pisanja imaju razliito znaenje u izrazima, kao „to †emo videti u Poglavlju 2. Za trenutak †emo se dr‚ati prefiks varijante. Program za brojanje znakova ima svoj broja znakova u vidu promenljive long tipa, umesto tipa int. Najve†i broj koji se mo‚e prikazati promenljivom int tipa je naje„†e 32768 (int je obino dug dva bajta). To znai da bi bio dovoljan ulaz relativno male du‚ine da do”e do prekoraenja brojaa ako je deklarisan kao int; po„to promenljiva tipa long naje„†e zauzima etiri bajta, mo‚e se zakljuiti da je u ovom sluaju njena primena adekvatna. Navedena specifikacija %ld naznaava funkciji printf da joj je odgovaraju†i argument nc ve†a celobrojna vrednost tipa long. Za operisanje sa jo„ ve†im brojevima, mo‚ete koristiti promenljive tipa double (realan broj dvostruke veliine). Mi †emo tako”e koristiti for iskaz umesto while, kako bi prikazali alternativni nain za pisanje petlje. #include /* brojanje znakova na ulazu; druga verzija */ main() { double nc; for (nc = 0; getchar() != EOF; ++nc) ; printf("%.0f \n", nc); } Funkcija printf koristi istu specifikaciju %f i za float i za double tip promenljive; ovde %.0f odbacuje „tampanje nepostoje†eg dela iza decimalne take. Telo for petlje je u ovom sluaju prazno, stoga „to se ceo posao obavlja u delu za testiranje uslova i u delu za reinicijalizaciju. Me”utim, gramatika pravila C-a nala‚u da for petlja mora imati telo. Izolovan znak ;, praktino nulti (nepostoje†i) iskaz, je tu da zadovolji ta pravila. Stavili smo ga u zasebnu liniju da bismo ga uinili vidljivijim. Pre nego „to napustimo program za brojanje znakova, primetite da ako na ulazu nema nijednog znaka, uslovi u while i for petljama nisu zadovoljeni ve† pri prvom pozivu funkcije getchar, tako da program kao rezultat vra†a nulu, „to je ispravan odgovor. Jedna od dobrih stvari u vezi sa while i for petljama je ta da se uslov testira na poetku petlje, pre nego „to se u”e u telo petlje. Ako nema „ta da se radi (uslov nije zadovoljen), ni„ta ne†e ni biti ura”eno, ak i ako to znai da se nijedanput ne†e pro†i kroz telo petlje. Programi bi trebalo da se pona„aju inteligentno kad barataju sa ulazima bez znakova. Iskazi while i for poma‚u tako „to rade razumne operacije u ekstremnim situacijama. 1.5.3 Brojanje linija Slede†i program broji koliko linija ini ulaz. Pretpostavlja se da su linije me”usobno odvojene znakom \n za novi red koji je u dosada„njem tekstu striktno dodavan svakoj liniji koja je trebalo da se od„tampa. Znai, brojanje linija se svodi na brojanje znakova za novi red. #include /* brojanje linija na ulazu */ main() { int c, nl; nl = 0; while ( (c = getchar()) != EOF) if (c == '\n') ++nl; printf("%d \n", nl); } Telo while petlje se sada sastoji od if iskaza, koji kontroli„e pove†avanje brojaa nl za jedan. Iskaz if ispituje uslov koji sledi u zagradama i, ako je taan, izvr„ava iskaz (ili grupu iskaza navedenih unutar vitiastih zagrada) koji ini telo if konstrukcije. Jo„ jednom smo hteli da istaknemo „ta se ime kontroli„e. Dvostruki znak jednakosti == je C oznaka za "je jednako". Ovaj simbol je uveden da razgranii test jednakosti od dodeljivanja vrednosti promenljivoj, koje se vr„i operatorom =. Po„to je dodeljivanje vrednosti promenljivoj otprilike dvaput e„†e od testiranja jednakosti u tipinim C programima, razumljivo je da je i operator dodeljivanja upola kra†i. Bilo koji znak napisan izme”u jednostrukih navodnika tretira se kao celobrojna vrednost koja odgovara tom znaku. Koja †e to vrednost biti zavisi od toga koji je skup znakova ugra”en u raunar. Tako napisan znak se naziva znakovna konstanta. Tako je, na primer, 'A' znakovna konstanta; u ASCII setu znakova je njena odgovaraju†a vrednost 65, „to je interna reprezentacija znaka A. Naravno da je bolje pisati 'A' nego 65; njeno znaenje je oigledno, i ne zavisi od drugaijeg seta znakova. Eskejp sekvence kori„†ene u nizovima znakova se tako”e mogu napisati u obliku znakovnih konstanti, pa tako '\n' predstavlja vrednost koja odgovara znaku za novi red (u ASCII je to 10). Naroito obratite pa‚nju na injenicu da je '\n' jedan znak i da se u izrazima tretira kao ceo broj. Sa druge strane, "\n" je niz znakova koji u ovom sluaju sadr‚i samo jedan znak. Nizovi i znakovi bi†e predmet razmatranja u Poglavlju 2. ž Ve‚ba 1 - 7 Napi„ite program koji broji blanko znakove, tabulatore i znake za novi red. ž Ve‚ba 1 - 8 Napi„ite program koji kopira ulaz na izlaz, i pri tome zamenjuje eventualni niz blanko znakova samo jednim blanko znakom. ž Ve‚ba 1 - 9 Napi„ite program koji zamenjuje svaki tabulator nizom od tri znaka: znakom >, backspace znakom i znakom - .Takva kombinacija ova tri znaka da†e na izlazu -> .Neka program zamenjuje i svaki backspace znak slinim nizom <- .To †e backspace i tab znake uiniti vidljivim. 1.5.4 Brojanje rei šetvrti u na„oj seriji korisnih programa broji linije, rei i znake, usvajaju†i definiciju po kojoj je re bilo koja grupa znakova koja ne sadr‚i blanko znak, tabulator i znak za novi red. (Ovo je ogoljena verzija UNIX rutine wc). #include #define YES 1 /* jeste re */ #define NO 0 /* nije re */ /* broji linije, rei i znake ulaza */ main() { int c, nl, nw, nc, state; state = OUT; nl = nw = nc = 0; while ( (c = getchar()) != EOF) { ++nc; if (c == '\n') ++nl; if (c == ' ' || c == '\n' || c == '\t') state = NO; else if (state == NO) { state = YES; ++nw; } } printf("%d %d %d \n", nl, nw, nc); } Svaki put kada program nai”e na znak, on uve†a broja znakova. Promenljiva state bele‚i da li je program trenutno unutar neke rei ili nije. Poetno stanje je "nije re", i promenljivoj state se dodeljuje vrednost simbolike konstante NO. Bolje je koristiti simbolike konstante YES i NO nego konkretne vrednosti 0 i 1, jer je sa simbolikim konstantama program itljiviji. Naravno da u si†u„nom programu kakav je ovaj to pravi malu razliku, ali u ve†im programima preglednost programa je od velike koristi. Tako”e †ete otkriti da je mnogo lak„e vr„iti obimne ispravke u programima koji su pisani koriste†i simbolike definicije. U takvim programima je, umesto da svuda tra‚ite i ispravljate vrednost neke promenljive, dovoljno ispraviti simboliku definiciju i na svim mestima gde se koristi ta definicija bi†e unete ispravke. Linija nl = nw = nc = 0; postavlja sve promenljive na nulu. Ovo nije poseban sluaj, ve† posledica injenice da i izraz dodeljivanja, ovde nc = 0, ima svoju vrednost (ovde je to nula). Uz to, dodeljivanja se vr„e sa desna na levo, pa bi bilo identino da smo pisali nl = (nw = (nc = 0)); Operator || znai OR (ili) , pa linija if (c == ' ' || c == '\n' || c == '\t') znai "ako je c blanko znak ili je znak za novi red ili je tabulator ..." . (Eskejp sekvenca \t je nain da se tabulator predstavi u vidljivoj formi). Postoji i odgovaraju†i operator za logiko AND (i) i on se oznaava sa &&. Prioritet operatora && je ve†i od prioriteta operatora ||. Izrazi povezani me”usobno sa operatorima && i || se izraunavaju sleva nadesno, a izraunavanje prestaje im je istinitost ili neistinitost poznata. Na taj nain, ako promenljiva c sadr‚i blanko znak, itava konstrukcija je istinita (jer je dovoljno da je zadovoljen jedan od tri uslova vezanih logikim "ili"), pa nema potrebe da se ispituju i druga dva uslova. Ti uslovi se tada ne ispituju. Ova injenica nije od neke posebne va‚nosti ovde, ali je, kao „to †emo uskoro videti, veoma znaajna kod komplikovanijih izraza. Primer tako”e uvodi else iskaz, koji defini„e „ta treba preduzeti u sluaju da uslov koji ispituje if nije zadovoljen. Op„ti oblik ovakve konstrukcije je if ( izraz ) iskaz 1 else iskaz 2 Jedan i samo jedan od dva iskaza pridru‚ena if - else konstrukciji †e biti izvr„en. Ako je izraz taan (istinit), izvr„ava se iskaz 1 ; ako nije, izvr„ava se iskaz 2. Svaki iskaz mo‚e biti prilino komplikovan. U programu za brojanje rei, iskaz posle else je itava if konstrukcija koja kontroli„e dva iskaza unutar vitiastih zagrada. ž Ve‚ba 1 - 10 Izmenite program za brojanje rei koriste†i bolju definiciju " rei ". Na primer, re je niz slova, brojeva i interpunkcije koji poinje slovom. 1.6 POLJA Napi„imo program koji kontroli„e broj pojavljivanja svake cifre, specijalnih znakova (blanko znak, tabulator i znak za novi red) i svih ostalih znakova. Ovo je ve„taki napravljen primer, ali nam omogu†ava da prika‚emo nekoliko mogu†nosti jezika C. U ovom primeru mo‚e se pojaviti dvanaest razliitih ulaza (deset cifara, specijalni znak, ostali znakovi), pa je pogodno upotrebiti jedno polje za uvanje broja pojavljivanja svake cifre umesto deset zasebnih promenljivih. Evo jedne verzije programa : #include /* broji cifre, specijalne i druge znake */ main() { int c, i, nwhite, nother; int ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; ++i) ndigit[i] = 0; while ( (c = getchar()) != EOF) { if (c >= '0' && c <= '9') ++ndigit[c - '0']; else if (c == ' ' || c == '\n' || c == '\t') ++nwhite; else ++nother; } printf("Digits = "); for (i = 0; i < 10; ++i) printf(" %d", ndigit[i]); printf("\n special = %d, other = %d \n", nwhite, nother); } Deklaracija int ndigit[10]; deklari„e ndigit kao polje od deset celobrojnih elemenata. Oznake za elemente polja (indeksi) uvek poinju od nule, tako da polje ndigit ima elemente ndigit[0], ndigit[1], ... , ndigit[9]. Bitno je imati ovo na umu kod pisanja for petlji. Indeks mo‚e biti bilo koji celobrojni izraz, celobrojna vrednost (kao, na primer, i) ili celobrojna konstanta. Ovaj program se zasniva na znakovnom predstavljanju cifara. Na primer, test if (c >= '0' && c <= '9') ... odre”uje da li je znak u promenljivoj c cifra. Ako jeste, brojna vrednost te cifre je c - '0'. Na primer, u ASCII setu znakova brojna vrednost cifre 0 iznosi 48. Ako je u promenljivoj c znak ija je brojna vrednost 52, onda je njegova brojna vrednost c - '0' = 52 - 48 = 4, a to je cifra 4. Ovo funkcioni„e ako su '0', '1', itd. pozitivne rastu†e vrednosti i ako sem cifara nema nikakvih drugih znakova izme”u '0' i '9'. Sre†om, ovo va‚i za sve uobiajene setove znakova. Po definiciji, ako u izrazu uestvuju promenljive tipa char i int, pre izraunavanja se sve konvertuje u int tip. To †e re†i da su promenljive i konstante char tipa u su„tini identine int tipu, gledano u kontekstu aritmetike. To je sasvim prirodno i odgovara nam; na primer, c - '0' je celobrojni izraz ija je vrednost izme”u 0 i 9 (zavisno od toga koji je znak od '0' do '9' sme„ten u promenljivoj c), a to je pogodno iskori„†eno za indekse polja ndigit koji tako”e idu od 0 do 9. Ispitivanje da li je znak u promenljivoj c cifra, specijalni znak ili neki od preostalih znakova, je izvedeno u delu if (c >= '0' && c <= '9') ++ndigit[c -'0']; else if (c == ' ' || c == '\n' || c == '\t') ++nwhite; else ++nother; Konstrukcija if (uslov 1) iskaz 1 else if (uslov 2) iskaz 2 else iskaz 3 se esto pojavljuje da bi iskazala slo‚eno odluivanje. Izvr„ava se tako „to se linije itaju od vrha sve dok neki uslov ne bude zadovoljen. Kada se to dogodi, izvr„i†e se njemu odgovaraju†i iskaz, i cela konstrukcija se napu„ta. Naravno, i ovde iskaz mo‚e biti jedan iskaz ili grupa iskaza u vitiastim zagradama. Ako nijedan od uslova nije zadovoljen, iskaz koji stoji posle poslednjeg else u konstrukciji bi†e izvr„en ukoliko postoji. Ako poslednje else i njemu odgovaraju†i iskaz nisu navedeni (kao „to je to sluaj sa programom za brojanje rei), ne†e se preduzeti nikakva akcija. U konstrukciji mo‚e uestvovati proizvoljan broj else if (uslov) iskaz elemenata izme”u poetnog if i poslednjeg else . Opet kao pitanje stila, preporuljivo je da se if - else konstrukcije pi„u na nain na koji smo mi to uradili, da dugake odluke ne bi prekoraile desnu ivicu strane. Iskaz switch, o kome †e biti rei u Poglavlju 3, obezbe”uje drugi nain pisanja slo‚enih odluka : on je posebno pogodan za ispitivanja tipa "da li se vrednost nekog celog broja ili znaka poklapa sa nekom iz skupa konstanti". Nasuprot primeru iz ovog odeljka, u Poglavlju 3 prikaza†emo verziju programa koja koristi switch iskaz. ž Ve‚ba 1 - 11 Napi„ite program koji „tampa histogram du‚ine rei na ulazu. Najlak„e je napraviti horizontalni histogram; vertikalni histogram je mnogo ve†i izazov. 1.7 FUNKCIJE U C-u, funkcija je ekvivalentna funkciji u Fortranu ili proceduri u Paskalu. Funkcija obezbe”uje pogodan nain da se neka izraunavanja obave u tzv. crnoj kutiji, i da se kasnije koriste bez znanja kako su nastala. Funkcije su zaista jedini nain da se iza”e na kraj sa eventualnom slo‚eno„†u dugakih programa. Sa pravilno oblikovanim funkcijama, mogu†e je ne znati kako je posao obavljen; znanje o tome „ta je ura”eno je dovoljno. C je napravljen tako da je kori„†enje funkcija lako, elegantno i efikasno; esto †ete vi”ati funkciju svega par linija dugaku i pozvanu samo jedanput, ali koja razja„njava deo programa. Do sada smo koristili samo funkcije kao printf, getchar i putchar koje su nam bile obezbe”ene; do„lo je vreme da napi„emo i sami nekoliko. Po„to u C-u ne postoji operator stepenovanja ** kakav ima, recimo, Fortran, prikaimo postupak kreiranja funkcije pi„u†i funkciju power. Funkcija power(m, n) stepenuje ceo broj m na potenciju n koja je pozitivan ceo broj. Tako je, na primer, vrednost power(2, 5) jednaka 32. Funkcija power, istini za volju, nije „iroko primenljiva po„to barata samo sa malim pozitivnim potencijama malih celih brojeva, ali je najbolje re„avati problem korak po korak. Evo funkcije power i glavnog dela programa koji je poziva, tako da pred sobom imate celu strukturu. #include int power(int m, int n); /* deklaracija funkcije */ main() { int i; for (i = 0; i < 10; ++i) printf("%d %d %d \n", i, power(2, i), power(-3, i)); return 0; } /* power: di‚e osnovu na n - ti stepen; n > 0 */ int power(int base, int n) /* definicija funkcije */ { int i, p; p = 1; for (i = 1; i <= n; ++i) p = p * base; return p; } Svaka funkcija ima isti oblik: ime funkcije(deklaracije parametara, ukoliko oni postoje) { deklaracije iskazi } Funkcije se mogu pojavljivati u bilo kom redosledu, i to u jednoj ili vi„e datoteka. Naravno, ako izvorni program ini vi„e datoteka, potrebno je mnogo vi„e kompajliranja i uitavanja nego da je sve sme„teno u jednoj datoteci. Me”utim, to je stvar operativnog sistema, a ne stvar jezika. Za trenutak, pretpostavi†emo da su obe funkcije u istoj datoteci, tako da sve „to ste nauili o startovanju C programa i dalje va‚i. Funkcija power je pozvana dva puta, u liniji printf("%d %d %d \n", i, power(2, i), power(-3, i)); Svaki poziv prosle”uje dva argumenta funkciji power, koja svaki put vra†a u program ceo broj koji treba da se „tampa. U nekom izrazu, power(2, i) je ceo broj, ba„ kao „to su to i argumenti 2 i i. Ne vra†aju sve funkcije celobrojnu vrednost u program; time †emo se pozabaviti u Poglavlju 4. U definiciji funkcije power, int power(int base, int n) odre”eni su tipovi i imena parametara, kao i tip rezultata koji funkcija vra†a u program. Mi †emo koristiti naziv parametar za promenljivu koja je navedena na listi prilikom definisanja funkcije, a za promenljivu ija se vrednost koristi pri pozivu funkcije koristi†emo naziv argument. Sa druge strane, deklaracija int power(int m, int n); ka‚e da je power funkcija koja oekuje dva int argumenta, a vra†a u program vrednost int tipa. Ova deklaracija, koja se zove prototip funkcije, mora da se slo‚i sa definicijom funkcije. Gre„ka je ako se definicija funkcije ili neka njena upotreba ne slo‚i sa deklaracijom. Nije neophodno da se imena parametara u definiciji i u prototipu sla‚u; u stvari, imena parametara u prototipu funkcije su proizvoljna, pa smo mogli pisati int power(int, int); Imena koja koristi funkcija power za svoje argumente su potpuno lokalna i nisu vidljiva za bilo koju drugu funkciju: druge funkcije mogu bez problema imati ista imena. To va‚i i za promenljive i i p; promenljiva i u funkciji power nije ni u kakvoj vezi sa promenljivom i koju koristi funkcija main. Vrednost koju funkcija power izrauna je vra†ena u program iskazom return. Iskaz return mo‚e vratiti bilo kakav izraz naveden u zagradama ( i ). Funkcija ne mora da vra†a neku vrednost u program; return iskaz naveden bez izraza samo vra†a kontrolu programu koji je pozvao tu funkciju. Isto bi se desilo i da se do„lo do kraja tela (a to je desna vitiasta zagrada) pozvane funkcije. Uglavnom, konstrukcija return 0 podrazumeva normalni zavr„etak. ž Ve‚ba 1 -12 Napi„ite program koji konvertuje ulaz u mala slova, koriste†i funkciju lower(c) koja vra†a c ako c nije slovo, odnosno vrednost koja odgovara malom slovu, ako je c veliko slovo. 1.8 ARGUMENTI - POZIV POMOU VREDNOSTI Jedna karakteristika C funkcija mo‚e biti veoma udna programerima koji poznaju neke druge jezike, posebno Fortran. U C-u funkcije, umesto da me”usobno komuniciraju svojim argumentima, komuniciraju njihovim vrednostima. To znai da funkcija koja je pozvana uva vrednosti svojih argumenata u privremenim promenljivama (tehniki, na steku) umesto u originalima (t.j. na njihovim adresama). To vodi nekim drugim osobinama od onih koje su poznate u Fortranu ili Paskalu, u kojima pozvana funkcija sme„ta izraunate vrednosti na adresu originalnog argumenta t.j. barata sa argumentom, a ne sa njegovom vredno„†u. Osnovna razlika izme”u jezika je ta da u C-u pozvana funkcija ne mo‚e da izmeni vrednost promenljive u funkciji iz koje je pozvana; ona mo‚e menjati jedino njenu privremenu kopiju. Pozivanje pomo†u vrednosti je prednost, nikako mana. Ono obino vodi sa‚etijim programima sa manje razliitih promenljivih, jer se argumenti mogu tretirati kao pogodno obele‚ene lokalne promenljive u pozvanoj funkciji. Na primer, evo verzije funkcije power koja koristi ovu osobinu: /* power: di‚e osnovu na n - ti stepen; druga verzija */ int power(int base, int n) { int p; for(p = 1; n > 0; --n) p = p * base; return p; } Argument n je upotrebljen kao privremena promenljiva koja se smanjuje dok ne do”e do nule; nema vi„e potrebe za promenljivom i. Žta god da se uini sa promenljivom n unutar funkcije power nema uticaja na argument sa kojim je funkcija power pozvana. Kada je to potrebno, mogu†e je obezbediti da pozvana funkcija menja promenljivu u funkciji iz koje je pozvana. Tada pozvana funkcija mora da zna adresu originalnog argumenta, a to mora da obezbedi funkcija iz koje je pozvana. Tehniki, to se izvodi pomo†u pokazivaa koji pokazuje na adresu argumenta. Pozvana funkcija tako”e treba da deklari„e pokaziva i tako preko njega pristupi promenljivoj u funkciji iz koje je pozvana. Ovo †emo objasniti do detalja u Poglavlju 5. Sa poljima je stvar sasvim drugaija. Kada se polje pojavi kao argument, nema stvaranja privremenih kopija; pozvanoj funkciji se prosle”uje lokacija (adresa) poetnog elementa tog polja. Tako pozvana funkcija mo‚e da pristupi bilo kom elementu polja i da ga izmeni. To je tema slede†eg odeljka. 1.9 POLJA ZNAKOVA Verovatno naje„†i tip polja u C-u je polje znakova. Da bismo prikazali upotrebu polja znakova i funkcija koje njima manipuli„u, napi„imo program koji ita skup linija i „tampa najdu‚u od njih. Algoritam je dovoljno jednostavan : while(ima jo„ linija) if (linija je du‚a od dosad najdu‚e) upamti tu liniju i njenu du‚inu „tampaj najdu‚u liniju Ovaj algoritam jasno pokazuje da se program prirodno deli na vi„e delova. Jedan deo ita liniju, drugi je ispituje, tre†i pamti i ostatak upravlja procesom. Po„to su stvari tako dobro podeljene, bilo bi dobro da ih tako i napi„emo. U skladu sa tim, najpre napi„imo odvojeno funkciju getline koja uzima slede†u liniju sa ulaza; ona je uop„tenje funkcije getchar. Da bismo funkciju uinili upotrebljivom i u drugim situacijama, poku„a†emo da je uinimo fleksibilnom „to je mogu†e vi„e. Najmanje „to funkcija getline mora da radi je da vrati u program signal o kraju skupa linija sa ulaza; mnogo korisnije bi bilo napisati je tako da u program vra†a du‚inu linije sa ulaza, ili nulu ako je do„lo do kraja skupa linija na ulazu. Nula ne mo‚e biti smatrana podatkom o du‚ini linije po„to svaka linija mora imati bar jedan znak; ak i linija koja ima samo znak za novi red je du‚ine jedan. Kada otkrijemo liniju du‚u od prethodne najdu‚e, ona se mora sauvati negde. To nas upu†uje na slede†u funkciju, copy, koja sprema novu najdu‚u liniju na sigurno mesto. Konano, potreban nam je glavni program koji †e upravljati funkcijama getline i copy. Evo rezultata. #include #define MAXLINE 1000 /* max du‚ina linije */ int getline(char line[], int maxline); void copy(char to[], char from[]); /* „tampanje najdu‚e linije sa ulaza */ main() { int len; /* du‚ina teku†e linije */ int max; /* do sada najve†a du‚ina linije */ char line[MAXLINE]; /* teku†a linija */ char longest[MAXLINE]; /* do sada najdu‚a linija */ max = 0; while( (len = getline(line, MAXLINE)) > 0) if (len > max) { max = len; copy(longest, line); } if (max > 0) /* ima jo„ linija na ulazu */ printf("%s", longest); return 0; } /* getline: uitava liniju u polje s, vra†a njenu du‚inu */ int getline(char s[], int lim) { int c, i; for (i = 0;i #define MAXLINE 1000 /* max dozvoljena du‚ina linije */ int max; /* do sada najve†a du‚ina linije */ char line[MAXLINE]; /* teku†a linija */ char longest[MAXLINE]; /* do sada najdu‚a linija */ int getline(void); void copy(void); /* „tampa najdu‚u liniju; specijalna verzija */ main() { int len; extern int max; extern char longest[]; max = 0; while ( (len = getline()) > 0) if (len > max) { max = len; copy(); } if (max > 0) /* ima linija */ printf("%s", longest); return 0; } /* getline: specijalna verzija */ int getline(void) { int c, i; extern char line[]; for (i = 0;i i sadr‚e u sebi simbolike konstante za sve te veliine, zajedno sa ostalim osobinama raunara i kompajlera. O tome vi„e u dodatku B. 2.3 KONSTANTE Celobrojna konstanta, kao npr. 1234, je tipa int. Konstanta tipa long se pi„e sa l ili L na kraju, kao npr. 123456789L; ceo broj koji je suvi„e veliki da bi se prikazao int tipom konstante bi†e preveden u long tip. Konstante tipa unsigned pi„u se sa u ili U na kraju, a sufiks ul ili UL oznaava konstantu tipa unsigned long. Konstante u obliku realnih brojeva sadr‚e decimalnu taku (123.4) ili eksponent (12e-3, 12E-3) ili i jedno i drugo; njihov tip double, osim ako nemaju sufiks na kraju. Sufiksi f ili F oznaavaju float konstantu; sufiksi l ili L oznaavaju long double konstantu. Postoji notacija za oktalne i heksadecimalne brojeve. Nula (0) na poetku int konstante znai da je broj predstavljen u oktalnom sistemu brojeva, a 0x ili 0X da je re o heksadecimalnom broju. Na primer, broj 31 †e biti predstavljen kao 037 u oktalnom, i kao 0x1f ili 0X1F u heksadecimalnom sistemu brojeva. Heksadecimalne i oktalne konstante se tako”e mogu izraziti u long formi ako iza njih sledi slovo L, ili u unsigned formi ako iza njih stoji U: 0xFUL je unsigned long heksadecimalna konstanta koja odgovara decimalnoj vrednosti 15. Znakovna konstanta je znak naveden izme”u jednostrukih navodnika, na primer 'x'. Svakom znaku odgovara jedna numerika vrednost, a koja †e to biti zavisi od toga koji set znakova raunar koristi. Na primer, u ASCII setu znakova, znak nula t.j. '0' ima svoju odgovaraju†u vrednost 48, dok u EBCDIC skupu znaku '0' odgovara vrednost 240; vrednosti u oba skupa oito nemaju veze sa brojnom vredno„†u nula. Pi„u†i '0' umesto konkretnih vrednosti kakve su 48 ili 240, inimo program nezavisnim od seta karaktera koji je primenjen na svakom pojedinanom raunaru. Znakovne konstante se tretiraju u izraunavanjima kao i bilo koji drugi brojevi, iako se najvi„e koriste u relacijama pore”enja sa drugim znakovima. Slede†i odeljak se bavi pravilima konverzije. Odre”eni nevidljivi znakovi mogu biti predstavljeni kao znakovne konstante pomo†u tzv. eskejp sekvenci kao „to su \n (znak za novi red), \t (tabulator), \0 (nulti znak), \\ (obrnuta kosa crta), \' (jednostruki navodnik) itd. Ovako napisani izgledaju kao dva znaka, ali je to u su„tini samo jedan znak. Uz to, mogu†e je stvoriti proizvoljan element veliine jednog bajta pi„u†i '\ooo' gde je ooo jedna do tri oktalne cifre (0...7),ili kao '\xhh' gde je hh jedna ili vi„e heksadecimalnih cifara (0...9, a..f,A...F). Tako mo‚emo pisati #define FORMFEED '\014' /* ASCII form feed */ ili u heksadecimalnom kodu #define FORMFEED '\xE' /* ASCII form feed */ Kompletan set eskejp sekvenci je \a znak za zvuni signal \\ obrnuta kosa crta \b povratnik \? znak pitanja \f form feed \' jednostruki navodnik \n novi red \" dvostruki navodnik \r carriage return \ooo oktalni broj \t horizontalni tabulator \xhh heksadecimalni broj \v vertikalni tabulator Znakovna konstanta '\0' predstavlja znak ija je odgovaraju†a numerika vrednost nula. šesto pi„emo '0' umesto 0 da bi naglasili znakovnu prirodu nekog izraza. Konstantni izraz je izraz u kome figuri„u samo konstante. Takvi izrazi se mogu izraunati jo„ za vreme kompajliranja umesto da se raunaju u toku izvr„avanja programa. U skladu sa svojom prirodom, mogu se pojaviti na bilo kom mestu u programu gde to mo‚e i konstanta. Na primer, kao u #define MAXLINE 1000 char line[MAXLINE+1]; ili #define LEAP 1 /* u prestupnim godinama */ int dani[31+28+LEAP+31+30+31+30+31+31+30+31+30+31]; Niz znakova ili string je niz od nula ili vi„e znakova naveden unutar dvostrukih navodnika, kao "I am a string " ili kao "" /* string du‚ine nula */ Dvostruki navodnici nisu deo niza, ve† su tu da bi ga ograniili. Iste eskejp sekvence kori„†ene za znakovne konstante primenjuju se i na stringove: \" predstavlja znak dvostruki navodnik. Nizovi znakova se mogu povezati za vreme kompajliranja "hello," "world" je isto „to i "hello,world" Ovo je korisno kod dugih nizova jer se mogu podeliti u vi„e osnovnih linija. Tehniki, string je polje iji su elementi pojedinani znakovi. Kompajler prema dogovoru automatski stavlja znak \0 na kraj svakog takvog stringa, kako bi programi mogli da znaju gde se string zavr„ava.Ovakvo predstavljanje znai da nisu postavljena ogranienja koliko string mo‚e biti dug, pa programi moraju da pretra‚e kompletan string da bi odredili njegovu du‚inu. Broj lokacija u memoriji u koje se sme„ta string je ve†i od broja znakova navedenih izme”u dvostrukih navodnika za jedan. Slede†a funkcija strlen(s) vra†a du‚inu stringa s ne ukljuuju†i znak \0. /* strlen: vraca du‚inu stringa s */ int strlen(char s[]) { int i; i = 0; while (s[i] != '\0') ++i; return i; } Ostale funkcije koje operi„u sa nizovima i funkcija strlen su deklarisane u standardnom zaglavlju . Pa‚ljivo razgraniite izme”u znakovne konstante i stringa koji sadr‚i samo jedan karakter: 'x' nije isto „to i "x". Prvo je jedan znak, i koristi se da proizvede numeriku vrednost koja odgovara znaku x iz seta znakova, a drugo je niz znakova koji sadr‚i jedan znak (slovo x) i \0. Postoji i jedna druga vrsta konstanti, tzv. enumerisana konstanta. Enumeracija je formiranje liste konstantnih celobrojnih vrednosti, kao u enum boolean { NO, YES } ; Prvi naziv u enum listi ima vrednost 0, slede†i 1, itd. dokle god se eksplicitno ne zada neka druga vrednost. Ako nisu sve vrednosti u listi zadate, one koje nisu zadate progresivno rastu od poslednje zadate vrednosti, kao u drugom od slede†a dva primera: enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t', NEWLINE = '\n', VTAB = '\v', RETURN = '\r' } ; enum meseci { JAN = 1, FEB, MAR, APR, MAJ, JUN, JUL, AVG, SEP, OKT, NOV, DEC } ; /* FEB je 2, MAR je 3, itd. */ Imena u razliitim enumeracijama moraju se razlikovati. Vrednosti u jednoj enumeraciji se ne moraju razlikovati. Enumeracije obezbe”uju pogodan nain da pridru‚e konstantne vrednosti imenima, kao alternativu za #define, uz prednost da nove vrednosti mogu biti generisane automatski. Kompajleri ne moraju proveravati da li je to „to je sme„teno u promenljivu tipa enum ispravno za enumeraciju. Ipak, enumerisane promenljive pru‚aju mogu†nost provere zbog ega su esto bolje od #define. Uz to, dibager je u mogu†nosti da „tampa vrednosti enumerisanih promenljivih u njihovoj simbolikoj formi. 2.4 DEKLARACIJE Sve promenljive moraju biti deklarisane pre kori„†enja, mada neke deklaracije mogu biti izvedene tako da slede iz konteksta. Deklaracija navodi tip promenljive iza koga sledi lista od jedne ili vi„e promenljivih tog tipa, kao u int lower, upper, step; char c, line[1000]; Promenljive mogu biti raspore”ene po listama u bilo kakvom rasporedu: poslednji primer je mogao biti napisan kao int lower; int upper; char c; int step; char line[1000]; Poslednja forma zauzima mnogo vi„e prostora, ali je veoma pogodna za dodavanje komentara svakoj deklaraciji ili za este izmene programa. Promenljive tako”e mogu biti postavljene na neke vrednosti unutar deklaracija, mada tu postoje izvesna ogranienja. Ako se u deklaraciji iza imena neke promenljive navedu znak jednakosti i konstantni izraz, taj deo †e biti protumaen kao inicijalizator te promenljive, kao u char backslash ='\\'; int i = 0; float eps = 1.0e-5; int limit = MAXLINE + 1; Ako promenljiva nije automatska (nego je extern ili static tipa), inicijalizacija se vr„i samo jednom, obino pre poetka programa, a inicijalizator mora biti konstantni izraz. Eksplicitno inicijalizovane automatske promenljive se inicijalizuju svaki put kada se pozove funkcija u kojoj se nalaze. Inicijalizator mo‚e biti bilo kakav izraz. One automatske promenljive za koje nije eksplicitno navedena poetna vrednost, po aktiviranju funkcije sadr‚a†e nedefinisane, proizvoljne vrednosti. Ako im se eksplicitno ne dodeli neka vrednost, promenljive tipa extern i static ima†e poetnu vrednost nula. Ipak, dobro je i u tom sluaju naglasiti inicijalizaciju. Kvalifikator const mo‚e biti naveden ispred deklaracije bilo koje promenljive da bi naglasio da se ona ne†e menjati. Za polje, na primer, kvalifikator const pokazuje da se njegovi elementi ne†e menjati. const double e = 2.71828182845905; const char msg[] = "pa‚nja: "; Kvalifikator const se mo‚e primeniti i na argumente funkcije, da bi naznaio da ih funkcija ne†e menjati. Kada je argument funkcije polje, onda da ga funkcija ne bi izmenila pi„e se int strlen(const char s[]); Rezultat je jasno odre”en ako do”e do poku„aja promene objekta deklarisanog kao const . 2.5 ARITMETIšKI OPERATORI Binarni (primenjuju se na dva operanda) aritmetiki operatori su +, -, *, /, i modul operator % . Postoji unarni operator -, ali nema unarnog operatora +. Deljenje dva cela broja daje ceo broj i ostatak koji se odbacuje. Izraz x % y †e proizvesti ostatak deljenja vrednosti x vredno„†u y. Ako y deli x tano ceo broj puta, gornji izraz da†e nulu. Modul operator deljenja je upotrebljen u slede†em primeru: godina je prestupna ako je deljiva sa 4 a nije deljiva sa 100, ili ako je deljiva sa 400 . Mo‚e se napisati if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) printf("leap year"); else printf("not a leap year"); Operator % ne mo‚e biti primenjen na vrednosti tipa float ili double. Operatori + i - imaju isti prioritet pri izraunavanju izraza, i on je ni‚i od prioriteta operatora * , / i % , a koji su, opet, ni‚eg prioriteta od unarnog -. Aritmetiki operatori su asocijativni sleva nadesno. Tabela na kraju ovog poglavlja prikazuje prioritet i asocijativnost za sve operatore. Za asocijativne i komutativne operacije kakve su sabiranje i mno‚enje, redosled izraunavanja nije odre”en. Kompajler mo‚e preurediti izraz koji sadr‚i ove operacije. Tako, a + (b + c) mo‚e biti izraunato kao (a +b) + c. Akcija koja se preduzima kada do”e do prekoraenja rezultata u bilo kom pravcu, zavisi od raunara do raunara. 2.6 RELACIONI I LOGIšKI OPERATORI Relacioni operatori su > >= < <= i svi imaju isti prioritet. Odmah ispod njih po prioritetu su operatori jednakosti == != koji imaju isti prioritet. Relacioni operatori imaju ni‚i prioritet od aritmetikih operatora, pa †e izraz kakav je i < lim-1 biti protumaen kao i < (lim-1), „to se i moglo oekivati. Mnogo interesantniji su logiki operatori && i ||. Izrazi povezani operatorima && i || se izraunavaju sleva nadesno, a izraunavanje se zaustavlja im istinitost ili neistinitost rezultata postane poznata. Ova pravila su od kritinog znaaja za pisanje ispravnih programa. Na primer, evo petlje iz funkcije getline koju smo napisali u Poglavlju 1: for (i = 0; i= '0' && s[i] <= '9'; ++i) n = 10 * n + (s[i] - '0'); return n; } Kao „to je pomenuto u Poglavlju 1, izraz s[i] - '0' daje numeriku vrednost znaka sme„tenog u s[i]. Odatle se vidi da je char tip polja s tretiran u izrazu kao int tip da bi se izraunala vrednost promenljive n koja je int tipa. Jo„ jedan primer konverzije char tipa u int tip je funkcija lower koja pretvara velika slova u mala iskljuivo za ASCII set znakova. Ako znak nije veliko slovo, funkcija lower ga vra†a neizmenjenog. /* lower: konverzija velikih u mala slova ; ASCII set */ int lower(int c) { if (c >= 'A' && c <= 'Z') return c + 'a' - 'A'; else return c; } Ova funkcija ispravno radi za ASCII set znakova, jer je kod tog seta fiksno rastojanje izme”u numerike vrednosti malog slova i numerike vrednosti njemu odgovaraju†eg velikog slova. Tako”e, abeceda je neprekidna - izme”u A i Z nema nieg osim slova. Poslednja primedba ne va‚i za EBCDIC set znakova, pa ova funkcija gre„i kod raunara koji imaju ugra”en ovaj set znakova: bi†e konvertovani i znaci koji nisu slova. Standardno zaglavlje , opisano u Dodatku B, defini„e familiju funkcija koje obezbe”uju test i konverziju u zavisnosti od seta znakova. Na primer, funkcija tolower(c) vra†a vrednost malog slova ako je u promenljivoj c veliko slovo. To znai da je ova funkcija neka vrsta zamene za na„u funkciju lower(c). Postoji jedna osetljiva taka u vezi sa konverzijom znakova u cele brojeve. Jezik ne precizira da li promenljiva tipa char sadr‚i predznaenu ili nepredznaenu vrednost. Kada se char tip konvertuje u int tip, ho†e li ikad mo†i da se proizvede negativan ceo broj? Na‚alost, odgovor na ovo pitanje varira od raunara do raunara, odra‚avaju†i razlike u unutra„njoj arhitekturi. Na nekim ma„inama (PDP-11, na primer) †e char promenljiva iji je krajnji levi bit setovan (1) biti konvertovan u negativan ceo broj ('broj sa predznakom'). Na drugim raunarima, char tip se konvertuje u int tip uz postavljanje krajnjeg levog bita na nulu, ine†i dobijeni ceo broj uvek pozitivnim. Definicija C-a garantuje da †e bilo koji znak iz seta znakova koji je ugra”en uvek biti pozitivan, tako da se znakovi u izrazima mogu slobodno tretirati kao pozitivne veliine. Me”utim, proizvoljan niz bitova sme„ten u char promenljivu mo‚e biti tretiran kao negativan broj na jednim, a kao pozitivan broj na drugim raunarima. Naje„†e pojavljivanje ovakve situacije je sluaj kada je vrednost -1 upotrebljena za oznaku kraja datoteke (EOF). Razmotrite slede†e: char c; c = getchar(); if (c == EOF) ... Na ma„ini na kojoj se ne koriste predznaeni brojevi, c je uvek pozitivno jer je char tipa, a EOF je negativan broj. Kao posledica toga, test je uvek neistinit. Da bismo izbegli ovo, bili smo oprezni i svaki put smo koristili promenljivu int tipa kada je trebalo uvati vrednost koju vra†a funkcija getchar. Stvarni razlog za upotrebu int tipa umesto char nije u vezi sa mogu†im predznaavanjem brojeva. Jednostavno je u pitanju to „to funkcija getchar mora vratiti vrednost za sve mogu†e znakove (da bi mogla da ita proizvoljan ulaz) i, uz to, odre”enu EOF vrednost. Kako vrednost EOF ne mo‚e biti predstavljena kao znak, ona mora biti uvana u int promenljivoj. Jo„ jedan oblik automatske konverzije tipa je da relacioni izrazi i logikih izrazi povezani operatorima && i || dobijaju vrednost 1 ako su istiniti (tani), odnosno 0 ako nisu. Odatle dodeljivanje isdigit = c >= '0' && c <= '9'; postavlja promenljivu isdigit na 1 ako je c cifra, odnosno na 0 ako nije. U delovima if, while, for, itd. konstrukcija koji ispituju neki uslov, 'istinito' jednostavno znai 'sve osim nule'. Uzgred, isdigit(c) je funkcija iz biblioteke , i koristi se kada treba ispitati uslov c >= '0' && c <= '9' Implicitne aritmetike konverzije rade uglavnom kako se i oekuje. Op„te uzev„i, ako jedan operator kao + ili * koji se primenjuje na dva operanda ('binarni operator') ima operande razliitog tipa, tada †e operand 'ni‚eg' tipa biti preveden u 'vi„i' tip pre nego „to pone izraunavanje. Rezultat †e biti vi„eg tipa. Tanije, za svaki aritmetiki operator va‚e slede†a pravila: Tipovi char i short se prevode u int tip. Nakon toga, ako je jedan operand tipa long double, i drugi se prevodi u long double tip. Rezultat je tako”e long double tipa. Ako to nije sluaj, a jedan operand je tipa double, i drugi se prevodi u double tip. Rezultat je double tipa. Ako to nije sluaj, a jedan operator je float tipa, i drugi se prevodi u float tip. Rezultat je float tipa. Ako to nije sluaj, a jedan operand je tipa long int, i drugi se prevodi u long int tip. Rezultat je long int tipa. Ako nije nastupio nijedan od prethodnih sluajeva, oba operanda mora da su int tipa, pa †e i rezultat biti int tipa. Primetite da se u nekom izrazu float tipovi ne konvertuju automatski u double tip. Ovo je izmena u odnosu na originalnu definiciju. Glavni razlog za upotrebu float tipa je da bi se sauvao memorijski prostor u velikim poljima ili, „to je re”e, da bi se u„tedelo vreme na raunarima kod kojih je aritmetika sa dvostrukom tano„†u prilino spora. šitava aritmetika realnih brojeva (sve matematike funkcije) je u C-u izvedena u dvostrukoj preciznosti. Konverziona pravila postaju komplikovanija kada se u igri pojave operandi tipa unsigned. Konverzije se odigravaju i kroz dodeljivanja; vrednost na desnoj strani se prevodi u tip koji ima leva strana, „to je i tip rezultata. Znak se pretvara u ceo broj, predznaen ili ne, kako je ranije opisano. Obrnuta transformacija, int tipa u char tip nije problematina: int i; char c; i = c; c = i; vrednost u promenljivoj c ostaje neizmenjena, bez obzira na to rauna li se sa predznakom ili ne. Ako je x promenljiva float tipa, a i promenljiva int tipa, onda oba sluaja, x = i; i i = x; prouzrokuju konverziju; konverzija float tipa u int tip izaziva gubitak dela iza decimalne take. Tip double se konvertuje u float tip ili zaokru‚ivanjem, ili tako „to se gubi decimalni deo (zavisno od primene). Tako”e, long int brojevi se prevode u short int ili u char tipove odbacivanjem krajnje levih bitova. Kako je i argument neke funkcije ustvari izraz, to se konverzije tipova primenjuju i kad se argumenti prosle”uju funkciji; konkretno, char i short tipovi prelaze u int tip, a float tip prelazi u double tip. Eto za„to smo mi deklarisali argumente funkcije kao int i double tipove ak i kad je funkcija pozvana argumentima char i float tipa. Konano, eksplicitne konverzije tipa mogu se primeniti i na itave izraze, operatorom koji se naziva cast. Op„ti oblik je (tip) i z r a z i njime †e i z r a z biti konvertovan u tip prema ve† navedenim pravilima. U su„tini, cast operator se mo‚e shvatiti kao da je i z r a z dodeljen promenljivoj tipa tip, koja se onda dalje koristi umesto cele konstrukcije. Na primer, funkcija sqrt iz standardne biblioteke oekuje argument tipa double, i ako se primeni na neki drugi tip, proizve„†e besmislicu. Tako, ako je n ceo broj, mo‚emo koristiti cast operator za sqrt( (double) n) i pretvoriti n u double tip pre nego „to ga prosledimo funkciji sqrt. Primetite da cast konstrukcija proizvodi ispravnu vrednost n; stvarni sadr‚aj n nije izmenjen. Operator cast ima isti prioritet kao i drugi unarni operatori, „to se vidi iz tabele na kraju poglavlja. Ako su tipovi argumenata deklarisani prototipom funkcije, to deklarisanje prouzrokuje automatsku konverziju svih argumenata prilikom poziva funkcije. Odatle, za dati prototip funkcije sqrt double sqrt(double); †e poziv root2 = sqrt(2); pretvoriti ceo broj 2 u vrednost 2.0 tipa double bez potrebe za operatorom cast. Standardna biblioteka sadr‚i mini model generatora sluajnih brojeva i funkciju za njegovu inicijalizaciju; slede†i primer ilustruje upotrebu operatora cast: unsigned long int next = 1; /* rand: vraca slucajan ceo broj izme”u 0 i 32767 */ int rand(void) { next = next * 1103515245 + 12345; return (unsigned int) (next / 65536) % 32768; } /* srand: postavljanje pocetne vrednosti za rand */ void srand(unsigned int pocetak) { next = pocetak; } ž Ve‚ba 2 - 2 Napi„ite funkciju htoi koja pretvara niz heksadecimalnih brojeva u ekvivalentnu celobrojnu vrednost. Va‚e†e cifre su od 0 do 9, a -f ili A -F. 2.8 OPERATORI UVEAVANJA I UMANJIVANJA Jezik C pru‚a dva neobina operatora za uve†avanje (inkrementiranje) i umanjivanje (dekrementiranje) vrednosti promenljive. Operator uve†avanja ++ uve†ava svoj operand za jedan; operator umanjivanja -- oduzima jedan od svog operanda. šesto smo koristili operator ++ da uve†amo neku promenljivu, kao u if (c == '\n') ++nl; Ono neobino u vezi sa operatorima ++ i -- je to da mogu biti upotrebljeni kao prefiks operatori (ispred promenljive, kao ++n), ili kao sufiks operatori (posle promenljive: n++). U oba sluaja efekat je uve†avanje promenljive n za jedan. Ali, izraz ++n uve†ava promenljivu n pre nego „to se bilo gde upotrebi, dok izraz n++ uve†ava promenljivu n tek nakon „to se negde upotrebi. To znai da, u sluaju da se vrednost promenljive n negde upotrebljava, ne†e samo efekat izraza n++ i ++n biti razliit: bi†e to i promenljiva kojoj je dodeljena vrednost promenljive n. Ako je n = 5, bi†e posle x = n++; (x = 5, n = 6) x = ++n; (x = 6, n = 6) x = n--; (x = 5, n = 4) x = --n; (x = 4, n = 4) Operatori uve†avanja i umanjivanja mogu biti primenjeni iskljuivo na promenljive: izrazi tipa x = (i + j)++ nisu dozvoljeni. U situacijama gde se ne barata sa vredno„†u promenljive, ve† ona slu‚i samo kao broja, kao u if (c == '\n') nl++; sasvim je svejedno da li †ete upotrebiti prefiks ili sufiks varijantu. Me”utim, postoje situacije kada to nije svejedno. Na primer, razmotrimo funkciju squeeze(s, c) koja uklanja sve znakove c iz niza s. /* squeeze : brisanje svih znakova c iz niza s */ void squeeze(char s[], int c) { int i, j; for (i = j = 0; s[i] != '\0'; i++) if (s[i] != c) s[j++] = s[i]; s[j] = '\0'; } Svaki put kada se pojavi znak razliit od c, on se kopira na trenutnu j poziciju, i samo onda se j uve†ava da bi bilo spremno za novi znak. To je potpuno ekvivalentno sa if (s[i] != c) { s[j] = s[i]; j++; } Jo„ jedan primer sline konstrukcije sti‚e iz funkcije getline koju smo napisali u Poglavlju 1. U njoj sada mo‚emo if (c == '\n') { s[j] = c; j++; } zameniti sa kompaktnijim if (c == '\n') s[j++] = c; Kao tre†i primer uzmimo funkciju strcat(s, t) koja nadovezuje niz t na kraj niza s. Funkcija strcat podrazumeva da je u nizu s dovoljno mesta da prihvati kombinaciju. Kao „to smo napisali, funkcija strcat ne vra†a u program nikakvu vrednost; verzija ove funkcije iz standardne biblioteke vra†a u program pokaziva na rezultuju†i niz. /* strcat: nadovezivanje niza t na niz s; s je dovoljno velik */ void strcat(char s[], chart[]) { int i, j; i = j = 0; while (s[i] != '\0') /* na”i kraj niza */ i++; while ( (s[i++] = t[j++]) != '\0') /* kopira t u s */ ; } Kako se svaki znak kopira iz niza t u niz s, to se sufiks ++ dodaje i promenljivoj i i promenljivoj j da bi smo bili sigurni da su na pravoj poziciji za slede†i prolaz kroz petlju. ž Ve‚ba 2 - 3 Napi„ite funkciju any(s1, s2) koja u program vra†a prvu lokaciju u nizu s1 gde se neki znak iz niza s2 pojavljuje, odnosno vra†a 1 ako niz s1 ne sadr‚i nijedan znak koji sadr‚i niz s2. ž Ve‚ba 2 - 4 Napi„ite alternativnu verziju funkcije squeeze(s1, s2) koja bri„e svaki znak niza s1 koji postoji i u nizu s2. 2.9 BIT - OPERATORI C obezbe”uje odre”en broj operatora za manipulaciju bitovima; ovi operatori mogu biti primenjeni iskljuivo na celobrojne operande, dakle, na operande tipa char, short, int i long, bez obzira na to da li su uz to signed ili unsigned tipa. Evo liste bit-operatora: & AND (i) | OR (ili) ^ XOR (iskljuivo ili) << „iftovanje ulevo >> „iftovanje udesno ~ komplement (unarni) Bit-operator & (AND) je binarni operator: primenjuje se na dva operanda i to na svaki par njihovih bitova posebno. Neki bit rezultata bi†e postavljen na 1 samo ako su u odgovaraju†em paru bitova operanada oba bita bila postavljena na 1. Ovaj operator se esto koristi da maskira (postavi na nulu) neku grupu bitova; na primer, c = n & 31; postavlja na nulu sve bitove osim eventualno pet najni‚ih. Broj 31, predstavljen u binarnoj formi, je oblika 0000000000011111 (ako je veliine dva bajta). Koji god da je n broj, bi†e 1 1 0 0 0 1 0 1 1 0 0 1 1 0 1 0 (neko proizvoljno n) AND 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 (broj 31) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 (c = 26) Bit-operator | (OR) se primenjuje na dva operanda na isti nain na koji to ini i AND operator. Neki bit rezultata bi†e postavljen na 1 ako je bar jedan iz odgovaraju†eg para bitova operanada bio postavljen na 1. Ovaj operator se esto koristi da postavi neke bitove na jedinicu: x = x | MASK; postavlja na jedan one bitove u promenljivoj x koji su postavljeni na jedinicu u konstanti MASK. Ako je, recimo, x = 1 a MASK je 165, bi†e 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 (x = 1) OR 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 1 (MASK = 165) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 1 (x | MASK = 165) Bit-operator ^ (XOR) je tako”e binarni operator; u rezultatu setuje bitove na mestima gde operandi imaju razliite bitove, a resetuje na mestima gde su im bitovi isti. Morate razlikovati bit-operatore & i |, od logikih operatora && i ||, koji istinitost izraza izraunavaju sleva nadesno. Na primer, ako je x = 1 i y = 2, onda †e x & y proizvesti vrednost nula, a x && y proizvesti vrednost jedan. Opratori „iftovanja << i >> izvode pomeranje njihovog levog operanda ulevo i udesno za broj bitova odre”en desnim operandom. Tako †e izraz x << 2 „iftovati x za dva bita ulevo, a upra‚njene pozicije popuniti nulama. Ako je x = 8, onda †e biti 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 (x = 8) 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 (x << 2) Žiftovanje udesno †e upra‚njene pozicije nepredznaenih, unsigned brojeva popuniti nulama. Ako je broj predznaen, „iftovanje udesno †e upra‚njena mesta popuniti jedinicama na nekim raunarima, a nulama na nekim drugim. Unarni operator ~ proizvodi binarni komplement nekog celog broja: on pretvara svaki 1-bit u 0-bit i obrnuto. Ovaj operator obino ima primenu kod izraza tipa x & ~077 gde maskira poslednjih „est bitova vrednosti x na nulu. Primetite da je izraz x & ~077 nezavisan od du‚ine i da je stoga u prednosti nad, recimo, izrazom x & ~07700 „to podrazumeva da je x u oba sluaja „esnaestobitna vrednost. Kra†i oblik ne menja ni„ta, po„to je ~077 izraz koji se izraunava jo„ za vreme kompajliranja. Da bismo ilustrovali upotrebu nekih bit - operatora, posmatrajmo funkciju getbits(x, p, n) koja vra†a u program (desno poravnatu) grupu od n bitova poev od pozicije p, neke vrednosti x. Pretpostavili smo da je nulta bit pozicija krajnja desna pozicija, i da su n i p razumno velike pozitivne vrednosti. Na primer, getbits(x, 4, 3) vra†a u program desna tri bita poev od pozicije 4 (a to su bitovi 4, 3 i 2), pomerena sasvim uz desnu stranu tako da su na drugoj, prvoj i nultoj bit poziciji respektivno. /* getbits: vraca desnih n bitova pocev od pozicije p */ unsigned getbits(unsigned x, int p, int n) { return (x >> (p + 1 - n)) & ~(~0 << n) } Levi deo return izraza, x >> (p + 1 - n), pomera ‚eljenu grupu bitova do desne ivice rei (naje„†e: re = dva bajta). Deklari„u†i argument x kao unsigned tip, obezbedili smo da kada se x „iftuje udesno, na upra‚njena do”u nule, a ne eventualno jedinice zbog predznaka. Zbog toga †e program raditi na svim ma„inama. U ovom primeru, za getbits(x, 4, 3),posle „iftovanja udesno, x izgleda ovako: 0 0 0 . . . . . . . . . . bit4 bit3 bit2 gde umesto ...... stoje nule ili jedinice, zavisno od x. Sada je potrebno sve ostale bitove osim desnih n = 3 bita postaviti na nulu. To praktino znai da treba napraviti masku u kojoj †e svi bitovi biti na nuli, osim krajnja desna tri bita koja †e biti na jedinici. Maska predstavlja desni deo return izraza, i ovako je napravljena: 1. Operatorom ~ primenjenim na broj 0 svi bitovi tog broja su postavljeni na jedan: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 (~0) 2. Potom je izvr„eno „iftovanje ulevo za n = 3 pozicije kako bi se na krajnja desna tri mesta pojavile nule: 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 (~0 << n) 3. Ovo je upravo komplement maske koja nam je potrebna, pa se stoga jednostavno ponovo primeni unarni operator ~: 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ~(~0 << n) Posle primene operatora & na vrednost x i upravo kreiranu masku, dobi†e se ‚eljeni efekat: 0 0 0 . . . . . . . . . . bit4 bit3 bit2 (x) & 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ~(~0 << n) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 0 0 0 0 0 0 0 0 0 0 0 0 0 bit4 bit3 bit2 ž Ve‚ba 2 - 5 Izmenite funkciju getbits tako da oznaava bitove sleva nadesno (krajnji levi bit je nulti). ž Ve‚ba 2 - 6 Napi„ite funkciju wordlength() koja du‚inu rei na va„em kompjuteru, tj. broj bitova u int celom broju. ž Ve‚ba 2 - 7 Napi„ite funkciju rightrot(n, b) koja rotira ceo broj n za b bit pozicija udesno (bit koji 'ispadne' sa desne strane upisuje se u upra‚njeno mesto na levoj strani). ž Ve‚ba 2 - 8 Napi„ite funkciju invert(x, p, n) koja invertuje (pretvara jedinice u nule i obrnuto) n bitova broja x poev„i od pozicije p, ostavljaju†i ostale bitove neizmenjene. 2.8 OPERATORI I IZRAZI DODELJIVANJA Izrazi kao „to je i = i + 2; u kojima se izraz na levoj strani ponovljen na desnoj strani mogu biti napisani u skra†enoj formi kao i += 2; koriste†i operator dodeljivanja +=. Ve†ina binarnih operatora (operatori kao „to je + imaju levi i desni operand) imaju odgovaraju†i operator dodeljivanja op=, gde je op neki od slede†ih operatora: + - * / % << >> & ^ | Ako su e1 i e2 izrazi, onda je e1 op= e2; ekvivalentno e1 = (e1) op (e2); osim „to se u prvom sluaju e1 rauna samo jedanput. Primetite zagrade oko e2, jer je x *= y + 1; ustvari x = x * (y + 1); a ne x = x * y + 1; Kao primer, evo funkcije bitcount koja vra†a u program broj bitova postavljenih na jedinicu u nekom celom broju. /* bitcount: broji 1-bitove broja n */ int bitcount(unsigned n) { int b; for (b = 0; n != 0; n >>= 1) if (n & 01) b++; return b; } Osim sa‚etosti, operatori dodeljivanja su u prednosti nad uobiajenim konstrukcijama jer vi„e odgovaraju ljudskom nainu razmi„ljanja. Mi ka‚emo 'dodaj 2 promenljivoj i' ili 'uve†aj vrednost i za 2', a ne 'uzmi promenljivu i, dodaj 2, i rezultat vrati nazad u i'. Otud i += 2. Uz to, kod komplikovanijih izraza kao „to je yyval[yypv[p3 + p4] + yypv[p1 + p2]] += 2; operator dodeljivanja ini program lak„im za itanje. Onaj koji ita ne mora da se mui proveravaju†i da li je izraz na desnoj strani zaista isti kao i onaj na levoj, ili da se udi za„to nije. Operator dodeljivanja mo‚e ak pomo†i kompajleru da napravi efikasniji izvr„ni program. Ve† smo koristili injenicu da izraz dodeljivanja ima svoju vrednost i da se kao takav mo‚e pojaviti u drugim izrazima: naje„†i primer je while ( (c = getchar()) != EOF) ... Izrazi dodeljivanja koji koriste ostale operatore dodeljivanja (+=, -=, itd.) mogu se tako”e pojaviti u drugim izrazima, mada je to re”i sluaj. Tip izraza dodeljivanja je odre”en tipom njegovog levog operanda. 2.11 USLOVNI IZRAZI Konstrukcija if (a > b) z = a; else z = b; sme„ta u promenljivu z ve†u od vrednosti a i b. Uslovni izraz, napisan pomo†u operatora ?: omogu†uje alternativni nain da se napi„u ovakva i sline konstrukcije. U konstrukciji e1 ? e2 : e3 prvo se ispita uslov e1. Ako je istinit (vrednost mu je razliita od nule), onda †e se izraunati izraz e2, a to je i vrednost celog uslovnog izraza. Ako uslov e1 nije istinit, izrauna†e se izraz e3, i to †e biti vrednost celog uslovnog izraza. Izraunava se samo jedan od izraza e2 i e3. Zato, da bi u promenljivu z stavili ve†u od vrednosti a i b, pisa†emo z = (a > b) ? a : b; /* z = max(a, b) */ Treba primetiti da je uslovni izraz zaista izraz, i da se mo‚e koristiti kao i svaki drugi. Ako su izrazi e2 i e3 razliitih tipova, tip rezultata je odre”en pravilima konverzije o kojima je ve† bilo rei. Na primer, ako je f tipa float, a n tipa int, onda †e izraz (n > 0) ? f : n; biti tipa float bez obzira da li je n manje ili ve†e od nule. Zagrade nisu neophodne oko uslovnog dela u uslovnom izrazu, po„to je prioritet operatora ?: veoma nizak, tek iznad prioriteta operatora dodeljivanja. Preporuljivo je, svejedno, da se ipak pi„u jer time ine uslovni deo uoljivijim. Uslovni izrazi esto vode sa‚etijem programu. Na primer, slede†a petlja „tampa N elemenata nekog polja, deset po liniji, u kolonama me”usobno odvojenim jednim blanko znakom i sa znakom za novi red na kraju svake linije (ukljuuju†i i poslednju). for (i = 0; i < N; i++) printf("%6d%c", a[i], (i % 10 == 9 || i == N - 1) ? '\n' : ' '); Znak za novi red se „tampa posle svakog desetog elementa, i posle N-tog. Posle svih ostalih elemenata sledi blanko znak. Iako ovaj primer mo‚da izgleda kao trik, preporuujemo vam da poku„ate da napi„ete ekvivalentnu petlju bez kori„†enja uslovnog izraza. Ve‚ba 2 - 9 Napi„ite ponovo funkciju lower, koja konvertuje velika slova u mala, koriste†i uslovni izraz umesto konstrukcije if - else. 2.12 PRIORITET I REDOSLED IZRAšUNAVANJA Donja tabela sumira sva pravila za odre”ivanje prioriteta i asocijativnosti operatora, ukljuuju†i tu i one operatore o kojima jo„ nismo diskutovali. Operatori u istom redu imaju isti prioritet; svaki red ispod ima ni‚i prioritet: tako, na primer, operatori *, /, i % imaju isti prioritet, koji je vi„i od prioriteta operatora u liniji ispod, + i -. ŚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄæ ³ O p e r a t o r ³ asocijativnost ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ () {} -> . ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ ! ~ ++ -- + - * & tip sizeof ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ * / % ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ + - ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ < <= > >= ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ == != ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ & ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ ^ ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ | ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ && ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ || ³ sleva nadesno ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ ?: ³ zdesna nalevo ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ = += -= *= /= %= &= ^= |= <<= >>= ³ zdesna nalevo ³ ĆÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ“ ³ , ³ sleva nadesno ³ ĄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĮÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄŁ (unarni operatori +, - i * imaju vi„i prioritet od istih binarnih) Operatori -> i . se koriste da bi se pristupilo lanovima neke strukture; bi†e opisani u Poglavlju 6, zajedno sa operatorom sizeof. U Poglavlju 5 se diskutuje o operatorima * (preusmeravanje,ili „to je na adresi od) i & (adresa od). Primetite da je prioritet bit-operatora &, ^ i | ni‚i od prioriteta operatora == i !=. To znai da izrazi koji vr„e testiranje bitova, kao „to je if ( (x & MASK) == 0) ... moraju biti navedeni u zagradama da bi dali pravilne rezultate. Kao „to smo ranije pomenuli, izrazi koji sadr‚e asocijativne i komutativne operatore (*, +, &, ^, |) mogu biti preure”eni prilikom izraunavanja ak i ako su upotrebljene zagrade. U ve†ini sluajeva to ne pravi nikakvu razliku; u situacijama gde bi moglo, koriste se odre”ene privremene promenljive da bi se obezbedio ‚eljeni redosled izraunavanja. C, kao i ve†ina drugih jezika, ne defini„e kojim †e se redom izvr„avati operacije u nekom izrazu. Na primer, u izrazu kao „to je x = f() + g(); f mo‚e biti izraunato prvo, a mo‚e biti i obrnuto; zbog toga ako f menja neku spoljnu promenljivu od koje g zavisi (ili obrnuto), vrednost promenljive x mo‚e zavisiti od redosleda izraunavanja. Jo„ jednom, me”urezultati se mogu sme„tati u privremene promenljive da bi se obezbedio odre”eni redosled izraunavanja. Slino ovome, redosled kojim se izraunavaju argumenti funkcije tako”e nije definisan. Zato izraz printf("%d %d \n", ++n, power(2,n)); /* pogre„no */ mo‚e proizvesti (i proizvodi) razliite rezultate na razliitim ma„inama, zavisno od toga da li je promenljiva n uve†ana pre poziva funkcije power ili posle. Re„enje je, naravno, pisati ++n; printf("%d %d \n", n, power(2, n)); Pozivi funkcija, umetnuti iskazi dodeljivanja i operatori uve†avanja i umanjivanja izazivaju tzv. 'usputni efekat' - izraunavanjem nekog izraza usput je promenjena i neka promenljiva. Ako neki izraz proizvodi 'usputne efekte', redosled kojim su promenljive tog izraza sme„tane mo‚e postati osetljivo pitanje. Jedna nezgodna situacija je predstavljena izrazom a[i] = i++; Pitanje je da li je indeks nova ili stara vrednost promenljive i. Kompajler mo‚e ovo prevesti na razliite naine, i stvoriti razliita re„enja. Kada se pojave usputni efekti, sve je prepu„teno kompajleru po„to optimalni redosled zavisi od arhitekture konkretne ma„ine. Pouka ove diskusije je da je pisanje konstrukcija koje zavise od redosleda izraunavanja lo„a praksa u bilo kom jeziku. Naravno, neophodno je znati „ta treba izbe†i, ali ako ne znate kako se stvari odvijaju na drugim ma„inama, ta naivnost vam mo‚e pomo†i. Postoje C rutine koje otkrivaju ve†inu takvih mesta koja zavise od redosleda izraunavanja. P o g l a v l j e 3: KONTROLA TOKA Iskazi kontrole toka nekog jezika defini„u redosled kojim †e se neka izraunavanja izvr„iti. Kroz prethodne primere smo ve† upoznali najosnovnije konstrukcije za kontrolu toka u C-u; u ovom poglavlju †emo kompletirati skup tih konstrukcija i detaljno opisati one ve† pomenute. 3.1 ISKAZI I BLOKOVI Izrazi kakvi su x = 0 ili i++ ili printf(...) postaju iskazi kada za njima sledi znak ;: x = 0; i++; printf(...); U C-u, znak ; predstavlja oznaku za kraj iskaza. Vitiaste zagrade { i } se koriste da grupi„u deklaracije i iskaze u slo‚eni iskaz ili blok tako da su sintaksno ekvivalentni jednom iskazu. Zagrade oko iskaza koji ine neku funkciju su oigledan primer; zagrade oko grupe iskaza u if, else, while ili for konstrukcijama su drugi primer. Promenljive mogu biti deklarisane unutar bilo kog bloka; o ovome †e biti rei u Poglavlju 4. Posle desne zagrade } koja ograniava neki blok nikad ne sledi znak ;. 3.2 IF - ELSE Konstrukcija if - else se koristi kod dono„enja nekih odluka u programu. Njen formalni oblik je if (izraz) iskaz1 else iskaz2 gde se else deo konstrukcije mo‚e i izostaviti. Uslov izraz se izraunava; ako je istinit (tj. taan: izraz ima vrednost razliitu od nule), izvr„i†e se iskaz1. Ako nije taan (izraz ima vrednost nula) i postoji else deo, izvr„i†e se iskaz2. Po„to if testira numeriku vrednost izraza koji predstavlja uslov, to su mogu†a izvesna skra†enja u pisanju programa. Najoiglednije je pisanje if (izraz) umesto if (izraz != 0) Ponekad je ovo prirodno i jasno; ponekad nije. Zbog toga „to je else deo u if - else konstrukciji opcion, to postaje nejasno „ta †e se dogoditi kada se else izostavi iz konstrukcije u kojoj se oekuje. Ovo se re„ava na uobiajen nain - pridru‚uje se najblizoj if konstrukciji u kojoj nema else dela. Na primer, u if (n > 0) if (a > b) z = a; else z = b; else deo se pridru‚uje uz unutra„nje if, „to smo i naglasili uvlaenjem teksta. Ako to nije ono „to ‚elite, morate da upotrebite zagrade da biste ostvarili ‚eljeno pridru‚ivanje: if (n > 0) { if (a > b) z = a; } else z = b; Nejasno†a je posebno opasna u situaciji kao {to je : if (n > 0) for (i = 0; i < n; i++) if (s[i] >= 0) { printf("..."); return i; } else /* pogresno */ printf("greska - n je negativno\n"); Uvlaenje teksta nedvosmisleno pokazuje „ta ‚elite, ali kompajler to ne†e tako shvatiti, i pridru‚i†e else najbli‚oj, unutra„njoj if konstrukciji. Ovu vrstu gre„aka je veoma te„ko otkriti; dobra predostro‚nost je kori„†enje vitiastih zagrada kada se pojavljuje vi„e umetnutih if konstrukcija. Uzgred, primetite da u konstrukciji if (a > b) z = a; else z = b; iza izraza z = a stoji znak ;. To stoga jer gramatiki gledano posle if dela sledi iskaz, pa je izraz koji sledi iza if dela uvek zavr„en znakom ;. 3.3 ELSE - IF Konstrukcija if (izraz1) iskaz1 else if (izraz2) iskaz2 else if (izraz3) iskaz3 else iskaz4 se pojavljuje toliko esto u programima da je vredna kra†e diskusije. Ovakva konstrukcija je najop„tiji nain da se izrazi slo‚ena odluka. Uslovi (izraz1 - 4) se ispituju (izraunavaju) po redu: im je jedan od njih istinit, izvr„ava se iskaz koji je njemu pridru‚en, i cela konstrukcija se odmah napu„ta. I ovde, kao i ranije, iskaz mo‚e biti jedan iskaz ili grupa iskaza navedena u vitiastim zagradama. Iskaz uz poslednji else deo u gornjoj konstrukciji (iskaz4) bi†e izvr„en u sluaju da nijedan od prethodno testiranih uslova nije zadovoljen. Dakle, bi†e izvr„en kao preostali sluaj. Ponekad ne†e biti potrebno preduzeti neku akciju u preostalom sluaju; tada se else iskaz4 mo‚e izostaviti, ili se iskoristiti za konstatovanje gre„ke 'nemogu†a varijanta'. Da bismo ilustrovali troznano odluivanje, napisali smo funkciju binsearch koja pretra‚uje da li se odre”ena vrednost x pojavljuje u nekom polju v iji su elementi pore”ani po rastu†em redosledu. Funkcija u program vra†a poziciju elementa koji je jednak x (tj. broj izme”u nula i n-1), odnosno -1 ako se x ne pojavljuje me”u elementima polja v. Algoritam pretra‚ivanja je slede†i: ako je x manje od vrednosti sredi„njeg elementa polja, pretra‚ivanje se preme„ta u donju polovinu polja. U suprotnom, preme„ta se u gornju polovinu. U oba sluaja, slede†i korak je upore”ivanje x sa sredi„njim elementom izabrane polovine. Proces deljenja opsega na dva dela se nastavlja sve dok se ne prona”e tra‚ena vrednost ili se opseg ne iscrpi. /* binsearch: tra‚i x u polju v[0] ..... v[n-1] */ int binsearch(int x, int v[], int n) { int low, high, mid; low = 0; high = n - 1; while (low <= high) { mid = (low + high) / 2; if (x < v[mid]) high = mid - 1; else if (x > v[mid]) low = mid + 1; else /* prona”eno */ return mid; } return -1; /* nije prona”eno */ } Su„tinska odluka je da li je x manje, ve†e ili jednako sredi„njem elementu v[mid] u svakom koraku; prirodno je da je upotrebljena else - if konstrukcija. ž Ve‚ba 3 - 1 Na„a funkcija obavlja dva testa unutar petlje, mada bi i jedan bio dovoljan (po cenu ve†eg broja spoljnih testova). Napi„ite verziju sa samo jednim testom unutar petlje i uporedite razliku u vremenu izvr„avanja programa. 3.4 SWITCH Kori„†enje switch iskaza je nain da se u programu donese neka vi„eznana odluka. Njegova konstrukcija je switch (izraz) { case konst_izr_1 : iskaz1 case konst_izr_2 : iskaz2 case konst_izr_3 : iskaz3 default: iskaz4 } Iskazom switch se poredi vrednost celobrojnog izraza izraz sa konstantnim izrazima konst_izr_1 - 3 (konst_izr mo‚e biti celobrojna konstanta, znakovna konstanta ili konstantni izraz; ako ih je vi„e, odvajaju se dvotakom). Kada se nastupi neki od sluajeva case, tj. kada se ustanovi jednakost sa nekim od konstantnih izraza, izvr„ava se iskaz koji je pridru‚en tom konstantnom izrazu. I ovde je iskaz jedan ili vi„e iskaza, ovaj put navedenih bez vitiastih zagrada. Sluaj oznaen sa default je neobavezan; ako je naveden u programu, i ako izraz ne odgovara nijednom od konst_izr iznad, bi†e izvr„en iskaz4. Sluajevi mogu biti navedeni u programu bilo kojim redosledom, ali se konst_izr svih sluajeva moraju me”usobno razlikovati. U Poglavlju 1 smo napisali program koji broji koliko se puta na ulazu pojavila cifra, koliko specijalni znaci, a koliko svi ostali znaci. Tada smo koristili niz if - else konstrukcija, a sada evo istog programa napisanog kori„†enjem konstrukcije switch: #include main() /* broji cifre,spec. znakove i ostalo */ { int c, nwhite, nother, ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; i++) ndigit[i] = 0; while ( (c = getchar()) != EOF) { switch (c) { case '0' : case '1' : case '2' : case '3' : case '4': case '5' : case '6' : case '7' : case '8' : case '9': ndigit[c - '0']++; break; case ' ' : case '\n' : case '\t' : nwhite++; break; default: nother++; break; } } printf("cifre = "); for (i = 0; i < 10; i++) printf(" %d", ndigit[i]); printf(", spec_znaci = %d, ostalo = %d\n", nwhite, nother); return 0; } Iskaz break izaziva trenutni izlazak iz switch konstrukcije. Sluajevi su samo razliito oznaeni, a ne i odvojeni me”usobno. To znai da †e u sluaju da se izvr„i akcija vezana za neki sluaj, izvr„enje biti nastavljeno kroz slede†i sluaj, i tako redom sve dok se eksplicitno ne naznai izlazak iz konstrukcije. Iskazi break i return su uobiajeni nain da se na licu mesta iza”e iz switch konstrukcije. Iskaz break tako”e mo‚e poslu‚iti za trenutni izlazak iz for, while i do petlji, o emu †e detaljno biti rei kasnije. Prolazak kroz sluajeve ima dobre i lo„e strane. Dobro je to „to obuhvata vi„e sluajeva jednom akcijom, kao „to je to sluaj kod specijalnih znakova u ovom primeru. Me”utim, zbog toga svaki sluaj mora zavr„avati break iskazom da bi spreio prolazak kroz slede†i. Prolazak kroz sluajeve je sklon raspadu pri modifikaciji programa. Sa izuzetkom vi„estrukih oznaka za jedno izraunavanje, ovu konstrukciju treba shvatiti kao racionalnu i koristiti je. Kao pitanje dobrog stila, stavite break iskaz ak i na kraj poslednjeg sluaja (ovde default), iako je to logiki nepotrebno. Jednog dana †ete dodati jo„ neki sluaj na kraj va„e switch konstrukcije, i tada †e vam ova predostro‚nost pomo†i. ž Ve‚ba 3 - 2 Napi„ite funkciju expand(s, t) koja prilikom kopiranja niza s u niz t konvertuje znak za novi red i tabulator u vidljive eskejp sekvence \n i \t. 3.5 WHILE I FOR PETLJE Do sada smo se ve† sretali sa while i for petljama. U konstrukciji while (izraz) iskaz uslov izraz se izraunava. Ako je njegova vrednost razliita od nule, izvr„ava se deo iskaz i ponovo se ispituje uslov izraz . Ovaj ciklus se nastavlja sve dok vrednost uslova izraz ne postane jednaka nuli. Tada se preskae deo iskaz i izvr„avanje programa se nastavlja iza njega. Konstrukcija for oblika for (izr1 ; izr2 ;izr3) iskaz je ekvivalentna konstrukciji izr1; while (izr2) { iskaz izr3; } Sintaksno gledano, tri dela for konstrukcije su izrazi. Naje„†e, izr1 i izr3 su izrazi dodeljivanja ili pozivi funkcija, dok je izr2 relacioni izraz. Bilo koji od ova tri izraza mo‚e biti izostavljen, ali znaci ; moraju ostati. Ako se deo u kome je test (izr2) izostavi, smatra se da je stalno istinit, tako da je for (;;) { ... } beskonana petlja iz koje je mogu†e iza†i samo na neki drugi nain (iskazima break ili return). Stvar je afiniteta da li †ete koristiti konstrukciju for ili while. Na primer, u while ( (c = getchar()) == ' ' || c = '\n' || c = '\t') ; /* preskoi spec. znake */ nema inicijalizacije i reinicijalizacije nekog brojaa, pa se while konstrukcija ini najprirodnijom. Konstrukcija for je bez dileme superiornija kada je u pitanju jednostavna inicijalizacija i reinicijalizacija nekog brojaa, jer dr‚i sve iskaze koji kontroli„u petlju na jednom mestu - na vrhu petlje. To je najoiglednije na primeru for (i = 0; i < N; i++) koji predstavlja nain da se u C-u obradi prvih N elemenata nekog polja, slino DO petlji u Fortranu. Analogija nije potpuna, s obzirom na to da granice brojaa for petlje mogu biti menjane iz same petlje, a kontrolna promenljiva i zadr‚ava svoju vrednost kad se petlja okona iz bilo kog razloga. Zbog toga „to su delovi for konstrukcije izrazi proizvoljnog oblika, to for petlje nisu ograniene samo na aritmetike progresije. I pored svega toga, nije dobro ubacivati u for konstrukciju izraze koji nisu u vezi sa samom petljom; bolje je da su tu umesto njih operacije koje kontroli„u petlju. Kao bolji primer, evo jo„ jedne verzije funkcije atoi za konvertovanje stringa u njegov numeriki ekvivalent. Ova verzija je jo„ op„tija; ona manipuli„e ak i sa eventualno ubaenim specijalnim znacima i sa predznacima + i -. Poglavlje 4 prikazuje funkciju atof koja obavlja ovakvu konverziju sa realnim brojevima. Osnovna struktura programa je prilago”ena obliku ulaza: preskoi specijalni znak, ako postoji uzmi znak sa ulaza, ako jo„ ima znakova uzmi celobrojni deo i konvertuj ga Svaki korak obavlja jedan deo posla, i ostavlja stvari spremne za delovanje slede†eg dela. Ceo program se okonava onog trenutka kada nai”e na prvi znak koji ne mo‚e predstavljati neku cifru. include /* atoi: konvertuje niz s u ceo broj; verzija 2 */ int atoi(char s[]) { int i, n, sign; for (i = 0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t' ; i++) ; /* preskoi specijalne znakove */ sign = 1; if (s[i] == '+' || s[i] == '-') /* predznak */ sign = (s[i++] == '+') ? 1 : -1; for (n = 0; s[i] >= '0' && s[i] <= '9'; i++) n = 10 * n + s[i] - '0'; return sign * n; } Prednosti dr‚anja iskaza koji kontroli„u petlju na jednom mestu su jo„ oiglednije u situacijama kada postoji nekoliko umetnutih nivoa petlji. Slede†a funkcija predstavlja Shell-ov algoritam za sortiranje polja celih brojeva. Osnovna ideja ovog algoritma je da se jo„ u ranoj fazi porede udaljeni elementi umesto susednih, kako se radi kod jednostavnijih algoritama za sortiranje. Ovo vodi brzoj eliminaciji ve†ih neure”enih delova, tako da se u kasnijim fazama obavlja manje posla. Razmak izme”u elemenata koji se porede se postepeno smanjuje do jedinice, na kom stepenu se sortiranje jednostavno svodi na izmenu susednih elemenata. /* shellsort: sortira v[0] ... v[n] u rastu†em nizu */ void shellsort(int v[], int n) { int gap, i, j, temp; for (gap = n / 2; gap > 0; gap /= 2) for (i = gap; i < n; i++) for (j = i - gap; j > 0 && v[j] > v[j + gap]; j -= gap) { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; } } U ovom primeru postoje tri umetnute petlje. Spoljna petlja kontroli„e razmak gap izme”u elemenata koji se porede, i koji je najpre n / 2 , a zatim se sa svakim prolazom smanjuje deljenjem sa dva dok ne postane nula. Petlja u sredini poredi svaki par elemenata iji je razmak veliine gap ; unutra„nja petlja obr†e mesta para elemenata tako da budu u rastu†em redosledu. Po„to se razmak gap smanjuje do jedinice, to †e svi elementi biti pravilno sortirani. Primetite da se oblik spoljne petlje ne razlikuje od oblika sredi„nje i unutra„nje petlje, iako u spoljnoj petlji nije upotrebljena aritmetika progresija. Jedan od C operatora je i zarez (,), koji naje„†e nalazi upotrebu u for konstrukciji. Par izraza odvojenih zarezom se izraunava sleva nadesno, a tip i vrednost rezultata bi†e tipa i vrednosti desnog operanda. Na taj nain je mogu†e u for konstrukciji smestiti vi„e izraza u razliite delove petlje i tako, na primer, paralelno kontrolisati dva brojaa. To je ilustrovano funkcijom reverse(s) koja naopake okre†e redosled elemenata u nizu s . #include /* reverse: obrtanje niza s */ void reverse(char s[]) { int c, i, j; for (i = 0 , j = strlen(s) - 1 ; i < j ; i++ , j--) { c = s[i]; s[i] = s[j]; s[j] = c; } } Zarezi koji odvajaju argumente funkcija, promenljive u deklaracijama itd. nisu operatori (,) , i ne garantuju izraunavanje sleva nadesno. Najbolje je koristiti operatore (,) kod izraunavanja izraza koji su upu†eni jedni na druge, kao u for (i = 0 , j = strlen(s) - 1 ; i < j ; i++ , j--) { c = s[i] , s[i] = s[j] , s[j] = c; ž Ve‚ba 3 - 3 Napi„ite funkciju expand(s1, s2) koja pro„iruje skra†eni zapis a - z iz niza s1 u ekvivalentnu kompletnu listu abc...xyz u nizu s2. Predvidite u programu mogu†nost koris†enja malih i velikih slova i cifara, i pripremite program za sluajeve oblika a - b - c ili a - z0 - 9 ili -a-z. Usvojite konvenciju da se znak - na poetku ili na kraju skra†enog oblika shvati kao slovo. 3.6 DO - WHILE PETLJE Za petlje while i for je karakteristino da na vrhu petlje ispituju uslovni deo petlje, umesto da to ine na dnu. Tre†a varijanta petlje u C-u testira ovaj uslov na dnu petlje, posle svakog prolaza kroz petlju; dakle, telo petlje bi†e izvr„eno bar jedanput. Ova konstrukcija je oblika do iskaz while (izraz); Prvo se izvr„ava telo iskaz, a zatim se ispituje uslov izraz. Ako je istinit, iskaz se izvr„ava ponovo, ponovo se testira uslov i tako sve dok je izraz istinit. Kad postane netaan, petlja se okonava. Kao „to se moglo oekivati, petlja do - while se re”e koristi od petlji while i for, otprilike u svakom dvadesetom sluaju koji zahteva re„enje pomo†u petlje. I pored toga, ova konstrukcija je s vremena na vreme korisna, kao „to je to sluaj u slede†oj funkciji itoa, koja konvertuje broj u odgovaraju†i string (obrnuto od funkcije atoi). Posao je malo te‚i nego „to bi se moglo u prvi mah pomisliti, jer jednostavni metodi generi„u niz cifara u pogre„nom redosledu. Izabrali smo da se niz cifara generi„e u obrnutom redosledu, a zatim da se okrene. /* itoa: konvertuje broj n u niz s */ void itoa(int n, char s[]) { int i, sign; if ( (sign = n) < 0) /* utvr”uje predznak */ n = -n; /* ucini n pozitivnim */ i = 0; do { /* generisi cifre u obrnutom redosledu */ s[i++] = n % 10 + '0'; /* uzmi cifru */ } while ( (n /= 10) > 0); /* obrisi je */ if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); } Konstrukcija do - while je neophodna, ili bar pogodna, po„to bar jedan znak mora biti sme„ten u niz s, bez obzira na vrednost broja n. Tako”e, dodali smo vitiaste zagrade oko jednog jedinog izraza koji ini telo do - while petlje. Iako su nepotrebne, one †e spreiti nepa‚ljivog itaoca da deo while smatra poetkom neke while konstrukcije. ž Ve‚ba 3 - 4 Napi„ite slinu funkciju itob(n, s) koja konvertuje nepredznaen ceo broj n u njegov ekvivalentni binarni oblik koji se sme„ta u nizu s . Napi„ite i funkciju itoh koja konvertuje nepredznaen ceo broj u njegovu heksadecimalnu prezentaciju. ž Ve‚ba 3 - 5 Napi„ite verziju funkcije itoa koja prihvata tri argumenta umesto dva. Tre†i argument neka bude minimalna veliina polja; dobijeni niz mora se popuniti blanko znakovima ako je potrebno da se ostvari ‚eljena veliina polja. 3.7 BREAK I CONTINUE Ponekad je pogodno da postoji mogu†nost kontrole petlje ne samo na vrhu i na dnu, ve† i na nekom drugom mestu. Iskaz break obezbe”uje prevremeni izlazak iz petlji for, while i do, kao i iz switch konstrukcije. Ovaj iskaz izaziva trenutan izlazak ak i iz najuvuenije od nekoliko umetnutih petlji (ili iz switch konstrukcije). Slede†i program uklanja blanko znakove, znakove za novi red i tabulatore polaze†i od kraja linije sa ulaza, i koriste†i iskaz break za izlazak iz petlje onog trenutka kad nai”e na znak niza koji nije jedan od specijalnih znakova. /* trim: uklanja specijalne znakove sa kraja linije */ int trim(char s[]) { int n; for (n = strlen(s) - 1; n >= 0; n--) if (s[n] != ' ' && s[n] != '\n' && s[n] != '\t') break; s[n+1] = '\0'; return n; } Funkcija strlen vra†a u program du‚inu niza s. Petlja for poinje pretra‚ivanje od kraja niza uklanjaju†i specijalne znake sve dok ne nai”e na znak koji nije iz ove grupe znakova , ili dok broja znakova niza ne postane negativan (tj. kad se cela linija pretra‚i). Trebalo bi da zakljuite da se program ispravno pona„a ak i kad je linija prazna ili sadr‚i samo specijalne znake. Iskaz continue je povezan sa iskazom break, ali se re”e koristi; on izaziva poetak slede†eg prolaska kroz petlju u kojoj je naveden (for, while, do). U petljama while i do ovo znai da †e njihov test deo biti smesta izvr„en, a u for petlji znai da †e se smesta izvr„iti reinicijalizacija brojaa. Iskaz continue se primenjuje samo na petlje, ne i na konstrukciju switch. Ako je switch konstrukcija ubaena unutar neke petlje, tada †e iskaz continue naveden u switch konstrukciji izazvati slede†u iteraciju petlje. Kao primer upotrebe iskaza continue, evo petlje koja operi„e samo sa pozitivnim elementima nekog polja a; negativne vrednosti se preskau. for (i = 0; i < N; i++) { if (a[i] < 0) /* preskoci negativne elemente */ continue; ... /* operacije nad pozitivnim elementima */ } Iskaz continue se koristi u situacijama kada je deo petlje koji sledi komplikovan, pa bi obrtanje uslova i uvlaenje jo„ jednog nivoa programa bilo previ„e. ž Ve‚ba 3 - 6 Napi„ite program koji kopira liniju sa ulaza na izlaz, ali tako „to „tampa samo jednu iz grupe uzastopnih identinih linija. (Ovo je upro„†ena verzija UNIX rutine uniq). 3.8 GOTO I LABELE C obezbe”uje ne mnogo korisnu naredbu goto, i labele (oznake) kojima se oznaavaju mesta na koja se skok vr„i. Op„te uzev, iskaz goto se nikad ne mora upotrebljavati, a i u praksi je gotovo uvek jednostavnije napisati program bez njega. U ovoj knjizi nismo koristili iskaz goto. I pored toga, pokaza†emo vam par situacija u kojima bi iskaz goto mogao na†i primenu. Naje„†i sluaj je potreba trenutnog izlaza iz neke duboko umetnute strukture, kao „to je izlazak iz dve ili vi„e petlja istovremeno. U ovakvom sluaju ne mo‚e biti upotrebljen iskaz break, jer on obezbe”uje izlazak iz samo jedne petlje. for ( ... ) for ( ... ) { ... if (katastrofa) goto greska; } ... greska: sredi stanje Ova organizacija je pogodna u situacijama kada rutina za otklanjanje gre„ke nije jednostavna i stoga mora biti izdvojena, ili ako se gre„ka mo‚e pojaviti na vi„e mesta i nije mogu†e na svakom od tih mesta praviti posebnu rutinu za njeno otklanjanje. Labela (oznaka) ima isti oblik kao i ime promenljive, i pra†ena je dvotakom. Mo‚e biti pridru‚ena bilo kom iskazu unutar funkcije u kojoj je goto. Kao drugi primer, razmotrite problem nala‚enja prvog negativnog elementa u dvodimenzionalnom polju. Vi„edimenzionalna polja su opisana u Poglavlju 5. for (i = 0; i < N; i++) for (j = 0; j < M; j++) if (v[i][j] < 0) goto na”eno; /* element nije prona”en */ . . . na”eno: /* na”en je element na poziciji (i,j) */ . . . Program pisan uz upotrebu iskaza goto uvek mo‚e biti napisan i bez njega, premda mo‚da po cenu nekih ponavljanja testova ili neke