Funktsioonide parameetrid

Programmeerimiskeele C funktsioonid näevad välja järgmiselt:

/* funktsiooni deklaratsioon ehk prototüüp */
<väärtuse tüüp> <nimi>(<parameetrite loetelu>);
...
/*funktsioonid definitsioon */
<väärtuse tüüp> <nimi>(<parameetrite loetelu>)
{
 <kohalike muutujate defineerimine>
 <laused>
 return <arvutatud väärtus>
}
...
/* funktsiooni kasutamine mingis muus funktsioonis */
...
<nimi>(<tegelikud parameetrid>);
...

Funktsioon luuakse selliselt, et see arvutaks mingi uue väärtuse, lähtudes mingitest üleantud parameetritest. Vastasel juhul tuleks luua eraldi funktsioon iga võimaliku parameetrite kombinatsiooni kohta. Muidugi on mõeldavad ka funktsioonid, mis mingit väärtust ei arvuta (näiteks ekraanile väljastamine) ja funktsioonid, mis mingeid parameetreid ei vaja. Andmevahetuseks välja kutsutava funktsiooni ja teda kasutava funktsiooni vahel võib kasutada funktsiooni parameetreid või globaalseid muutujaid. Globaalsete parameetrite kasutamine on küll kõige kiirem viis andmevahetuseks, sest siis ei ole vaja üleantavaid väärtusi uude kohta kopeerida; kuid see viis on ka kõige ohtlikum. Globaalsete muutujate kasutamine muudab programmi halvasti struktureerituks ja lihtsustab vigade teket. Selliste parameetrite kasutamine raskendab ka loodud funktsioonide kasutamist mingis teises programmis. Teise programmi looja (tõenäoliselt teine programmeerija) peab sel juhul teadma, milliseid muutujaid ta peab oma programmis defineerima nende funktsioonide jaoks. Ta ei tohi kasutada muid samanimelisi muutujaid jne.

Kõige parem on, kui funktsiooniga vahetatakse andmeid ainult üle tema parameetrite. Siis on funktsioon nagu kindla sisuga moodul, mis loovutab vastavalt parameetritele kindla väärtuse ja ei mõjuta mingil määral oma "ümbrust".

Funktsiooni parameetrid võivad olla väärtused (value parameters) või viidad (reference parameters). Väärtusparameetrite puhul kopeeritakse funktsioonile üle antud muutuja väärtus kohalikku muutujasse - parameetrisse. Funktsiooni sees saab seda väärtust kasutada nagu iga teistki kohalikku muutujat, ainult et talle ei saa midagi omistada. Väärtusparameetrite abil ei saa mingeid väärtusi väljakutsujale tagastada. Selleks tuleb kasutada kas osuteid või funktsiooni väärtust.

Ajutiste vahetulemuste salvestamiseks võite kasutada funktsiooni kohalikke muutujaid. Peale funktsiooni töö lõppu eemaldatakse aga need muutujad mälust. Te ei tohi kunagi loovutada funktsiooni väärtusena funktsiooni oma kohaliku muutuja aadressi, sest selleks ajaks, kui vastuvõtja seda aadressi kasutab, on tema sisu juba ammu üle kirjutatud. Funktsiooni väärtus on samuti väärtusparameeter. Temale üleantud kohaliku muutuja või valemi väärtus kopeeritakse väljakutsujale tagasi.

Viitade kasutamisel kopeeritakse parameetrisse mingi muutuja aadress. Funktsioon on sunnitud sellise parameetri tegeliku väärtuse kasutamiseks valemis lisama parameetri ette operaatori *. Samal kombel saab sellise muutuja väärtust ka muuta. Tehtud muudatus salvestatakse nüüd kohe antud muutujas. Seega saab viitparameetrite abil ka väärtusi väljakutsujale tagastada. Seda kasutatakse sageli siis, kui funktsioon peab tagastama enam kui ühe väärtuse ja seega on funktsiooni enda väärtusest vähe. Viitparameetrite kasutamine on soovitav ka funktsioonile suurema hulga andmete üleandmisel. Siis ei ole enam vaja neid andmeid kuhugi kopeerida. Väiksemate andmekoguste üleandmisel on aga soovitav kasutada väärtusparameetreid, sest nende kasutamine toimub kiiremini.

Näiteks:

int funk1(int a,		/* väärtusparameeter */
          int* b)		/* viitparameeter */
{
  int	i;  /* kohalik muutuja. Tema väärtuse tagastamiseks tuleb */
	    /* funktsiooni väärtust või mingit viitparameetrit */

  for(i = 0; i < a; i++) 
    *b += i * 2;	/* iga viitparameetri sisuga tehtud muudatus on */
			/* otsekohe üle antud */
  return i;		/* nii antakse üle kohaliku muutuja väärtus */
}

