Tečaj C  - Poglavje  5

Funkcije, spremenljivke in prototipi

NAŠA PRVA UPORABNIŠKO DEFINIRANA FUNKCIJA

Primer programa ------> SUMSQRES.C

Naložite in si oglejte program SUMSQRES.C kot primer programa v C-ju s funkcijami. Pravzaprav to ni prva funkcija, ki smo jo do sedaj srečali, saj je program, ki smo ga do sedaj največ uporabljali funkcija, to je printf( ) funkcija. Printf( ) funkcija sodi v  knjižnico funkcij, ki je  že na voljo prevajalniku.

V tem poglavju bomo končno definirali, čemu služi vrstica 3, vendar šele, ko pridemo to četrtega primera, torej še malo počakajte in za sedaj to vrstico ignorirajte.

Pozorni bodite na izvajalni del tega programa, ki se začne v vrstici 10 z vrstico kode, ki pravi "header( );", to pa je način klicanja katerekoli funkcije. Oklepaj je potreben, saj C z njegovo pomočjo ugotovi, da je to funkcija, ne pa narobe postavljena spremenljivka. Ko program pride do te vrstice kode, pokliče funkcijo header(), njeni stavki se izvedejo, nato pa nas kontrola programa vrne na stavek, ki sledi temu klicu. Če nadaljujemo, pridemo do for zanke, ki se bo izvedel sedemkrat in ki ob vsakem prehodu skozi zanko kliče novo funkcijo, square( ). Nazadnje se bo poklicala in izvedla še funkcija ending( ). Za trenutek ignorirajte ime spremenljivke index v oklepaju klica funkcije square( ). Vidimo, da ta program pokliče header, sedemkrat poklice square, ter enkrat ending. Sedaj moramo te funkcije definirati.
 

DEFINIRANJE FUNKCIJ

Glavnemu programu sledi funkcija, ki se začne v vrstici 19, ki se drži vseh pravil, ki smo jih podali za glavni porgram, le da se imenuje header( ). To je funkcija, ki jo klicemo iz vrstice 19 v programu. Vsak od njenih stavkov se izvede, ko pa je to končano, nas kontrola vrne v glavni program ali bolj natančno, funkcijo main( ).

Prvi stavek dodeli spremenljivki sum vrednost nič, saj jo nameravamo uporabiti za seštevek vseh kvadratov. Ker spremenljivko sum definiramo pred glavnim programom, je dosegljiva vsem funkcijam, ki jih definiramo pozneje. Taki spremenljivki pravimo globalna spremenljivka, velja pa čez celoten program, tudi v novih funkcijah. Včasih  ji rečemo tudi datotečna spremenljivka, saj je dosegljiva po celotni datoteki. Več o dosegljivosti spremenljivk bo povedano na koncu tega poglavja. Stavek v vrstici 22 izpiše glavo na zaslon. Programska kontrola nas potem vrne v main() funkcijo, saj tu ni več stavkov, ki bi se morali izvesti. Tako pademo skozi dno funkcije in se vrnemo na stavek, ki se nahaja takoj za klicem te funkcije.

Moralo bi  biti jasno, da bi lahko dva izvajalna stavka iz te funkcije premaknili v glavni program, z njima nadomestili klic funkcije header(), pa bi program naredil natačno to, kot sedaj. To ne zmanjšuje vrednost funkcijam, pač pa enostavno prikaže delovanje te preproste funkcije. Videli boste, da so funkcije zelo dragocene v programiranju v C-ju.
 

PODAJANJE VREDNOSTI FUNKCIJAM (KLASIČNA METODA)

Če se vrnemo na glavni program, ali še bolj natančno, na for zanko, najdemo nov konstrukt s konca prejšnjega poglavja, to je index++ v vrstici 11. Koristno bi bilo, če bi se dobro spoznali s tem konstruktom, saj ga boste videli v večini C programov.

