Potprogrami
Potprogrami su izdvojeni delovi programa koji se izvršavaju po pozivu. Potprogrami se najčešće kreiraju kao korisničke funkcije.
U programiranju često dolazimo u situaciju da nam se deo programa ponavlja na više različitih mesta. Idealno bi bilo da taj deo programa nekako izdvojimo i samo ga pozivamo kada nam zatreba. Ovaj problem je veoma dobro poznat u programiranju i rešava se korišćenjem potprograma.
Generalno, postoje dve vrste potprograma - procedure i funkcije. Procedure su klasični potprogrami. Kada napravimo proceduru, praktično, kao da smo dodali novu naredbu našem programskom jeziku. Dakle, procedura se pozove, izvrši se i to je to. Sa druge strane, funkcije rade sve to isto, s tim što još i vraćaju rezultat.
Procedure i funkcije
Podela potprograma na procedure i funkcije dolazi iz programskih jezika poput Pascala i nekih verzija Basica. Ovi jezici su prilično striktni po pitanju poziva i rada potprograma. Pogledajte primer iz klasičnog Pascala.
Program primer;
var
x : ineteger;
procedure rutina(num:integer);
var
a : integer;
begin
a := num * 2;
writeln(a);
end;
function fun(num:integer);
var
a : integer;
begin
a := num * 2;
fun := a;
end;
begin
{poziv procedure je kao da pozivamo neku naredbu}
rutina(25);
{poziv funkcije mora biti unutar izraza}
x := fun(55);
end.
Vidite, iako oba potprograma rade istu stvar - množe zadati broj sa 2, procedura odmah ispisuje rezultat, a poziva se "samostalno". Sa druge strane, funkcija obavezno vraća vrednost (iako smo i unutar funkcije mogli da ispiujemo rezultat), a poziv funkcije mora biti unutar nekog izraza.
Ovaj način rada sa potprogramima je dosta rigidan, pa su moderni programski jezici preuzeli ideju koju je popularisao jezik C, da funkcije ne moraju vraćati vrednost. Kao takve, ne moraju se pozivati unutar izraza (u stvari poziv funkcije može biti izraz-sam-za-sebe). Samim tim, procedure više nemaju svrhu postojanja.
Svi moderni programski jezici (Java, Python, Go, Rust, Dart...) kao i jezici koje na ovom sajtu učimo za web development (JavaScript i PHP), koriste isključivo funkcije. Da još pojačamo poentu, u programskom jeziku C, vaš glavni deo programa je takođe samo još jedna funkcija - funkcija main().
Inače, treba da znamo da programski jezici dolaze sa velikim brojem već pripremljenih ugrađenih funkcija (nekad su "spakovane" u pakete ili biblioteke, nekad dolaze kao metodi objekata), koje programerima olakšavaju život. Ipak, što naš program bude kompleksniji, moraćemo sve više da pravimo sopstvene funkcije. Da bi se naši potprogrami razlikovali od ugrađenih funkcija, nazivamo ih korisničke funkcije.
Prednosti korišćenja potprograma
Dakle, očigledno je da su potprogrami dobra stvar. Razloga nema puno, ali su jako važni.
Već smo pomenuli da potprograme koristimo da u njih smestimo deo programa koji se ponavlja više puta. Tako programiramo brže, naš program postaje "čistiji", a samim tim i lakše ispravljamo greške.
Korišćenjem potprograma se jako olakšava podela posla. Na ovaj način više programera može raditi na istom programu - prosto, svako piše svoje potprograme i tako program postaje modularan. Najbolje od svega je što iste potprograme možemo ponovo koristiti za neke druge programe.
Neke tehnike programiranja možemo upotrebiti upravo zahvaljujući potprogramima. Ovde najpre mislimo na rekurzivne algoritme i tehnike kao što su backtracking ili rad sa stablima...
Ako se naviknemo da pravimo potprograme, lakše ćemo naučiti objektno-orijentisano programiranje, pošto su metodi klasa (objekata) jedna od osnovnih stvari OOP-a, a oni su praktično - potprogrami.
Uvođenje potprograma
Zavisno od programskog jezika može ili ne mora postojati tačno određeno mesto u programu gde uvodimo potprograme. Čak iako nas programski jezik ne "primorava" da kreiramo potprograme na posebnom mestu, preporučujemo da svoje potprograme grupišete na jednom mestu u programu. Da li će to biti početak ili kraj, zavisi od vaših potreba.
Inače, kada napišemo potprogram, to se zove deklaracija potprograma.
Tipični delovi deklaracije potprograma
Obično za potprograme koristimo neku posebnu oznaku ili ključnu reč, kao npr. "function", "func", "fn", "procedure", "sub" i sl. kojom računaru "objašnjavamo" da je ono što sledi potprogram.
function naziv(a,b) {
naredba;
naredba;
...
}
Zatim navodimo naziv pomoću kojeg kasnije pozivamo taj potprogram.
function naziv(a,b) {
naredba;
naredba;
...
}
Treći elemenat je lista parametara pomoću kojih potprogramu prenosimo neke vrednosti, ako je potrebno.
function naziv(a,b) {
naredba;
naredba;
...
}
Nije obavezno da funkcija ima parametre. U tom slučaju ćemo deklarisati funkciju samo sa otvorenom i zatvorenom zagradom.
function naziv() {...}
Konačno, četvrti i poslednji element je telo potprograma, koje suštinski predstavlja jedan program u malom.
function naziv(a,b) {
naredba;
naredba;
...
}
U funkciji može postojati i jedna posebna naredba - obično je to return. Ova naredba služi da vrati rezultat funkcije. Istovremeno, kada se dođe do ove naredbe funkcija prestaje sa radom (čak iako posle nje postoji još naredbi).
function naziv(a,b) {
naredba;
naredba;
...
return rezultat;
}
Poziv funkcije
Funkciju (tj. bilo koji potprogram) pozivamo tako što navedemo njen naziv i listu vrednosti za parametre.
naziv(x,y)
Poziv možemo vršiti iz glavnog programa ili neke druge funkcije. Poziv se može vršiti čak i iz iste funkcije, odnosno funkcija može pozivati samu sebe i tada se ulazi u rekurziju. Ovu tehniku programiranja i druge koje se zasnivaju na njoj ćemo posebno objasniti.
Poziv funkcije može biti deo nekog izraza, ako funkcija vraća vrednost. Takođe, poziv se može navesti i "samostalno", kada predstavlja izraz sam za sebe. U tom slučaju se povratna vrednost gubi.
SAMOSTALNO:
naziv(x,y);
U OKVIRU IZRAZA:
r = f + naziv(x,y) / 2;
Kako to radimo u pseudo-kodu...
Da bismo razumeli malo složenije algoritamske primere, koji zahtevaju korišćenje potprograma, moramo prvo objasniti način deklaracije potprograma u pseudo-kodu. Pošto je ceo naš sajt posvećen web programiranju, (prvenstveno u JavaScriptu i PHP-u), ni mi nismo izmišljali toplu vodu, pa tako naši potprogrami prilično "vuku" na porodicu programskih jezika koji potiču iz C-a.
Funkcije
U našem primeru smo kreirali funkciju max() koja pronalazi najveću od tri zadate vrednosti i vraća je kao rezultat.
function max(a,b,c) {
x = a;
if (x < b) { x=b; }
if (x < c) { x=c; }
return(x);
}
read(p,q);
rez = max(p, q+1, 10) * 2;
write(rez);
U glavnom programu se unose promenljive p i q. Pomoću funkcije max() se pronalazi najveći od brojeva p, q+1 i 10, množi sa 2 i dodeljuje promenljivoj rez, koja se onda ispisuje.
Vrednosti koje prosleđujemo u pozivu funkcije, kopiraju se u parametre a, b i c. U funkciji onda radimo sa tim vrednostima i pomoću dodatne lokalne promenljive x pronalazimo najveću. Onda to x vraćamo kao rezultat funkcije. Pošto volimo da sve bude cakum-pakum, kod nas je obavezno da se u naredbi return vrednost navede u zagradi.
Možda vam trenutno nije baš sve najjasnije, ali pročitajte dalje još malo o parametrima i oblasti važenja, pa se vratite na ovaj primer.
Parametri funkcije
Da bismo "komunicirali" sa potprogramom, koristimo parametre koji mogu biti formalni i stvarni. Formalni parametri su oni koje zadajemo u deklaraciji i koje koristimo kada pišemo program funkcije. To je ono kad kažemo "biće sabrane neke promenljive a i b". Formalni parametri su uvek promenljive.
Sa druge strane, stvarni parametri su konkretne vrednosti koje zadajemo kada pozivamo funkciju. Pri pozivu funkcije, bitno je da parametri imaju vrednost. Inače to mogu biti promenljive, literali, pozivi funkcija, čitavi izrazi... - samo je bitno da imaju vrednost kakva se traži u funkciji za formalni parametar.
Kako to sve funkcioniše? Jednostavno - vrednosti koje zadamo u pozivu (stvarni parametri), kopiraju se u promenljive koje smo zadali kao parametre u deklaraciji (formalne parametre). Evo par primera:
function zbir(a,b) { return (a+b);}
zbir(5, 2);
zbir(x, y*4);
zbir(z-2, zbir(y+8, x));
Izgleda komplikovano, ali u stvari nije... Sve što treba da zapamtimo je da se prvo računa vrednost parametara, pa se oni tek onda prosleđuju funkciji. Čak i kada imamo "funkciju unutar funkcije", prvo se računa unutrašnja, pa tek onda spoljašnja funkcija.
U klasičnom programiranju obično važi da se stvarni i formalni parametri moraju poklopiti po broju, tipu i redosledu (značenju). Hajde da to malo razjasnimo.
Poklapanje po broju znači da koliko smo predvideli formalnih parametara, toliko smemo da zadamo i stvarnih parametara - ni manje, ni više.
DEKLARACIJA:
function max(a,b,c) {...}
LOŠ POZIV:
max(15, y);
Dakle, ovo je primer greške - zamislili smo da funkcija prima tri parametra, ali pri pozivu smo prosledili samo dva.
Poklapanje po tipu znači da ako smo zamislili da prvi parametar bude broj, drugi string i sl. ne smemo da pogrešimo tipove kod zadavanja stvarnih parametara.
DEKLARACIJA:
function proizvod(a,b) {
return a*b;
}
LOŠ POZIV:
proizvod("Joca", "Pera");
U ovom primeru, funkcija proizvod() zahteva dva parametra, pa dve vrednosti i prosleđujemo, ali pošto funkcija množi zadate parametre, očigledno je da se traže dva broja, a mi prosleđujemo dva stringa.
Poklapanje po značenju znači da stvarne parametre navodimo onim redosledom kako je zamišljeno kod formalnih parametara.
DEKLARACIJA:
function student(ime, prezime) {...}
LOŠ POZIV:
student("Jovanović", "Ana");
Pogledajte sada - funkcija traži dva parametra i to da budu stringovi. Pri pozivu zaista i prosleđujemo dva stringa, ali funkcija traži da ti parametri budu ime i prezime, a prosleđujemo prezime pa ime. Znači ne poklapaju se po značenju. Ovakav tip greške je i najgori - program će raditi, ali će rezultat biti pogrešan, a uzrok se ponekad jako teško pronalazi.
Oblast važenja
Promenljive, prema oblasti važenja delimo na globalne i lokalne. Globalna promenljiva je ona koja ima važenje u celom programu i svim potprogramima. Lokalna promenljiva je ona koja važi samo u svom potprogramu - glavnom programu i drugim potprogramima je nedostupna.
Generalna ideja programiranja je da potprogrami budu potpuno nezavisni i da se globalne promenljive nikada ne koriste u njima. Sav prenos podataka između programa i potprograma odvija se preko parametara.
Ipak, u nekim jednostavnijim programima, iz praktičnih razloga, jednostavno koristimo globalne promenljive, prosto zato što nas mrzi da uradimo potprograme kako treba. Takođe, može da se desi i da u ozbiljnim aplikacijama promenljive koje sadrže neke opšte parametre proglasimo za globalne, upravo ako imamo ogroman broj potprograma i svi koriste te iste parametre.
Kada je promenljiva globalna, to suštinsli znači da funkcija može menjati njenu vrednost i uticati (ponekad i nepredviđeno) na sam program i druge potprograme. Zamislite kakav bi haos nastao kada bismo dozvolili da npr. neka brojačka promenljiva iz ciklusa (recimo i ili j) bude globalna... Taj ciklus u kome bi se pozivao potprogram u kome bismo opet imali ciklus sa istim brojačem ne bi imao šanse da se izvrši kako treba!
U strogo tipiziranim programskim jezicima u kojima je neophodno unapred deklarisati promenljive, svaka promenljiva deklarisana na nivou glavnog programa je globalna, a svaka koja je deklarisana u funkciji je lokalna.
Situacija počinje da se komplikuje kod slabo tipiziranih jezika u koje spadaju JavaScript i PHP. Kod ovih jezika ne postoji nikakava deklaracija promenljivih unapred, već se promenljive prosto uvode po potrebi. Onda se postavlja pitanje da li je neka promenljiva u potprogramu lokalna ili globalna?
Odgovor na ovo pitanje zavisi od samog jezika. U JavaScriptu su sve promenljive u funkcijama globalne, osim onih koje se baš eksplicitno uvedu u funkciji ključnom rečju var ili let. Sa druge strane, u PHP-u su sve promenljive u funkcijama lokalne, osim onih koje eksplicitno ne navedemo u global listi.
U pseudo-kodu smo se držali prakse koja najviše podseća na PHP, s tim što deklarišemo globalne promenljive u global bloku. Na jednom mestu u glavnom programu deklarišemo globalne promenljive koje onda važe za sve funkcije. Inače su sve promenljive u funkcijama lokalne.
Samo da napomenemo, formalni parametri se uvek u funkciji tretiraju kao lokalne promenljive.
Važenje promenljivih
Ovde imamo sličan primer kao malopre - funkcija max() pronalazi najveću od tri vrednosti i vraća je kao rezultat. Međutim, ovaj put se funkciji ne prosleđuju parametri, već se traži najveća vrednost od tri globalne promenljive - a, b i c. Zbog toga smo ih na početku programa proglasili za globalne.
global {a,b,c}
function max() {
x = a;
if (x < b) { x=b; }
if (x < c) { x=c; }
return(x);
}
x = 100;
read(a,b);
c = 10;
rez = max() * 2;
write("Rezultat: " + rez);
write("X = " + x);
U glavnom programu se unose promenljive a i b, dok se promenljivoj c dodeljuje vrednost 10. Onda se poziva funkcija max() unutar izraza u kome se njen rezultat množi sa 2, s tim što ovaj put ne prosleđujemo nikakve parametre. Promenljive a, b i c su prepoznate u funkciji, kao globalne. Šta god im uradili unutar funkcije, odrazilo bi se i u glavnom programu.
Ovo nije dobra praksa. Primetite kako je u prošlom primeru funkcija max() bila nezavisna - mogli smo da joj prosledimo bilo koje tri vrednosti i da dobijemo najveću. Sada je funkcija vezana za globalne promenljive a, b i c. Kada želimo da je pozovemo, moramo prvo da ubacimo vrednosti u njih.
Pogledajte sada promenljivu x. Definisali smo je u glavnom programu i dodelili joj vrednost 100. Zatim je negde tokom izvršavanja programa došlo do poziva funkcije max() koja takođe koristi i menja vrednost promenljivoj x. Međutim, u funkciji je to lokalna promenljiva koja nema veze sa globalnim x. To i dokazujemo kada na kraju ispišemo vrednost x i vidimo da se nije promenila - i dalje je 100.