Osa funktsioone omab muutuvat parameetrite arvu. Näiteks funktsioon printf() võib omada üht või mitut parameetrit. Muutuva arvu parameetrite kasutamine on veel üks C - keele tugevaid külgi. Sel kombel saab luua väga paindlikke funktsioone, mida saab rakendada väga erinevates olukordades. Programmeerimiskeeles PASCAL ei ole muutuva arvu parameetrite kasutamine võimalik. See on tingitud parameetrite üleandmise järjekorrast. Selle asemel on funktsiooni väljakutsumine programmeerimiskeeles PASCAL natuke kiirem ja loodud programm on natuke väiksem. Borland C translaator suudab aga päris harilikus C - keele funktsioonis kasutada programmeerimiskeele PASCAL parameetrite üleandmise korda. Selleks tuleb vaid enne funktsiooni nime lisada võtmesõna PASCAL, näiteks:

int PASCAL funk1(int a, char b)
{
 ...		/* funktsioonis endas ei muutu midagi */
		/* kogu muutunud üleandmise korraldab translaator */
}

Võtmesõna PASCAL vastandiks on võtmesõna cdecl, mis määrab, et funktsioon kasutab C - keele parameetrite üleandmise korda. Microsoft Windowsi loomisel kasutati suuremas hulgas funktsioonides programmeerimiskeele PASCAL parameetrite üleandmise korda ja säästeti sellega kuuldavasti umbes 10% koodi. Tavaliselt ei ole siiski mõtet kasutada PASCAL parameetrite üleandmise korda. Eriti suurt kiiruse suurenemist sellega ei saavuta. Kõige efektiivsem viis programmi kiiruse tõstmiseks on ikka ainult parema algoritmi kasutamine.

Muutuva hulga parameetritega funktsioonid (võtmesõna cdecl) on sageli väga kasulikud. Sama tulemust saab sageli saavutada ka ühe konstantse hulga parameetritega funktsiooni korduva kasutamisega, kuid see ei ole nii efektiivne. Muutuva hulga parameetritega funktsiooni loomiseks tuleb viimase määratud parameetri järele sisestada parameetriteloetelusse kolm punkti - ... . Näiteks:

int funk1(int i, ... ); /* üks täisarv ja tundmatu arv 
                           muid parameetreid */

Kuidagi tuleb funktsioonile ka teatada, kui palju ja millist tüüpi parameetreid talle üle anti. Muidu ei oska funktsioon neid parameetreid kasutada. Üks võimalus on kasutada formaadimäärangut nagu näiteks funktsioonis printf(). Sel juhul määrab esimene parameeter nii parameetrite hulga kui ka nende tüübi. Selline funktsioon on väga paindlik, kuid tema loomine on üsna keerukas. Nii kasutatakse veel kahte võimalust: esimene, kus parameetrite üldarvu määrab esimene parameeter ja teine, kus viimane parameeter omab erilist väärtust, näiteks nulli. Viimast võimalust kasutatakse palju operatsioonisüsteemi UNIX ühe graafilise kasutuskeskkonna Open Look funktsioonides. Millist võimalust te ka ei kasutaks, tuleb "liigsete" parameetrite lugemiseks kasutada vastavaid C - keele makrosid.

void va_start(va_list ap, viimane_kindel);
<tüüp>
va_arg(va_list ap, <tüüp>);
void va_end(va_list ap);

Makro va_start() seab viida ap esimesele "liigsele" parameetrile. Selleks, et ta teaks, millisest parameetrist alates parameetrite hulk võib muutuda, tuleb sellest makrole parameetri viimane_kindel abil teatada. Muutuva hulga parameetritega funktsioonid võivad sisaldada nii määratud kui määramatuid parameetreid. Määratud parameetrid peavad asuma kõik parameetrite loetelu alguses. Kui te näiteks defineerite funktsiooni

int funk1(int i, ...)

siis tuleks makrole va_start() loovutada teise parameetrina i. Nüüd näitab viit ap esimesele määramata parameetrile. Iga määramatu parameetri lugemiseks tuleb kasutada makrot va_arg(). Selle makro teine parameeter määrab, millist tüüpi muutujat hetkel loetakse ja makro väärtuseks on selle parameetri väärtus. See makro nihutab ka viida ap edasi järgmise määramatu parameetrini. Peale viimase määramatu parameetri lugemist tuleks kasutada makrot va_end(). Kui te seda ei tee, siis võib programmi käigus tekkida hulganiselt seletamatuid vigu. Translaator selle makro puudumist ei märka.

Näited:

/* esimene määratud parameeter määrab parameetrite üldarvu */
int Korrutis(int n, ...)	/* korrutab n täisarvu */
{
  int 	  k = 0, i;	
  va_list  ap;

  va_start(ap, n); /* sea viit esimesele määramatule parameetrile */
  for(i = 0; i < n; i++)	/* korruta parameetrid */
    k *= va_arg(ap, int);
  va_end(ap);
  return k;		        /* loovuta tulemus */
}
...
int	k;
...
k = Korrutis(3, 5, 6, 1);
...
/* sama näide - viimane parameeter omab erilist väärtust */
int Korrutis(int a, ...)	/* korrutab n täisarvu */
{
  int 	   k, i;	
  va_list  ap;

  va_start(ap, a); /* sea viit esimesele määramatule parameetrile */
  k = a;
  while(0 != (i = va_arg(ap, int)))	/* korruta parameetrid */
    k *= i;
  va_end(ap);
  return k;		            /* loovuta tulemus */
}