V klicu funkcije square( ), imamo dodano še eno zadevo, spremenljivko index v oklepaju. To je znak prevajalniku, da želite ob izvajanju te funkcije s seboj prenesti vrednost spremenljivke index za uporabo ob izvajanju te funkcije. Če pogledamo naprej na funkcijo square() v vrstici 26, vidimo da je v oklepaju te funkcije zapisana druga spremenljivka, number. To je ime, ki ga želimo uporabljati za klic spremenljivke za uporabo, ko izvajamo stavke znotraj te funkcije. Lahko jo imenujemo kakorkoli, le da se držimo pravil imenovanja identifikatorja in da ime ni rezervirana beseda. Ker mora funkcija vedeti, katerega tipa je spremenljivka, je ta definirana za imenom funkcije, vendar pred začetnim zavitim oklepajem. Torej stavek v vrstici 23  "int number;" pove funkciji, da bo podana vrednost tipa integer (celoštevilska). Ko smo vse to opravili, imamo vrednost index-a iz glavnega programa podano funkciji square(), vendar preimenovano v number, in tako dosegljivo za uporabo. To je klasični stil definiranja funkcijskih spremenljivk, ki je v uporabi že od prvih verzij jezika  C. Novejša in mnogo boljša metoda pa pridobiva na popularnosti zaradi njenih mnogih prednosti, obdelali pa jo bomo pozneje v tem poglavju.

Za začetnim zavitim oklepajem definiramo novo spremenljivko numsq za uporabo samo v tej funkciji (več o tem kasneje) in pričnemo s potrebnimi izračuni. Spremenljivki numsq priredimo vrednost kvadrata spremenljivke number, nato pa numsq prištejemo trenutni vsoti, ki se nahaja v spremenljivki sum. Iz prejšnjega poglavja se spomnimo, da izraz "sum += numsq;" pomeni isto kot "sum = sum + numsq;". V vrstici 33 izpišemo število in njegov kvadrat, nato pa se vrnemo v glavni program.

VEČ O PODAJANJU VREDNOSTI FUNKCIJAM

Ko smo vrednost spremenljivke index podali funkciji, se je zgodilo nekoliko več, kot se zdi na prvi pogled. Funkciji nismo podali spremenljivke index, pač pa smo ji podali kopijo njene vrednosti. Tako je izvirna vrednost zašcitena pred nenamerno zlorabo funkcije. Spremenljivko number bi lahko v funkciji square( ) spreminjali na katerikoli želeni način, ko pa bi se vrnili v glavni program, se vrednost spremenljivke index ne bi spremenila. Tako lahko zaščitimo spremenljivko pred zlorabo, ne moremo pa vračati njene vrednosti klicajoči funkciji iz klicane funkcije. Videli bomo, da obstaja dobro definirana metoda, ko bomo prišli do polj, in še ena, ko bomo obdelali kazalce. Do takrat pa bo edini način komunikacije s kličočo funkcijo s pomočjo uporabe globalne spremenljivke. Nakazali smo že nekaj stvari o globalnih spremenljivkah, več o njih pa bomo povedali pozneje v tem poglavju.

Če nadaljujemo v main( ) funkciji, pridemo do zadnjega funkcijskega klica, klica funkcije ending( ) v vrstici 13. Ta vrstica pokliče funkcijo, ki nima definiranih nobenih lokalnih spremenljivk. Izpiše sporocilo in vrednost spremenljivke sum, ki jo vsebuje in tako zaključi program. Program se konča z vrnitvijo v main() funkcijo in ker tam ne najde ničesar vec, se program zaključi. Prevedite in poženite ta program in si oglejte izpis.
 

SEDAJ PA PRIZNANJE MAJHNE LAŽI

Pred kratki sem  dejal, da je edini način prenosa vrednosti do kličoče funkcije z uporabo globalne spremenljivke, vendar pa obstaja še en način, katerega bomo obdelali, ko boste naložili in si ogledali program SQUARES.C. V tem primeru bomo videli, da je lahko vrniti posamezno vrednost iz klicane funkcije v kličočo funkcijo. Vendar še enkrat, če hocemo vrniti več kot eno vrednost, bomo morali spoznati polja ali pa kazalce.

Primer programa ------> SQUARES.C

V main() funkciji definiramo dve celoštevilski spremenljivki in začnemo s for zanko, ki se bo izvedla osemkrat. Prvi stavek v for zanki je "y = squ(x);", kar je nov in precej nenavaden konstrukt. Iz prešnjih izkušenj vam
verjetno ni tež;ko sklepati, da je del stavka squ(x) klic funkcije squ(), ki s seboj vzame vrednost x-a kot parameter. Če pogledamo naprej v vrstico 20, vidimo, da funkcija vhodno spremenljivko imenuje raje input in njegovo vrednost kvadrira, rezultatu pa da ime square. V vrstici 26 pa se pojavi nova vrsta stavka, return stavek.  Vrednost v oklepaju je podana sami funkciji in se vrne kot vrednost, ki jo lahko uporabimo v glavnem programu. Tako funkcijskemu klicu  "squ(x)priredimo vrednost kvadrata, ki jo potem vrnemo v glavni program tako, da dobi y vrednost kvadrata. Če bi torej pred klicem x-u dodelili vrednost 4, bi y zaradi vrstice 10 dobil vrednost 16.

