4 Petlje
4 Petlje
4 Petlje
Petlja (eng. loop) je dio programa čije se izvršavanje ponavlja određeni broj
puta. Najjednostavniji primjer petlje smo već vidjeli: recimo da želimo na ekran
ispisati svoje ime 100 puta, možemo napisati sljedeći kod:
1 #include <stdio.h>
2 int main() {
3 int i;
4 for (i=0; i<100; i++) {
5 printf("Svoje ime\n");
6 }
7 return 0;
8 }
Ove vitičaste zagrade označavaju blok koda o čemu smo pričali ranije. Rekli smo i
to da ako se samo jedna naredba nalazi u bloku, zagrade se mogu izostaviti. Koliko
ovdje imamo naredbi u bloku? Možemo li izostaviti zagrade?
U C-u ovo nije dozvoljeno, dakle primjer iznad ne predstavlja validan C kod i neće
se kompajlirati (osim ako niste greškom kreirali C++ projekat umjesto C projekta).
Zaglavlje petlje definiše koliko puta će se petlja izvršiti, na način da se određuje
početna i krajnja vrijednost kontrolne promjenljive i , kao i korak za koji će se i
povećati u svakom prolazu kroz petlju. U ranijem primjeru vidimo da je početna
vrijednost i nula, da se petlja prekida kada i postane 5 (što znači da 5 nije
uključeno u petlju), te da se u svakom prolazu i poveća za jedan ( i++ ). Kako
bismo izmijenili ovu for petlju tako da je i broj 5 uključen?
Tijelo petlje je blok naredbi koje se ponavljaju. Sve ono što je u tijelu petlje
će se ponavljati, ono što nije – neće. Prije i poslije tijela petlje ne navodi se znak
tačka-zarez. Ako napišemo:
for (int i=0; i<5; i++) ; {
printf ("i je %d\n", i);
}
Zaglavlje for petlje nalazi se unutar običnih (malih) zagrada, a sastoji se opet od tri
dijela razdvojena znakom tačka-zarez (eng. semicolon). Generalno, da bi for petlja
bila validna moraju se u zaglavlju javiti tačno dva tačka-zareza. Ta tri dijela su:
inicijalizacija (označena žutom bojom), uslov (označen plavom bojom) i ažuriranje
(označeno crvenom bojom).
Inicijalizacija je neka naredba koja će se izvršiti samo jednom prije ulaska u
petlju. Ona služi za postavljanje početne vrijednosti kontrolne promjenljive. Npr. u
primjeru iznad i=0 je postavilo kontrolnu promjenljivu na njenu početnu vrijednost
nula.
Sve dok je uslov ispunjen petlja će se ponavljati. Kada uslov prestane biti
ispunjen, petlja se prekida a izvršavanje se nastavlja poslije petlje (tj. iza tijela
petlje). U pravilu uslovom se definiše krajnja vrijednost kontrolne promjenljive npr.
iznad je stavljeno i<5 što znači da će i rasti dok ne dostigne vrijednost pet.
Konačno, ažuriranje je neka naredba koja će se izvršiti prilikom svakog pro-
laza kroz petlju, poslije tijela petlje, a služi kako bi se definisao korak sa kojim će
kontrolna promjenljiva rasti. U našem primjeru i++ označava da se i pri svakom
prolazu kroz petlju povećava za jedan.
Pogledajmo neke primjere.
Koliko puta će se izvršiti sljedeće petlje:
for (i=1; i<8; i++) { ...
for (i=100; i<200; i++) { ...
for (i=1; i<=8; i++) { ...
for (i=8; i<1; i++) { ...
U posljednjem slučaju petlja se neće izvršiti nijednom, jer uslov petlje nije is-
punjen odmah na početku. Postavili smo i na 8 što nije manje od 1 tako da uslov
već u startu nije ispunjen. To znači da se tijelo petlje neće izvršiti niti jednom, neće
se uopšte na ekranu ispisati da "if nije petlja".
Za kraj ove diskusije o strukturi for petlje pogledaćemo ovaj programčić:
1 #include <stdio.h>
2 int main() {
3 int i,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (i=0; i<n; i++) {
7 printf("if nije petlja!\n");
8 }
9 return 0;
10 }
Ovdje korisnik najprije sa tastature unosi cijeli broj n a zatim se na ekranu ispiše
tekst “if nije petlja” n puta. Tu možemo vidjeti primjer for petlje koja se izvršava
neki proizvoljan broj puta koji korisnik definiše svojim unosom, a ako korisnik unese
negativan broj petlja se naravno neće izvršiti niti jednom.
Probajte odgonetnuti šta će ovaj program ispisati na ekran ako korisnik unese n=5 .
Možete li?
Da bismo shvatili zašto je program ispisao baš ovo, trebamo razumjeti precizno kako
funkcioniše for petlja odnosno kojim redom se izvršavaju naredbe. Proći ćemo kroz
program korak po korak.
U liniji 3 programa deklarisane su promjenljive i i n . U ovom trenutku one su
neinicijalizovane. U liniji 5 korisnik unosi n sa tastature, a rekli smo da će unijeti
broj 5. Da vidimo šta se dešava dalje.
U liniji 6 programa nalazi se zaglavlje for petlje koje se izvršava ovako: najprije se
dešava inicijalizacija, tj. postavlja se i na 0. Zatim se provjerava uslov tj. da li je
i<n odnosno da li je 0<5. Pošto je uslov ispunjen prelazi se na tijelo petlje. Tijelo
petlje u liniji 7 ispisuje poruku i je ... , u ovom trenutku promjenljiva i još uvijek
ima vrijednost 0 pa će se ispisati i je 0 .
Ovim je tijelo petlje završeno. Tek kada se izvrše sve naredbe u tijelu petlje
izvršava se ažuriranje kontrolne promjenljive koje se nalazi u zaglavlju for petlje.
Naredba za ažuriranje glasi i++ pa će se i povećati za 1 i postaće 1.
Sada se ponovo provjerava uslov, da li je i<n tj. 1<5, što je ispunjeno. Ponovo
se izvršava tijelo petlje, tj. ispisuje se i je 1 . Ponovo se izvršava ažuriranje, pa i
postaje 2 itd.
Preskočimo sada na posljednji prolaz for petlje u kojem se ispisala poruka i je 4 .
Nakon izvršenja tijela petlje (ispisa poruke i je 4 ) izvršava se ažuriranje for petlje
i++ , i se povećava za 1 i postaje 5. Zatim se provjerava uslov i<n , pošto je i=5 i
n=5 uslov NIJE ispunjen (5 nije manje od 5) i petlja se prekida. U ovom trenutku
izvršavanje for petlje skače na kraj petlje tj. na prvu naredbu koja se nalazi poslije
tijela petlje. To je naredba:
9 printf("poslije petlje %d\n", i);
3 int i=0,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (; i<n; i++) {
7 printf("i je %d\n", i);
8 }
9 printf("poslije petlje %d\n", i);
10 return 0;
11 }
Ovo nije greška striktno govoreći, ali ćete u odgovarajućoj liniji dobiti upozorenje
kompajlera
Statement with no effect
koje označava da imate kod koji ne radi ništa. Naime, ako navedete samo naziv
promjenljive, sam taj naziv je ujedno i izraz koji ima određenu vrijednost, ali mi sa
tom vrijednošću nismo uradili ništa, baš kao da ste u zasebnoj liniji napisali naredbu:
i;
Zapamtite da je prvo polje zaglavlja for petlje naredba koja se izvršava prije ulaska
u petlju. Kompajler vas tu upozorava da niste nešto slučajno zaboravili pošto je un-
esena naredba beskorisna. Da bismo izbjegli poplavu upozorenja (koja je generalno
loša jer se mogu slučajno preskočiti ozbiljne greške), bolje je da pobrišemo ovo i
koje je ujedno i nepotrebno jer je dozvoljeno imati prazno polje zaglavlja.
Također i ažuriranje može biti prazno. Gdje bismo trebali u prethodnom primjeru
prebaciti i++ kako bi polje zaglavlja za ažuriranje postalo prazno, a da program
zadrži istu funkciju?
Na kraju, i sam uslov može biti prazan. Petlja koja glasi ovako:
for (;;) {
Ovaj program i dalje radi potpuno isto što i prethodnih nekoliko primjera. Da biste
prepoznali inicijalizaciju, uslov i ažuriranje for petlje, samo tražite znak tačka-zarez.
Radi lakšeg razumijevanja obojićemo ih bojama kao i ranije:
1 #include <stdio.h>
2 int main() {
3 int i=0,n;
4 printf("Unesite broj n: ");
5 for (scanf("%d", &n); i<n; printf("i je %d\n", i++));
6 printf("poslije petlje %d\n", i);
7 return 0;
8 }
Dakle, pošto je inicijalizacija naredba koja se izvršava samo jednom prije početka
petlje, tu smo ubacili poziv funkcije scanf jer nam je to potrebno samo jednom.
Uslov je ostao isti kao i ranije. Operacija ažuriranja nam je sada kompletno tijelo
petlje sa svojom printf funkcijom. Možete uočiti da se unutar poziva printf
funkcije i uvećava za jedan, pri čemu smo koristili postfiksni operator ++ kako
bismo osigurali da se na ekranu ispiše neuvećana vrijednost.
Konačno, zelenom bojom je označeno tijelo petlje koje se sastoji samo od znaka
tačka-zarez! Naime, pošto smo kompletno tijelo uspjeli uvući u zaglavlje petlje, tijelo
nam je sada prazno. Ponekad u kodu se dešava da želimo označiti blok koda koji je
prazan tj. koji ne sadrži niti jednu naredbu. To možemo uraditi na dva ravnopravna
načina. Jedan je naravno da stavimo samo otvorenu i zatvorenu vitičastu zagradu
for (scanf("%d", &n); i<n; printf("i je %d\n", i++)) { }
10 return 0;
11 }
Obratite pažnju na polje ažuriranja u zaglavlju petlje. i+=3 je naredba koja koristeći
kombinovani operator += uvećava i za 3. Isto bi značenje imala i naredba i=i+3 .
Obratite pažnju da je ažuriranje naredba. Da ste napisali samo i+3 ovako:
for (i=0; i<n; i+3) {
Ali možemo li iskoristiti korak kako bismo uzimali svaki drugi broj?
1 #include <stdio.h>
2 int main() {
3 int i,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (i=n; i<=n*n; i+=2) {
7 printf("%d\n", i);
8 }
9 return 0;
10 }
Problem je što ne znamo da li je broj n paran ili nije. Ako broj n nije paran,
program iznad će ispisati sve neparne brojeve na intervalu, a ne sve parne. Rješenje
je da prije for petlje uvećamo n ako je neparno.
1 #include <stdio.h>
2 int main() {
3 int i,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 i=n;
7 if (n%2 == 1) i++;
8 for (; i<=n*n; i+=2) {
9 printf("%d\n", i);
10 }
11 return 0;
12 }
Ovaj program je ekvivalentan primjeru koji smo proučavali ranije. Sada se ko-
risti beskonačna for petlja iz koje se izlazi naredbom break . Uslov je invertovan
(sjetimo se da je >= negacija od <) jer se u ovom programu petlja prekida kada je
uslov ispunjen, a ranije smo u zaglavlju for petlje navodili uslov za koji će se petlja
ponavljati sve dok je taj uslov ispunjen.
Naredba continue (eng. nastavi) preskače sve preostale naredbe do kraja petlje
i prelazi na sljedeću iteraciju petlje. Zbog toga je sam naziv ove naredbe često zbun-
jujući jer nije najjasnije šta se to nastavlja. Pogledajmo sljedeći primjer upotrebe
naredbe continue:
1 #include <stdio.h>
2 #include <math.h>
3 int main() {
4 int i;
5 double x,y;
6 for (i=0; i<100; i++) {
7 x = sin(i/3.);
8 if (x<=0) continue;
9 y = i + sqrt(x);
10 printf("i=%d x=%f y=%f\n’’, i, x, y);
11 }
12 return 0;
13 }
Ovaj program će na ekranu ispisati broj 58. Da bismo razumjeli zašto, proći ćemo
kroz njega liniju po liniju.
Kao što znamo, for petlja se izvršava 70 puta. Brojač h će se uvećati za jedan u
svakom prolazu kroz petlju (linija 6), osim kada se izvrši naredba continue u liniji 5
budući da ona preskače naredbu h++ . Ova naredba continue će se izvršiti za svako
k čiji je modulo 6 jednak nuli, odnosno za sve brojeve djeljive sa 6 kao i za nulu
(pošto je 0%6=0 ). Na intervalu [0,70) takvih brojeva ima ukupno 12:
0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66.
Dakle, naredba h++ će se izvršiti 70-12=58 puta. Što se tiče naredbe break ona
se neće nikada izvršiti jer su brojevi 6 i 12 djeljivi sa 6 (vidimo ih na prethodnom
spisku) pa će u oba slučaja naredba continue preskočiti naredbu break.
Ako program modifikujemo tako da piše:
7 if (k==6 || k==12 || k==17)
Unesite broj n: 5
*****
*****
*****
*****
*****
Kod rješavanja ovog zadatka možemo primijeniti opštu strategiju rješavanja pro-
gramskih zadataka, koja je inspirisana latinskom uzrečicom “zavadi pa vladaj”:
3 Ako imate složen problem koji ne znate (isprve) riješiti, podijelite ga na jed-
nostavnije probleme koje znate riješiti.
Čak i ako ne uspijete riješiti sve dijelove zadatka, barem imate nešto što je u
praksi često bolje nego ništa. Ostatak problema možete podijeliti sa vašim kolegama,
ili naći neko polu-gotovo rješenje pretraživanjem interneta.
U ovom slučaju možemo zadatak podijeliti ovako:
Važno je zapamtiti da računar obavlja zadatke koje smo mu dali jedan po jedan,
pa je dovoljno strpljivo navoditi rješenja i ponavljati ih potreban broj puta dok ne
dođemo do konačnog rezultata.
Dakle, kako ćemo ispisati jednu zvjezdicu na ekranu? Kôd koji obavlja taj
zadatak glasi printf("*"); – ovom naredbom smo ispisali jednu zvjezdicu. Sada
nju trebamo ponoviti n puta. Za ponavljanje nekog zadatka n puta koristimo for
petlju:
for (i=0; i<n; i++)
printf("*");
Time smo dobili n zvjezdica što čini jedan red. Ostaje da ovaj zadatak ispisa jednog
reda ponovimo n puta. Možemo li kompletan kod naveden iznad staviti u još jednu
petlju, ovako?
for (i=0; i<n; i++)
for (i=0; i<n; i++)
printf("*");
Ovo nije ispravno jer u ugniježdenoj petlji koristimo istu kontrolnu promjenljivu.
Moramo zamijeniti i sa j
for (i=0; i<n; i++)
for (j=0; j<n; j++)
printf("*");
Program je ispisao devet zvjezdica, ali se sve te zvjezdice nalaze u istom redu. Zašto?
Da li smo negdje zatražili od računara da pređe u novi red? Nismo. Potrebno
je nakon svakog reda zvjezdica ispisati po jedan znak \n . Gdje trebamo dodati
odgovarajući printf ? Ako to uradimo ovako:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
nećemo imati nikakvog efekta jer će se novi red ispisati na kraju programa gdje je
nevidljiv. Ovakva popravka:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (i=0; i<n; i++) {
7 for (j=0; j<n; j++) {
8 printf("*");
9 printf("\n");
10 }
11 }
12 return 0;
13 }
Drugim riječima dobijamo po jedan novi red nakon svake zvjezdice. Ispravno rješenje
je sljedeće: j -petlja predstavlja ispis jednog reda, pa znak \n treba ispisati nakon
j -petlje ali unutar i -petlje koja odbrojava redove, tj. ovako:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (i=0; i<n; i++) {
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
...
pri čemu je k neki broj sa kojim množimo sve članove tog reda. Možemo uočiti
da petlja prolazi kroz sve vrijednosti i P r1, 10s te da smo printf funkciji direktno
proslijedili rezultat množenja brojeva i i k . Ostaje da uočimo da ova promjenljiva
k takođe poprima sve vrijednosti na intervalu [1,10] te da na kraju svakog reda
ispišemo \n .
1 #include <stdio.h>
2 int main() {
3 int i,j;
4 for (i=1; i<=10; i++) {
5 for (j=1; j<=10; j++) {
6 printf("%d", i*j);
7 }
8 printf("\n");
9 }
10 return 0;
11 }
Ovo već počinje ličiti na tablicu množenja, ali brojevi nisu lijepo poravnati. Da
bismo dobili lijepo poravnanje kao u primjeru, uočavamo da će najveći broj u ovoj
tablici biti 100, dakle trocifren, tako da je dovoljno da za svaki broj rezervišemo po
4 mjesta. Preostaje da prepravimo poziv funkcije printf na sljedeći način:
6 printf("%4d", i*j);
Sve dok je uslov ispunjen, tijelo petlje se ponavlja, a petlja se završava kada uslov
više nije ispunjen. Obratite pažnju da je sa while petljom vrlo lako napraviti beskon-
ačnu petlju! To će se desiti ako se u tijelu petlje ne dešava ništa što bi promijenilo
istinitost uslova. Npr. ako napišemo:
while (x>0) y++;
ovo će sigurno biti ili beskonačna petlja ili petlja koja se ne izvrši nijednom. Ako
je uslov x>0 ispunjen prvi put, biće ispunjen i svaki sljedeći put jer se x ne mijenja
nigdje u petlji. Pogledajmo malo složeniji primjer:
1 #include <stdio.h>
2 int main() {
3 int x, suma=0;
4 while (suma <= 100) {
5 printf("Unesite broj: ");
6 scanf("%d", &x);
7 suma += x;
8 }
9 printf("suma je %d", suma);
10 return 0;
11 }
Značenje while petlje ovdje je “sve dok je suma manja ili jednaka od 100 ponavljaj
unos broja x i dodaj ga na sumu”. U ovom primjeru ne možemo znati kakve
brojeve će unositi korisnik, suma može odmah prekoračiti broj 100 a može se unositi
beskonačno mnogo brojeva (ako korisnik bude unosio i negativne brojeve).
for petlja je istovremeno jednostavnija i složenija od while petlje. Jednostavnija
je zato što – ako se ispravno koristi – na prvi pogled možemo vidjeti koliko će se
puta izvršiti. Složenija je zato što se for petlja sastoji od nekoliko dijelova koji se
ne izvršavaju onim redom koji su navedeni, nego se sa uslova skače na tijelo petlje
pa nazad na ažuriranje.
Kod while petlje, analiza je vrlo jednostavna. Ako je uslov ispunjen, izvršava se
tijelo petlje te se program vraća na uslov. Ako nije, izvršenje skače na prvu naredbu
poslije tijela petlje.
Ako želimo namjerno napraviti beskonačnu petlju pomoću petlje while, samo se
podsjetimo da je u C-u jedinica logička vrijednost istine. Pa ako napišemo:
while(1)
ova petlja će biti beskonačna pošto će uslov u petlji uvijek imati vrijednost “istina”.
Kao što znamo, možemo dobiti program koji obavlja istu funkciju ako inicijalizaciju
prebacimo ispred petlje, a ažuriranje postavimo u tijelo petlje nakon ostalih naredbi:
i=0;
for (; i<n; ) {
printf("i je %d", i);
i++;
}
A for petlja koja u zaglavlju sadrži samo uslov je ustvari while petlja! Odnosno
možemo napisati:
i=0;
while (i<n) {
printf("i je %d", i);
i++;
}
Generalno, svaku for petlju možemo pretvoriti u while petlju na sljedeći način
(Slika 4.2): inicijalizaciju (označenu crvenom bojom) prebacimo ispred petlje, ažuri-
ranje (označeno zelenom bojom) prebacimo u tijelo petlje nakon ostalih naredbi, a
uslov (označen plavom bojom) ostaje gdje jeste i sada postaje uslov while petlje.
Moguća je i transformacija u obrnutom smjeru, pri čemu tražimo u okolnom
kodu naredbe koje bi mogle biti kandidati za inicijalizaciju i ažuriranje, pa ako ih ne
nađemo možemo ta polja for petlje ostaviti i praznim.
3 Razlika između while i do-while petlje je u tome što se while petlja ne mora
izvršiti niti jednom, dok će se do-while petlja uvijek izvršiti barem jednom.
Ova petlja se neće izvršiti niti jednom, korisnik nikada neće vidjeti lozinku jer uslov
x<3 u datom trenutku sigurno nije ispunjen. Izvršenje programa preskače kompletno
tijelo petlje i nastavlja sa prvom naredbom nakon petlje. Da smo umjesto toga stavili
do-while petlju:
1 int x=5;
2 do {
3 printf("Lozinka glasi: califragilisticexpialidocius\n");
4 } while (x<3);
korisnik bi sigurno ugledao lozinku bez obzira da li je uslov ispunjen ili ne. Ispun-
jenost uslova utiče isključivo na to koliko puta će se petlja ponoviti.
Iz ovoga proizlazi da ne možemo jednostavno pretvoriti do-while petlju u while
petlju, morali bismo ponoviti kompletan kod petlje (Slika 4.3).
Ovaj dupli kod možemo izostaviti samo ako znamo da je uslov B sigurno ispunjen
kada prvi put ulazimo u while petlju. do-while petlja upravo služi da bismo izbjegli
nepotrebne inicijalizacije promjenljivih koje osiguravaju barem jedno izvršenje petlje.
Obratimo pažnju na sintaksu do-while petlje koja se može vidjeti na primjeru
iznad: nakon ključne riječi do slijedi tijelo petlje (blok naredbi), zatim ključna riječ
while , uslov naveden u običnim (malim) zagradama, te na kraju znak tačka-zarez.
Treba naglasiti da se znak tačka-zarez javlja tek na kraju! Nema ga poslije riječi
do ili prije riječi while .
Da bismo pokazali kako se koristi do-while petlja, podsjetimo se jednog programa
koji smo koristili za demonstraciju rada switch-case strukture. Srezaćemo program
na interesantni dio:
1 #include <stdio.h>
2 int main() {
3 int mjesec;
4 printf("Unesite mjesec: ");
5 scanf("%d", &mjesec);
6 if (mjesec < 1 || mjesec > 12) {
7 printf("Neispravan mjesec");
8 return 0;
9 }
10 if (mjesec == 1) ...
Ovaj program sadrži grešku. Naime, uporedili smo vrijednost promjenljive mjesec
sa brojevima 1 i 12 prije nego što je promjenljiva dobila ikakvu vrijednost. Ovo
je pristup neinicijalizovanoj promjenljivoj! Iako će u velikoj većini slučajeva ovaj
program raditi ispravno, postoji određena vjerovatnoća da će promjenljiva dobiti
vrijednost na intervalu [1,12] tako da korisnik nikada neće ni dobiti priliku da unosi
mjesec.
Ovaj problem možemo riješiti na nekoliko načina. Možemo staviti još jedan unos
promjenljive mjesec prije petlje koji će se sigurno izvršiti:
1 #include <stdio.h>
2 int main() {
3 int mjesec;
4 printf("Unesite mjesec: ");
5 scanf("%d", &mjesec);
6 while (mjesec < 1 || mjesec > 12) {
7 printf("Unesite mjesec: ");
8 scanf("%d", &mjesec);
9 }
10 ...
Ali ovo bi dovelo do ponavljanja koda. Svako ponavljanje koda smatra se štetnim.
Nekada je lakše rješenje uraditi copy-paste određenog koda onoliko puta koliko vam
je potreban, ali to treba izbjegavati kada god je moguće. Pored toga što nepotrebno
produžuje program i otežava čitljivost, ponavljanje koda može voditi ka greškama
jer kada uočite grešku u kodu, sada tu istu grešku morate popraviti na više mjesta,
a velika je vjerovatnoća da ćete preskočiti neko od tih mjesta.
Možete i postaviti inicijalnu vrijednost promjenljive mjesec na neku vrijednost
koja osigurava da će se while petlja izvršiti barem jednom:
1 #include <stdio.h>
2 int main() {
3 int mjesec=0;
4 while (mjesec < 1 || mjesec > 12) {
Pošto smo postavili mjesec na nulu, znamo da će se petlja izvršiti barem jednom.
Ovo je također jedna vrsta koda koji je ispravan i daje tačan rezultat, ali je primjer
loše prakse u programiranju koja se naziva magična konstanta. Naime, ovaj primjer
je dovoljno jednostavan da je jasno šta se ovdje dešava, ali u složenijim programima
programer koji analizira taj program može postaviti pitanje: šta predstavlja ta nula?
Zašto baš nula? Zašto ne 1000 ili -1? Navođenje bilo kakve konstantne vrijed-
nosti u kodu u pravilu zahtijeva komentar koji objašnjava zašto je baš ta vrijednost
odabrana.
Ako malo razmislimo, u oba ova primjera mi smo ustvari simulirali rad do-while
petlje. U primjeru sa ponavljanjem koda, mi smo upravo obavili transformaciju
while u do-while petlju. U primjeru sa mjesec=0 , postavili smo početnu vrijednost
promjenljive mjesec da bismo osigurali da se while petlja izvrši barem jednom. Pravo
pitanje koje će iskusniji programer postaviti početniku koji koristi magične konstante
glasi “šta želiš postići?” Ako želimo postići da se petlja izvrši barem jednom, za to
se koristi do-while petlja. Pa ćemo prepraviti program tako da se koristi upravo ta
petlja:
1 #include <stdio.h>
2 int main() {
3 int mjesec;
4 do {
5 printf("Unesite mjesec: ");
6 scanf("%d", &mjesec);
7 } while (mjesec < 1 || mjesec > 12);
8 if (mjesec == 1) {
9 ...
zadataka, ako znamo da se već dugo vremena nikakvi ozbiljni programi niti igre ne
rade na ovaj način?
Crtanje 2D oblika je jedan vrlo praktičan primjer kroz koji možemo razumjeti
način rada programa, a pogotovo dvostrukih petlji. Vidjećemo kasnije da će nam to
biti korisno kada budemo učili matrice i druge višedimenzionalne strukture. Mnogi
lakše razumiju apstraktne pojmove na vizualan način, ako ih vide ilustrovane nekim
crtežom.
Prilikom rješavanja ovih zadataka vrlo je važno shvatiti da se mi iz C programa
ne možemo (na standardan način) pozicionirati na proizvoljnu lokaciju na ekranu.
Ne možemo ići prema lijevo niti prema gore. Moguće je koristiti znak \b za brisanje
jednog karaktera, ali ovo nam u pravilu nije previše korisno. Ako želimo pomjeriti
kursor prema dolje ispisujemo znak za novi red \n , ali obratite pažnju da se nakon
ispisa \n više ne možemo vratiti nazad prema gore. Kretanje u desno postižemo
ispisom znaka razmaka: printf(" ");
Dakle kada razmišljamo o rješenju uvijek moramo imati na umu da se kursor
kreće red po red, odozgo prema dolje, a unutar jednog reda s lijeva na desno. Ovaj
smjer kretanja odgovara tzv. scan-linijama koje se koriste inače za osvježavanje
sadržaja na ekranima (Slika 4.4).
Zadatak 5. Nacrtati trougao sastavljen od zvjezdica kao na primjeru čije
su katete dugačke po n zvjezdica (broj n unosi korisnik).
Unesite broj n: 5
*
**
***
****
*****
3 Ako ne znate uraditi ono što se traži, uradite dio koji znate a zatim probajte
prepravljati program prema ciljanom rješenju.
Ono što znamo uraditi je nacrtati kvadrat čije stranice čini n zvjezdica jer smo
to uradili u zadatku 4.3 (poglavlje 4.1.7).
Unesite broj n: 5
*****
*****
*****
*****
*****
ili
7 for (j=0; j<=i; j++) {
Unesite broj n: 5
*****
****
***
**
*
Tekst zadatka je isti, ali se oblik razlikuje tj. trougao je rotiran. Na koji način
možemo ispisati ovako rotiran trougao?
Krenimo opet analizirati svaki pojedinačni red trougla. U prvom redu imamo 5
zvjezdica, dakle n zvjezdica, u drugom redu imamo 4 zvjezdice (n-1), u trećem 3
(n-2) itd. Dalje uočavamo da je svaki red pomaknut u desnu stranu. Da se ne bi
desilo da su zvjezdice pomaknute ulijevo, prije svakog reda trebamo ispisati određeni
broj razmaka. Koji je to broj?
Možemo uočiti da u nekom i -tom redu treba ispisati i razmaka i n-i zvjezdica,
pri čemu i kreće od 0, a to je upravo ono što nam treba. Rješenje glasi:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6 for (i=0; i<n; i++) {
7 for (j=0; j<i; j++)
8 printf(" ");
9 for (j=0; j<n-i; j++)
10 printf("*");
11 printf("\n");
12 }
13 return 0;
14 }
Unesite broj n: 5
.....
. .
. .
. .
.....
22 return 0;
23 }
Kôd u linijama 7-9 ispisuje prvi red tačaka koji se završava prelaskom u novi red
( \n ). Linije 18-20 predstavljaju posljednji red i možemo vidjeti da su identične. U
linijama 11-16 ispisuje se sredina, pri čemu jedan red sredine ispisuju linije 12-15
(tačka, n-2 razmaka, tačka i \n ), a taj red se ponavlja n-2 puta. Radi lakšeg
čitanja koda ostavili smo prazne redove u linijama 6, 10, 17 i 21.
Postoji i drugačiji pristup rješavanju zadataka sa crtanjem oblika na ekranu.
Možemo na prostor za ispis postaviti koordinatni sistem:
Unesite broj n: 5
.....
. .
. .
. .
.....
Ishodište koordinatnog sistema je u gornjem lijevom uglu jer ispis kreće slijeva
nadesno, odozgo prema dole. Vodoravna koordinata je j a uspravna je i . Sada
zadatak postaje da kreiramo logičku (Booleovu) funkciju f pi, jq koja ima vrijednost
istina ako treba na koordinatama pi, jq ispisati tačku, a neistina ako ne treba tj.
ako treba ispisati razmak. Ne smijemo zaboraviti \n na kraju svakog reda.
for (i=0; i<n; i++) {
for (j=0; j<n; j++) {
if (f(i,j))
printf(".");
else
printf(" ");
}
printf("\n");
}
Pošto još uvijek nismo radili funkcije, logički uslov za ispis tačke ćemo uglaviti u kod
umjesto f(i,j) . Vidimo da se tačka treba ispisati ako je i nula (gornja stranica)
ili ako je i=n-1 (donja stranica) ili j=0 (lijeva stranica) ili j=n-1 (desna stranica).
Prevedeno u C to nam daje sljedeći kompletan program:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6
17 return 0;
18 }
* * * *
** * ** *
* * * * * *
* ** * * *
* * * **
* *
4.4.2 Sume
Sljedeći čest tip zadataka sa petljama su programi koji trebaju izračunati neku
matematičku sumu. Pa pogledajmo na primjeru kako se rješavaju ovakvi zadaci.
Zadatak 9. Napisati program koji izračunava arctanh x – hiperbolički
arkus tangens nekog broja x – koristeći sljedeću aproksimativnu formulu:
x3 x5
arctanh x “ x ` ` ` ... (4.1)
3 5
Računari ne mogu predstaviti brojeve sa beskonačnom preciznosti. Zbog toga
se iracionalne vrijednosti (kakve su u pravilu rezultati trigonometrijskih funkcija)
izračunavaju tako što se pronađe neki red koji konvergira ka željenoj vrijednosti, a
zatim se izračunava njegova konačna suma. Broj članova sume n određuje se tako
da krajnji rezultat bude dovoljno tačan za potrebe zadatka.
Dakle, ako je zadatak definisan kao iznad, korisnik ustvari treba unijeti dvije
vrijednosti: broj x koji je u pravilu realan broj, te prirodan broj n koji predstavlja
broj članova sume.
Možemo uočiti da su promjenljive suma , x i clan tipa double , jer za velike vrijed-
nosti n povećana preciznost može imati efekta na rezultat, te smo koristili format
%lf za unos. Petljom do-while osigurali smo da je vrijednost n pozitivna pošto C
nema tip podataka za prirodne brojeve. Na kraju koristimo format %g za ispis čime
postižemo da će se ispisati onoliko decimala koliko bude potrebno.
Zadatak 10. Napisati program koji izračunava broj π na k tačnih decimala,
po sljedećoj aproksimativnoj formuli:
1 1 1 1
π “ 4 ¨ p1 ´ ` ´ ` ...q (4.4)
3 5 7 9
Koliko članova sume n nam je potrebno da bismo imali k tačnih decimala?
Uočavamo da je svaki sljedeći član sume date iznad manji od prethodnog. Dakle
kako raste naše i dolazimo do članova koji su toliko mali da ne bi trebali imati
efekta na prvih k decimala. Konkretno, kada član sume postane manji od 10´k
možemo prekinuti petlju.
Ovdje treba napomenuti da čak ni sa tipom double ne možemo predstaviti više
od 6-7 tačnih decimala. Kada radimo sa brojevima manjim od 0.000001 greška
zbog nepreciznosti predstavljanja realnih brojeva u memoriji postaje tolika da se
lako može desiti da upadnemo u beskonačnu petlju. No za potrebe rješavanja ovog
zadatka pretpostavićemo da je tip double precizniji nego što realno jeste.
Suma u skraćenom obliku glasi:
n
ÿ 1
π “4¨ p´1qi (4.5)
i“1
2i ´ 1
ali ovo bi bilo prilično neefikasno rješenje jer bismo u svakom prolasku kroz petlju
radili stepenovanje, što nije potrebno. Umjesto toga možemo pisati ovako:
clan = predznak / (2*i-1);
predznak = -predznak;
x2 x3
S “x` ` ` ... (4.6)
2! 3!
Ova suma konvergira jer funkcija faktorijela raste brže od eksponencijalne funkcije.
Faktorijel se definiše ovako:
k! “ 1 ¨ 2 ¨ 3 ¨ ... ¨ k (4.7)
Dakle k! možemo izračunati sljedećim C kodom:
fakt=1;
for (i=2; i<=k; i++)
fakt *= i;
Ovo rješenje je potpuno tačno, ali ga možemo uraditi znatno efikasnije. Pogledajmo
sljedeće rješenje:
1 #include <stdio.h>
2 int main() {
3 int i,j,n,fakt;
4 double suma=0, x, clan=1;
5 printf("Unesite broj x: ");
6 scanf("%lf", &x);
7 do {
8 printf("Unesite broj clanova sume: ");
9 scanf("%d", &n);
10 } while (n<=0);
11 for (i=1; i<=n; i++) {
12 clan *= x/i;
13 suma += clan;
14 }
Pogledajte u liniji 12 kako izračunavamo opšti član. Svaki član sume jednak je
prethodnom članu pomnoženom sa x{i:
xi´1 x xi
¨ “ (4.8)
pi ´ 1q! i i!
Sada u petlji imamo jedno sabiranje, jedno množenje i jedno dijeljenje, dok smo
ranije imali još i stepenovanje i petlju za računanje faktorijela. Početna vrijednost
člana je 1 (linija 4) kako bi u prvom prolazu bio pomnožen sa x{1 i dobijamo prvi
član sume x.
Prilikom rješavanja zadataka u kojima se pojavljuje faktorijel, obratite pažnju da
funkcija faktorijela raste izuzetno brzo, pa već za vrijednosti oko 13! prekoračujemo
opseg vrijednosti koje možemo predstaviti tipom int .
Problem ovdje je što ste previdjeli riječ isključivo u tekstu definicije. Svaki broj
je djeljiv sa 1 i sa samim sobom, ali prosti brojevi nisu djeljivi ni sa kojim drugim
brojem, pa bi sa stanovišta programiranja bolja bila sljedeća definicija: Broj je prost
ako NIJE djeljiv niti sa jednim brojem između 1 i tog broja.
Često se može vidjeti i ovakvo rješenje:
if (n%2 != 0 && n%3 != 0 && n%5 != 0 && n%7 != 0)
print("Prost");
else
print("Slozen");
Ponekad se uslovi nastavljaju, pa studenti znaju ispisati i više od pola stranice teksta
nabrajajući brojeve sa kojima prost broj nije djeljiv. Studentu svaka čast na trudu
ali ovakvo rješenje ponovo nije tačno. To možemo vidjeti iz sljedećeg primjera:
uzmimo sve brojeve koje je student nabrojao u svom uslovu i nađimo neki broj koji
je veći od svih tih brojeva i koji je prost (u primjeru iznad to bi bilo 11). Nazovimo
taj broj x. Broj x2 je složen broj jer je djeljiv sa x, no on nije djeljiv niti sa jednim
od brojeva navedenih u kôdu! Dakle, ako bi korisnik unio broj n “ 112 “ 121, kôd
naveden iznad bi za taj broj ispisao da je prost što nije tačno.
Pa će zatim student probati popraviti svoj kod na sljedeći način:
if (n%2 != 0 && n%3 != 0 && n%5 != 0 && n%7 != 0 && n%(int)sqrt(n) != 0)
print("Prost");
else
print("Slozen");
return 0;
}
No za broj 9 imamo:
Unesite broj n: 9
Prost
Slozen
Šta se ovdje desilo? Prođimo kroz program korak po korak. Najprije je i imalo
vrijednost 2 pa smo provjeravali da li je 9 djeljivo sa 2. Pošto nije, uslov nije ispunjen
pa se na ekranu ispisalo Prost . Vidimo da ovo nije ispravno jer je 9 složen broj,
pa se tekst Prost ne treba ispisivati u petlji za svaku vrijednost i sa kojom n nije
djeljivo, nego samo na kraju petlje. Možemo ovako prepraviti program:
for (i=2; i<n; i++) {
if (n%i == 0) {
printf("Slozen");
break;
}
}
printf("Prost");
jer se tekst Prost ispisuje za svako uneseno n bilo ono prost ili složen broj. Želimo
da se tekst Prost ispiše samo ako broj n nije djeljiv niti sa jednim i , odnosno ako
se break nikada nije izvršilo. Kako to možemo znati? Pa podsjetimo se poglavlja
4.1.6 gdje smo rekli da vrijednost kontrolne promjenljive i poslije petlje može biti
korisna da znamo da se petlja završila bez prekida. Time dolazimo do kompletnog
tačnog rješenja zadatka:
1 #include <stdio.h>
2 int main() {
3 int i,j,n;
4 printf("Unesite broj n: ");
5 scanf("%d", &n);
6
16 return 0;
17 }
Ovo rješenje je bolje od prethodnog jer ga lakše možemo iskoristiti u nekom većem
programu npr. ako se traži da prebrojimo sve proste brojeve na nekom intervalu.
Također često imamo i zadatke tipa “postoji barem jedan x” – a matematička
oznaka je Dx. Recimo provjera da li je broj složen spada u ovaj tip zadatka. Broj
n je složen ako i samo ako na intervalu (1,n) postoji barem jedno x takvo da je n
djeljivo s njim. Matematički to možemo zapisati
Zadatak tipa Dx je invertovana verzija zadatka @x, odnosno broj je složen ako nije
prost i obrnuto. Drugim riječima, ako za svako x važi suprotan uslov od traženog,
to znači da nije tačno da postoji barem jedan x za koji važi traženi uslov. Algoritam
je u biti isto samo se mijenja značenje logičke istine i neistine (0 i 1):
1. Pretpostavimo da ne postoji nijedno x koje zadovoljava uslov.
2. Koristeći for petlju prolazimo kroz sve moguće vrijednosti x.
3. Ako nađemo takvo x za koje uslov važi, zaključujemo da je uslov zadatka
ispunjen i prekidamo petlju.
• c=x%10=4 , x=x/10=281
• c=x%10=1 , x=x/10=28
• c=x%10=8 , x=x/10=2
• c=x%10=2 , x=x/10=0
7 while (x != 0) {
8 c = x % 10;
9 if (c>max) max=c;
10 x = x / 10;
11 }
12 printf ("Najveca cifra je: %d", max);
13
14 return 0;
15 }
Obratimo pažnju na par stvari. Za početnu vrijednost promjenljive max smo uzeli 0
što je sigurna vrijednost jer sigurno niti jedna cifra neće biti manja od nule. Uslov
petlje glasi x!=0 a ne x>0 jer bi ova druga varijanta radila netačno ako korisnik
unese negativan broj. No u tom slučaju će i c biti negativno, pa bismo u liniji 8
trebali uzeti:
8 c = abs(x % 10);
Zadatak 14. Napisati program koji sve parne cifre u broju zamjenjuje
cifrom 0. Pošto cjelobrojni tipovi u C-u ne mogu predstaviti brojeve koji
počinju nulama, sve parne cifre koje se nalaze s lijeve strane broja trebaju
biti obrisane. Ako su sve cifre broja parne, rezultat treba biti broj 0.