Oklepaj okrog vračajoce vrednosti v vrstici 26 ni nujno potreben, vendar ga večina izkušenih programerjev vseeno zapiše.

Drug način, kako si to lažje predstavljamo je, da si mislimo, da je izraz squ(x) samo še ena spremenljivka, ki ima vrednost  x2 in jo lahko postavimo kamorkoli lahko postavimo spremenljivko tega tipa. Vrednosti spremenljivk x in y nato še izpišemo.

Da bi prikazali, da lahko izraz squ(x) res uporabljamo kot navadno spremenljivko, je v vrstici 14 zapisana še ena for zanka, kamor namesto imena spremenljivke v printf() stavek postavimo kar klic funkcije.

Še eno zadevo je treba omeniti. Tip vrednosti, ki jo funkcija vrača, moramo definirati, da bi imeli smiselne podatke, če pa tega ne storimo, prevajalnik definira vrednost kot tip int. Če želimo imeti katerega od drugih tipov, moramo to izrecno definirati. Kako to storimo, bo prikazano v naslednjem programu. V tem programu uporabljamo kar vrednost, ki nam jo definira prevajalnik.

Prevedite in poženite ta program, ki prav tako uporablja klasično metodo definiranja funkcijskih spremenljivk. Še enkrat, zanemarite vsa
opozorila.
 

FUNKCIJE, KI VRACAJO ŠTEVILA S PLAVAJOČO VEJICO

Primer programa ------> FLOATSQ.C

Naložite program FLOATSQ.C kot primer klasično definirane funkcije, ki vrača vrednost tipa float. Začne se z definicijo globalne spremenljivke s plavajočo vejico z, katero bomo uporabili kasneje. nato v glavnem programu definiramo celoštevilsko spremenljivko in nato še dve spremenljivki s plavajočo vejico, sledita pa še dve nenavadni definiciji. Izraza sqr() in glsqr() izgledata kot klica funkcij. To je pravilen način definicije, da funkcija ne bo vračala vrednosti tipa int, pač pa nekega drugega tipa, v tem primeru float. To pove prevajalniku, da bo vrednost, prenešena iz teh dveh funkcij, tipa float. To je, še enkrat, klasična metoda definiranja, in je že zastarel. Bodite pozorni, da iz vrstice 9 ne kličemo nobene izmed teh dveh funkcij, pač pa le deklariramo tip, ki jih bosta vračali.

Sedaj si oglejte funkcijo z imenom sqr(), ki se začne v vrstici 29 in videli boste, da pred imenom funkcije stoji rezervirana beseda float. To je znak prevajalniku, da bo funkcija vračala vrednost tipa float vsakemu programu, ki jo bo klical. Tip, ki ga funkcija vrača, je sedaj kompatibilen z njenim klicem. Vrstica, ki sledi imenu funkcije, vsebuje izraz float inval;, kar je znak prevajalniku, da bo vrednost podana tej funkciji iz klicanega programa tipa float. Ker smo klicatelju povedali, da bomo vračali tip float, to v resnici storimo v vrstici 35 tako, da se vse ujema.

Funkcija glsqr(), z začetkom v vrstici 39, bo prav tako vračala tip float, vendar pa za vhod uporablja globalno spremenljivko. Kvadriranje izvaja v samem return stavku in zato ne rabi še ene spremenljivke, kamor bi shranjevala rezultat. Podobno bi lahko delovala tudi funkcija sqr(), vendar smo to storili posebej, da bi pokazali, da se da to storiti tudi tako.

Splošna oblika tega programa vam ne bi smela delati problemov in je zato ne bomo podrobneje obravnavali. Kot je priporočljivo z ostalimi primeri, tudi tega prevedite in poženite in zanemarite vsa opozorila, ki jih boste dobili.
 

KLASIČNI STIL

Trije programi, ki smo jih obdelali do sedaj v tem poglavju uporabljajo klasični stil definiranja funkcij. Čeprav je bil to prvi stil definiran v C-ju, ga hitro zamenjuje modernejša metoda definicij funkcije, saj le-ta veliko bolje odkriva in označuje napake. Ko boste brali članke v C-ju, boste zasledili klasično metodo, zato jo morate znati prebrati. To je razlog za vključitev tega stila v tečaj. Priporočil pa bi vam, da se naučite in uporabljate novejšo metodo, katero bomo kmalu obdelali. Svetujem vam celo, da v svojem programiranju ne uporabljate klasičnega
stila.

Knjiga avtorjev Kernigan in Ritchie-a, "The C Programming Language - Second Edition" je najbolj primerno berilo o klasičnem stilu.

Preostanek tega tečaja bo uporabljal modenro metodo, kot je predlagana in definirana v ANSI-C standardu. Če imate starejši prevajalnik, se vam lahko zgodi, da kateri od primerov ne bodo delovali, zato je na vas, da jih spremenite v klasični stil. Nazadnje je ANSI-C standard uporabljan tako splošno, da bi bilo pametno, da si nabavite prevajalnik kompatibilen z ANSI-C standardom, če tega še niste storili.
 

TIP, KI GA VRAČA FUNKCIJA main( )

V izvirni K&R definiciji C-ja, so vse funkcije standarno vračale tip int, če avtor ni zahteval česa drugega. Ker je bilo ekspicitno definiranje vračajocega tipa izbirno, je bila večina C-ja napisanega na ta način:

   main()
   {
      ...
   }
Ko pa so bili v jezik dodani prototipi (katere bomo obdelali kmalu), pa so nekateri programerji smatrali, da main() funkcija ne vrača ničesar, zato so za tip vračanih vrednosti uporabili tip void, zato je kmalu postala običajna praksa pisati main() funkcijo na naslednji način:
   void main()
   {
      ...
   }
Ko je bil ANSI-C standard končan, je bil edini dovoljeni tip vračanih vrednosti tip int. Dober prevajalnik bo preveril, če program v resnici vrača vrednost tako, da bo zahteval, da se iz vsake izstopne točke vrača vrednost tipa int. To je vodilo do naslednje oblike funkcije main():
   int main()
   {
      ...
      return 0;
   }
Očitno zaradi vztrajnosti uporabe tipa void, je veliko piscev prevajalnikov vključilo void kot tip vračanih vrednosti kot razširitev, da bi lahko uporabljali kode brez sprememb.  Nekateri prevajalniki zato podpirajo tip void, vendar pa je tip int edini odobren s strani ANSI-C standarda.

Da bi svojo kodo naredili kar se da prenosljivo, uporabljajte kodo, zadnjo omenjeno zgoraj.

Končno lahko vidite, zakaj smo v programe dodajali vrstico, ki vrača vrednost nič operacijskemu sistemu. To je namreč znak operacijskemu sistemu, da se je program izvršil normalno.
 

OBMOČJE VELJAVNOSTI SPREMENLJIVK

Primer programa ------> SCOPE.C

Naložite naslednji program, SCOPE.C, in si ga oglejte za razlago območja veljavnosti spremenljivke v programu. Vrstice od 2 do 5 lahko zenkrat zanemarite, saj jih bomo obdelali pozneje. Za ta program bomo porabili kar nekaj časa in pokrili veliko novih tem. Mnoge od njih se vam ne bodo zdele uporabne, vendar jih skušajte razumeti, saj so zelo pomembne.
 

KAJ JE GLOBALNA SPREMENLJIVKA?

Spremenljivka, definirana v vrstici  7, je globalna spremenljivka, imenovana count in je dostopna katerikoli funkciji, saj je definirana pred vsemi funkcijami. Vedno je dostopna zato, ker obstaja celoten izvajalni čas programa. (To bo razloženo v kratkem.) Nižje v programu je v vrstici 29 definirana še ena globalna spermenljivka counter, ki je prav tako globalna, ni pa dostopna main() funkciji, saj je deklarirana za njo. Globalna spremenljivka je vsaka spremenljivka, ki je deklarirana izven katerekoli funkcije. Včasih se te spremenljivke omenja tudi kot zunanje spremenljivke, saj so zunanje vsem funkcijam, včasih pa jim pravijo tudi datotečne spremenljivke.

Globalnim spremenljivkam se avtomatsko dodeli vrednost nič ob deklaraciji. Zato bosta spremenljivki count in counter obe inicializira na vrednost nič.

Vrnite se na main() funkcijo in videli boste spremenljivko index definirano kot tip int. Za trenutek pozabite besedo register. Spremenljivka je dosegljiva le v main() funkciji, kjer je deklarirana. Dodatno, je tudi avtomatska spremenljivka, kar pomeni, da začne obstajati, ko se izvršuje funkcija, v kateri je definirana, in preneha obstajati, ko je izvajanje funkcije končano. To v tem konkretnem primeru ne pomeni ničesar, saj je funkcija main() vedno aktivna, tudi ko prepusti programsko kontrolo drugi funkciji. V zavitem oklepaju, ki označuje for zanko, je definirana še ena celoštevilska spremenljivka stuff. Vsak pa zavitih oklepajev lahko vsebuje deklaracije spremenljivk, ki bodo dostopne le takrat, ko bo program izvrševal stavke, ki se nahajajo med tema dvema zavitima oklepajema. Te spremenljivke bodo avtomatične in bodo prenehale obstajati, ko se prenehajo izvajati stavki v zavitem oklepaju. Spremenljivka stuff se bo torej kreirala in uničila osemkrat, enkrat ob vsakem prehodu skozi zanko.
 

VEČ O AVTOMATSKIH SPREMENLJIVKAH

Oglejte si funkcijo head1() v vrstici 30, ki izgleda nekoliko čudno, saj je beseda void uporabljena dvakrat. Smisel uporabe te besede bo razložen kasneje. Funkcija vsebuje spremenljivko index, ki z spremenljivko index iz main() funkcije nima ničesar skupnega, razen tega, da sta obe avtomtski spremenljivki. Ko program ne izvaja stavkov te funkcije, spremenljivka index sploh ne obstaja. Ko pokličemo funkcijo head1(), se spremenljivka generira, ko pa funkcija izvede vse svoje stavke, pa spremenljivka index iz funkcije head1() preneha obstajati. (Avtomatske spremenljivke se shranijo na sklad, kar bomo obdelali kasneje.) Zapomnite pa si, da to nima nobenega učinka na spremenljivko index v funkciji main(), saj je ta čisto druga zadeva.

Avtomatske spremenljivke so torej avtomatično kreirane, ko jih potrebujemo, nato pa so tudi avtomatsko uničene. Pomembna stvar, ki si jo je treba zapomniti je ta, da moramo po nekem klicu funkcije, ko jo kličemo še enkrat, ponovno inicializirati avtomatske spremenljivke, saj se njihove vrednosti ne ohranijo.
 

KAJ SO STATIČNE SPREMENLJIVKE?

Sedaj moramo omeniti še en tip spremenljivk, statične spremenljivke. S tem, ko pred deklaracijo spremenljivke v funkciji postavimo rezervirano besedo static, postane ta spremenljivka oziroma te spremenljivke statične, kar pomeni, da bodo obstajale od enega klica do drugega. Statična spremenljivka je inicializirana enkrat, ob naložitvi programa, in se med inicializacijo programa nikoli ne reinicializira.

Če pa rezervirano besedo static postavimo pred zunanjo spremenljivko, to je tisto, ki nastopa izven vseh funkcij, jo naredimo privatno in je ni možno uporabljati v drugih datotekah. (To je popolnoma drugačna uporaba enake rezervirane besede.) To nakazuje, da je možno zunanje spremenljivke uporabljati v drugih, posebej prevedenih datotekah, kar je res možno. Primeri take uporabe bodo podani v poglavju 14. Ne bodo pa prikazani tukaj.
 

PONOVNA UPORABA ISTEGA IMENA

Preidite na funkcijo head2(). Vsebuje drugo definicijo spremenljivke count. Čeprav je bil count že definiran kot globalna spremenljivka v vrstici 7, ni ničesar narobe z uporabo enakega imena spremenljivke v tej funkciji. To je popolnoma nova spremenljivka, ki nima nobene zveze s tisto globalno, povzroči pa, pa globalna spremenljivka count ni dosegljiva v tej funkciji. To omogoča pisanje programov, kjer  pri pisanju funkcij ni potrebno skrbeti, če je ime že bilo uporabljeno za kakšno od globalnih spremenljivk, saj ne more priti do imenskega konflikta. Morate le poskrbeti za spremenljivke, ki potem s to funkcijo komunicirajo.
 

KAJ JE REGISTRSKA SPREMENLJIVKA?

Da izpolnimo prej dano obljubo, bomo sedaj definirali register tip spremenljivke. Računalnik lahko podatke hrani v registrih ali pa v pomnilniku. Registri so veliko hitrejši, vendar pa je le nekaj registrov na voljo programerju. Če v  programu nastopajo spremenljivke, ki se zelo intenzivno uporabljajo, lahko računalniku naročite, da bo te spremenljivke shranil v registre in tako nekoliko pospešite delovanje programa. Kako to storite, je prikazano v vrstici 11. Vaš prevajalnik vam verjetno omogoča deklaracijo ene ali več registrskih spremenljivk, če pa jih zahtevate več, pa bo nadaljne zahteve ignoriral. V dokumentaciji za  prevajalnik bi moralo pisati, koliko registrskih spremenljivk omogoča. Prav tako bi moralo pisati, katere tipe spremenljivk lahko shranite v register. Če pa prevajalnik ne omogoča uporabe registrskih spremenljivk, se bo zahteva po hranjenju v registru preprosto ignorirala.
 

KAJ JE UPORABA PROTOTIPOV?

Prototip je nek model resnične stvari in ko programirate v C-ju, imate možnost definirati model vsake funkcije za vaš prevajalnik. Le-ta lahko uporabi modele, da preveri vse klice funkcij in tako ugotovi, če ste uporabili pravilno število argumentov v klicu funkcije in če so ti pravilnega tipa. Z uporabo prototipov, dosežete, da prevajalnik nekoliko bolj preverja, če so v programu kakšne napake. ANSI-C standard vsebuje prototipe kot del priporočljivega standarda. Vsak ANSI-C prevajalnik ima možnost uporabe prototipov, zato bi bilo dobro, da bi se jih naučili uporabljati. Veliko več o prototipih bomo spoznali kasneje.

Če se vrnem na vrstice 3,4 in 5 v SCOPE.C, najdemo prototipe za vse tri funkcije, ki jih program vsebuje. Prvi void v vsaki vrstici sporoča prevajalniku, da te funkcije ne vračajo nobenih vrednosti, tako, da bi prevajalnik stavek  index = head1(); označil kot napako, saj funkcija ne vrača ničesar, kar bi lahko priredili spremenljivki index. Beseda void v oklepaju pove prevajalniku, da ta funkcija ne potrebuje nobenih parametrov in, če bi bila tu zapisana spremenljivka, bi bila to napaka, prevajalnik pa bi javil opozorilo. Če bi torej napisali stavek head1(index); bi to bila napaka. To vam omogoča, da izvajate preverjanje tipov podobno kot pri programiranju v Pascalu, Moduli-2 ali Adi, čeprav je preverjanje tipov v C-ju relativno šibko.

Že zdaj bi morali začeti uporabljati prototipe za preverjanje za vse funkcije, ki jih definirate. Vaš prevajalnik ima morda tudi možnost, ki bo zahtevala prototip za vsako funkcijo.  Preglejte vašo dokumentacijo, kako to storite. Uporaba prototipov bo uporabljena skozi celoten tečaj. Če prevajalnik ne podpira prototipov in moderne metode definiranja funkcij, boste morali spremeniti preostale primere. Veliko boljša rešitev pa bi bila nabava novega prevajalnika.  .

Vrstica 2 programa SCOPE.C pove sistemu, da naj gre v standardni direktorij, kjer so shranjene vključitvene datoteke in vzame kopijo datoteke stdio.h, ki vsebuje prototipe za standardne vhodne in izhodne funkcije, da jih boste lahko preverili, če vsebujejo pravilne tipe spremenljivk. Ne skrbite še preveč glede include stavka, razložen bo pozneje. Zagotovo prevedite in poženite ta program. .
 

STANDARDNE FUNKCIJSKE KNJIŽNICE

Vsak prevajalnik dobite skupaj z nekaj standardnimi, že definiranimi funkcijami, ki so pripravljene za uporabo. To so večinoma vhodno/izhodne funkcije, funkcije za znakovno manipulacijo ter manipulacijo z nizi, in pa matematicne funkcije. Veliko od teh funkcij si bomo ogledali v poznejših poglavjih. Prototipi so že definirani s strani avtorja prevajalnika za vse funkcije, ki ste jih dobili poleg  prevajalnika. Večina prevajalnikov ima že definirane še neke dodatne funkcije, ki omogočajo programerju, da dobi od svojega računalnika, kar je največ mogoče. V primeru IBM-PC in kompatibilnih računalnikov so to funkcije, ki omogočajo uporabo BIOS-ovih servisov ali  pisanje direktno na zaslon monitorja ali pa na katerokoli mesto v pomnilniku. Te funkcije tu ne bodo podrobneje predelane, saj boste te edinstvene lastnosti lahko obdelali sami. Nekateri primeri teh funkcij pa so uporabljeni v primerih v poglavju 14.
 

KAJ JE REKURZIJA?

Primer programa ------> RECURSON.C

Rekurzija je še ena izmed tistih programskih tehnik, ki se vam na začetku zdijo zelo nerazumljive na prvi pogled, če pa si ogledate program RECURSON.C, pa  bomo to tehniko podrobno spoznali. To je verjetno najbolj enostaven rekurzijski program, kar se jih da napisati, in je v resnični praksi neuporaben program, za namen prikaza pa je odličen.

Rekurzija ni nič drugega kot funkcija, ki kliče samo sebe. Zato je v zanki, ki mora imeti nek način, kako naj se konča. V program, ki ga trenutno gledate, v vrstici9 spremenljivki index priredimo vrednost 8, in jo uporabimo kot argument funkcije count_dn(). Funkcija enostavno dekrementira spremenljivko, jo izpiše skupaj z nekim sporočilom, in če je spremenljivka večje od nič, pokliče samo sebe, zopet dekrementira spremenljivko, jo izpiše itd. Nazadnje bo spremenljivka dosegla vrednost nič in funkcija bo prenehala s klicanjem same sebe. Namesto tega se bo vrnila na prejšnji klic, nato še na prejšnjega, dokler nas ne bo nazadnje vrnila v main() funkcijo in od tam v operacijski sistem.

Za namen razumevanja si lahko mislite, da imate na voljo osem kopij funkcije count_dn() in kličete vsako posebej tako, da vodite evidenco, katera je trenutno na vrsti. To ni čisto enako temu, kar se je v resnici zgodilo, je pa precej dobra ilustracija, da bi razumeli, kar se je zgodilo.
 

KAJ JE REKURZIJA NAREDILA?

Vseeno je potrebna boljša razlaga, kaj se je zgodilo. Ko ste poklicali funkcijo iz sebe, je vse spremenljivke in vse notranje zaznamke, ki jih potrebuje, shranila nekam v nek sklop. Ko je naslednjič poklicala samo sebe, je storila isto, shranila naslednji sklop vsega, kar potrebuje, da konca klic funkcije. Nadaljevala je z kreiranjem in shranjevanjem teh sklopov, dokler ni dosegla zadnjega klica, ko je zacela te sklope dobivati nazaj in jih uporabljati za kompletiranje vseh funkcijskih klicev. Sklopi so se shranili v notranji del računalnika, ki se imenuje sklad. To je del pomnilnika skrbno organiziranega za hranjenje tako opisanih podatkov. Izven dosega tega programa je podrobno opisovanje sklada, bilo pa bi v dobro vaših programerskih izkušenj, da si preberete nekaj čtiva o skladu. Sklad se v skoraj vseh modernih racunalnikih uporablja za notranja "gospodinjska" dela.

Pri uporabi rekurzije, boste morda želeli napisati program, ki uporablja posredno rekurzijo, ki je nasprotje neposredne rekurzije uporabljene tukaj. Pri posredni rekurziji bi funkcija A poklicala funkcijo B, ki bi spet poklicala funkcijo A,itd. To je popolnoma legalno, sistem bo poskrbel, da po postavil potrebne stvari na sklad in jih z njega spet jemal, ko bodo potrebne. Ni razloga, da ne bi imeli tri funkcije, ki bi druga drugo klicala v krogu, ali tri ali štiri. C-jevski prevajalnik bo namesto vas poskrbel za vse podrobnosti. .

Stvar, ki si jo morate pri rekurziji zapomniti, da mora nekaj v nekem trenutku postati nič, ali doseči neko prej definirano točko, kjer se bo zanka končala. Če tega ne boste storili, boste dobili neskončno zanko, sklad se bo napolnil, povzročil napako in ustavil program nekoliko burno.
 

ŠE EN PRIMER REKURZIJE

Primer programa ------> BACKWARD.C

Program BACKWARD.C vsebuje še en primer rekurzije, zato ga nažite in si ga oglejte. Program je podoben zadnjemu, le da uporablja znakovno polje. Vsak zaporedni klic funkcije forward_and_backwards() povzroči izpis enega znaka. Prav tako se po en znak izpisuje vsakič, ko se funkcija konča, tokrat v obratnem vrstnem redu, ko sledimo nizu rekurzivne funkcije.

Ta program uporablja moderno metodo definiranja funkcij in vključuje vse definicije prototipov. Moderna metoda definiranja funkcij premakne tipe spremenljivk v oklepaj skupaj z samim imenom spremenljivke. Končni rezultat je, da izgleda taka vrstica podobno kot ustrezne vrstica v jezikih, ki imajo močnejše preverjanje tipov kot so Pascal, Modula-2, ali Ada. Prototip v vrstici 5 je enostavno kopija glave funkcije v vrstici 20, sledi pa mu še podpičje. Razvijalci C-ja so celo omogočili, da v oklepaj prototipa postavite ime spremenljivke, ki ga prevajalnik sicer ignorira, vendar pa iz takega zapisa vidite, kako spremenljivka deluje, ter ima tako vlogo komentarja.

Ne obremenjujte se z znakovnim poljem ali drugim novim gradivom predstavljenim tukaj. Ko boste predelali sedmo poglavje, vam bo ta program popolnoma jasen. Bilo je namrec važno, da se prikaže še en primer rekurzije, zato je ta primer vključen tu. Opazili boste, da program naredi nekaj uporabnega z rekurzijo, vendar pa bi program z lahkoto napisal brez rekurzije. Pozneje si bomo ogledali nekaj programov, kjer je rekurzija nujno potrebna.

Prevedite in poženite ta program in opazujte rezultate.
 

PROGRAM KVADRIRANJA ŠTEVIL S PLAVAJOČO VEJICO S PROTOTIPI

Primer programa------> FLOATSQ2.C

Naložite in si oglejte program FLOATSQ2.C, ki je natančna kopija programa FLOATSQ.C, ki smo si ogledali prej, le da ima dodane prototipe. Uporaba prototipov je zadeva, ki je priporočljiva za učenje vsem C-jevskim programerjem.

Nekaj stvari je pri tem programu treba poudariti. Prvič, besede float na začetku vrstic 32 in 41 so znak prevajalniku, da so to funkcije, ki vračajo vrednosti tipa float. Prav tako, ker so prototipi funkcij zapisani pred main( ) funkcijo, jih v vrstici 12 ni potrebno definirati, kot smo to storili v vrstici 9 v programu FLOATSQ.C, na začetku tega poglavja. Bodite pozorni, da je tudi tip spremenljivke inval zapisan v oklepaju v vrsticah 4 in 32.

Ko boste prevedli in pognali ta program, zanemarjujoč vsa opozorila, zbrišite parameter iz vrstice 17 in poglejte kakšno sporočilo napake boste dobili.
 

NEJASEN PROBLEM, KI SE VČASIH POJAVI

Recimo, da bi v program FLOATSQ.C zapisali naslednjo vrstico kode;,

   printf(" ... ", sqr(5.0), glsqr());
Morda je presenetljivo, vendar pa je vrstni red klicanja funkcij, kar se tiče ANSI-C standarda, nedefiniran. Nek prevajalnik bo morda najprej poklical sqr() funkcijo, drugi pa funkcijo glsqr(), obe metodi sta namreč pravilni. V tem primeru ni pomembno, katera se pokliče najprej, v nekaterih primerih pa to je pomembno, recimo, da bi nekaj želeli izpisati iz obeh funkcij. Rezultat vsakega klica bo uporabljen na pravi lokaciji, vrstni red izvajanja pa je nedefiniran. Bodite prepričani, da se bo to vprašanje prej ali slej pojavilo tudi v vašem programiranju, zato se ga morate zavedati.
 

ŠE VEC STILSKIH PREDLOGOV

Primer programa ------> STYLE2.C

Primer imenovan STYLE2.C je podan kot ilustracija različnih načinov formatiranja funkcije. Opazili boste različne nacine definiranja vhodnega parametra. Primera tri in štiri oba uporabljata enak stil, vendar četrti primer prikazuje stil, ko funkciji ničesar ne podajamo, niti nam ničesar ne vrača. Slog pisanja zelo jasno nakazuje, da funkcija ničesar ne potrebuje in ničesar ne vrača, zato tega ne moremo razlagati kot to, da smo nekaj izpustili. Porabite nekaj časa, da preučite te primere, nato pa začnite razvijati slog, ki ga boste uporabljali. Če ste kot večina programerjev, boste razvili slog, ki ga nameravate uporabljati vse svoje življenje, nato pa ga boste zamenjali vsakih nekaj mesecev ali pa ob vsakem novem
projektu.

     
NALOGE IZ PROGRAMIRANJA
      1. Še enkrat napišite TEMPCONV.C iz enega prejšnjih poglavij in prestavite
         računanje temperature v funkcijo.
      2. Napišite program, ki desetkrat izpiše vaše ime na zaslon s klici
         funkcije, ki ime izpisuje. Pomaknite klicano funkcijo pred main
         funkcijo, da ugotovite, če vaš prevajalnik to omogoca.
      3. Dodajte prototipe v programa SUMSQRES.C and SQUARES.C, in spremenite
         definicije funkcij v moderno metodo.
Povratek na kazalo

Prehod na poglavje 6


Copyright  1988-1997 Coronado Enterprises
Prevedel: Sašo Kuntarič