|
Funkties en Variabelen
Definitie van funkties
Bestudeer het programma KWADRSOM.C als voorbeeld van een C programma
met funkties. Eigenlijk is dit niet de eerste keer dat we een funktie
tegenkomen, omdat het main programma, dat we tot nu toe
steeds gebruikten, technisch gezien ook een funktie is. Hetzelfde geldt
voor de printf() funktie. Deze laatste is een funktie uit
een funktie-bibliotheek die geleverd werd bij de compiler.
int som; /* Dit is een globale variabele */
main ()
{
int index;
kop (); /* Roep kop aan */
for (index = 1; index <= 7; index++)
kwadr (index); /* Kwadraat funktie */
voet (); /* Roep voet aan */
}
kop () /* Dit is de 'kop' funktie */
{
som = 0; /* Initialisatie */
printf ("Getallen kwadrateren en optellen ...\n\n");
}
kwadr (invoer) /* Deze funktie geeft het kwadraat van een getal */
int invoer;
{
int kwadraat;
kwadraat = invoer * invoer;
som += kwadraat;
printf ("Het kwadraat van %d is %d\n", invoer, kwadraat);
}
voet () /* Dit is de 'voet' funktie */
{
printf ("\nDe som der kwadraten is %d\n", som);
}
Let eens op de statements in dit programma. Het bevat een regel met als
inhoud kop(). Op deze manier wordt in C een funktie
aangeroepen. De haakjes zijn verplicht, omdat de C compiler ze gebruikt
om te ontdekken dat het hier om een funktie aanroep gaat en niet een
foutieve declaratie van een variabele.
Zodra het programma bij dit statement aankomt, wordt de funktie met de
naam kop aangeroepen, de statements van die funktie
uitgevoerd en teruggekeerd naar het statement volgend op de aanroep. In
dit geval is dat een for lus die zeven keer wordt doorlopen. Binnen de
lus wordt een andere funktie aangeroepen met de naam kwadr.
Tenslotte wordt nog een funktie met de naam voet aangeroepen
en uitgevoerd. Vergeet op dit moment even de variabele index
die tussen de haakjes van de funktie aanroep van kwadr()
staat. Samenvattend zien we dus dat dit programma een funktie voor een
kopregel aanroept, dan zeven keer de kwadraat funktie en tenslotte een
funktie voor een voetregel.
De kop() funktie is opgebouwd volgens de regels die we tot
nu toe voor main() gezien hebben. Het enige verschil is dat
de naam anders is. Hetzelfde geldt voor de kwadr en de
voet() funkties.
Het eerste statement van kop() maakt de variabele
som gelijk aan nul. Deze variabele zullen we gebruiken om de
som der kwadraten in op te slaan. Omdat de variabele som
gedeclareerd werd voor de eerste funktie, is hij beschikbaar
in alle funkties van het programma. Het is een zogenaamde
globale variabele en zijn scope is het hele programma, in
dit geval alle funkties. Over de scope van variabelen wordt later
uitgebreider ingegaan. Het tweede statement drukt een kopregel af. De
funktie eindigt en keert terug naar main().
In principe kunnen de twee statements van kop() ook direct
in main() gezet worden en de funktie aanroep in zijn geheel
vervangen. Het programma doet dan nog precies hetzelfde. Het is gedaan
om de werking van funkties uit te leggen. Funkties zijn namelijk zeer
waardevolle instrumenten in de programmeertaal C.
Parameter overdracht
In dit programma is meteen gebruik gemaakt van hetgeen je in de vorige
les geleerd hebt. Om de for lus een aantal malen te doorlopen wordt een
teller opgehoogd. In dit geval is dat de variabele index
middels de C constructie index++.
De aanroep van de kwadraat funktie bevat iets nieuws, namelijk de
variabele index++ tussen de haakjes. Voor de compiler is dit
een indicatie dat bij de aanroep van kwadr() de waarde van
de variabele index moet worden overgedragen aan de funktie.
De funktie kan er dan verder mee rekenen.
Kijken we dan naar de funktie kwadr() dan vinden we ook daar
de naam van een variabele tussen de haakjes, hier invoer.
Dit is de naam van de variabele zoals we hem in de funktie graag willen
hanteren. Het mag elke naam zijn, zolang hij maar aan de regels van een
identifier voldoet. De funktie moet weten van welk type de variabele
is. De declaratie wordt gedaan na de haakjes en
voor de openings accolade. De programma regel int
invoer; vertelt de funktie dat de doorgegeven parameter van het
type integer is. Met dit alles hebben we bereikt dat de waarde van
index uit main is overgedragen aan
kwadr en terecht is gekomen in variabele invoer.
In de kwadraat funktie zelf wordt de variabele kwadraat
gedeclareerd, puur voor gebruikt binnen de funktie zelf. De funktie
berekend het kwadraat van de doorgegeven parameter en kent de waarde
daarvan toe aan invoer. Tevens wordt de som der kwadraten
bijgewerkt. De ingevoerde waarde en zijn kwadraat worden afgedrukt en
vervolgens wordt teruggekeerd naar het aanroepende programma.
Bij de aanroep van de kwadraat funktie werd niet de variabele
index zelf doorgegeven, doch een kopie gemaakt. De variabele
index wordt daarmee beschermd tegen overschrijven en blijft
onaangetast. De variabele invoer mag dus worden gewijzigd,
hij is eigendom van de kwadraat funktie. Op deze manier is het echter
niet mogelijk een eventueel gewijzigde waarde terug te geven aan de
aanroepende funktie. Hoe dat gaat zullen we hierna behandelen. Nu
zullen we ons behelpen met globale variabelen, waarover later in dit
hoofdstuk nog wat meer zal worden verteld.
Tenslotte wordt in het programma nog de funktie voet()
aangeroepen. Deze funktie bevat zelf geen variabelen en drukt slechts
een voetregel af met de waarde van de som der kwadraten. Na
voet() is er niets meer te doen en eindigt het programma.
Compileer en test dit programma nu.
Nogmaals parameter overdracht
We hebben geleerd dat we waarden vanuit een funktie terug kunnen geven aan een
aanroepend programma met behulp van globale variabelen. Het kan echter mooier
en om dat te laten zien moet je nu het programma KWADRAAT.C bestuderen.
In dit programma zie je hoe een enkelvoudige waarde wordt teruggegeven aan
een aanroepend programma. Voor een samengestelde waarde echter hebben we
een andere oplossing nodig. Deze zal worden besproken in de hoofdstukken
over tabellen en pointers. main () { int x, y; for (x = 0; x <= 7;
x++) { y = kwadr (x); /* Haal het kwadraat van x */ printf ("Het kwadraat
van %d is %d\n", x, y); } for (x = 0; x <= 7; ++x) { printf ("Het kwadraat
van %d is %d\n", x, kwadr (x)); } } kwadr (invoer) /* Deze funktie geeft
het kwadraat van een getal */ int invoer; { int kwadraat; kwadraat = invoer
* invoer; return (invoer); } In main() worden twee variabelen
gedeclareerd en begint een for lus die acht keer wordt doorlopen. Het eerste
statement van de for lus luidt y = kwadr (x);, hetgeen nieuw voor
je is. Het deel kwadr (x) moet niet al te veel problemen opleveren;
het is de aanroep van een kwadraat funktie met x als parameter. Kijk
eens naar de funktie zelf. Je ziet dat de parameter hier invoer wordt
genoemd en dat de waarde met zichzelf vermenigvuldigd aan de variabele kwadraat
wordt toegekend.
Dan volgt er een nieuw statement, het return statement. De
waarde die tussen de haakjes staat wordt aan de funktie zelf toegekend
en wordt als bruikbare waarde aan het aanroepende programma
teruggegeven. Een funktie levert dus een waarde af. In het voorbeeld
krijgt kwadr (x) dus de waarde van het kwadraat van
x. Deze waarde wordt toegekend aan de variabele
y. Als x de waarde 4 heeft, krijgt y
als gevolg van de funktie aanroep de waarde 16.
Je kunt er ook als volgt bekijken. De combinatie kwadr (x)
kan gezien worden als een variabele van het type integer en als waarde
het kwadraat van zijn argument. Deze variabele mag overal
staan waar C toestaat een normale variabele te programmeren.
Om dit principe nader te tonen is nogmaals een for lus in het programma
opgenomen. De variabele y is nu weg gelaten en aan de
printf() funktie wordt nu direct het resultaat van de
kwadraat funktie aangeboden.
Tenslotte nog dit, het type van de betrokken variabele(n) en het type
van funktie moeten met elkaar overeenstemmen. Als niets wordt opgegeven
zal de C compiler integer als standaard waarde nemen. Dit betekent dat
als het type waarmee wordt gewerkt anders dan integer is, dit expliciet
moet worden opgegeven. Het hoofdstuk hierna beschrijft hoe dit in zijn
werk gaat.
Een Floating Point funktie
Bestudeer het programma KWADRFLT.C voor een voorbeeld van een funktie
van het type floating point. Begonnen wordt met de declaratie van een
globale floating point variabele. In de main() funktie
worden achtereenvolgens een integer en twee floating point variabelen
gedeclareerd. Daarna zien we hoe we de C compiler vertellen dat we
gebruik willen maken van het resultaat van twee funkties, hier
sqr en glsqr, van het type floating point. De
expressie float sqr(); geeft dus aan dat de funktie een
float terug zal geven en geen int.
float z; /* Dit is een globale variabele */
main ()
{
int index;
float x, y, sqr(), glsqr();
for (index = 0; index <= 7; index++) {
x = index; /* Converteer int naar float */
y = sqr (x); /* Kwadrateer x */
printf ("Het kwadraat van %d is %10.4f\n", index, y);
}
for (index = 0; index <= 7; index++) {
z = index;
y = glsqr ();
printf ("Het kwadraat van %d is %10.4f\n", index, y);
}
}
float sqr (invoer) /* Kwadrateer een float, return een float */
float invoer;
{
float kwadraat;
kwadraat = invoer * invoer;
return (kwadraat);
}
float glsqr () /* Kwadrateer een float, return een float */
{
return (z * z);
}
Kijk nu eens naar de funktie sqr() in het midden van het
programma en merk op dat de naam wordt voorafgegaan door het woordje
float. Voor de compiler is dit een aanwijzing dat de funktie
van het type floating point is en dus een waarde zal teruggeven van het
type float aan het aanroepende programma. Aanroep en funktie
zijn nu met elkaar in overeenstemming. Verder zien we dat ook de lokale
variabele invoer van het type float is.
De tweede funktie, glsqr(), geeft ook een floating point
waarde terug. Deze funktie gebruikt een globale variabele als invoer en
bevat zelf geen variabelen. Toch zal ook hier een correcte waarde
worden teruggegeven.
De werking van dit programma als geheel moet nu duidelijk zijn en wordt
daarom niet verder besproken. Compileer en test dit programma nu.
De scope van variabelen
Bestudeer het programma SCOPE.C voor een bespreking van wat we in C
onder de scope van variabelen verstaan.
int telling; /* Dit is een globale variabele */
main ()
{
register int index; /* Deze variabele is lokaal voor main */
head1 (); /* Funktie aanroep */
head2 (); /* Funktie aanroep */
head3 (); /* Funktie aanroep */
}
int teller; /* Deze variabele is beschikbaar vanaf hier */
head1 ()
{
int index; /* Deze variabele is lokaal voor head1 */
index = 23;
printf ("De waarde van index in 'head1' is %d\n", index);
}
head2 ()
{
int telling; /* Deze variabele is lokaal voor head2 */
telling = 53;
printf ("De waarde van telling in 'head2' is %d\n", telling);
teller = 77;
}
head3 ()
{
printf ("De waarde van teller in 'head3' is %d\n", teller);
}
De eerste variabele die we tegen komen is de globale variabele
telling. Deze is beschikbaar in elke funktie van dit
programma vanwege het feit dat hij werd gedeclareerd voor de
definitie van deze funkties.
Een eindje verderop in het programma vinden we de variabele
teller. Ook dit is een globale variabele die beschikbaar is
in elke funktie van dit programma, behalve in main(),
vanwege het feit dat hij werd gedeclareerd voor de definitie
van de funkties. en na de definitie van main().
Een globale variabele is een variabele die gedeclareerd wordt buiten
elke funktie. Globale variabelen zijn altijd aanwezig, een begrip wat
we nog nader zullen leren kennen. Ze worden ook wel eens externe
variabelen genoemd, omdat ze buiten elke funktie gedeclareerd zijn.
We keren terug naar het programma en zien dat er een variabele
index wordt gedeclareerd van het type integer. Het woordje
register geeft aan dat de compiler een register mag
toewijzen aan deze variabele, indien de processor die beschikbaar
heeft. Deze variabele is alleen beschikbaar in main(), omdat
hij daar wordt gedeclareerd. Het is tevens een automatic
variabele, hetgeen betekent dat hij slechts bestaat op het moment dat
de funktie wordt aangeroepen en wordt uitgevoerd. Zodra de funktie
wordt verlaten, wordt ook deze variabele weggegooid. In dit geval maakt
het niet uit, immers de funktie main() is altijd actief,
zelfs wanneer een andere funktie wordt aangeroepen.
Kijk naar de funktie head1(). Ook deze bevat een
automatic variabele index. Deze heeft echter geen
enkele relatie met die uit main(). Zolang head1()
niet wordt uitgevoerd bestaat de variabele niet.
Zodra head1() wordt uitgevoerd, wordt er ruimte voor de
variabele gereserveerd. Wanneer de funktie wordt verlaten, wordt deze
ruimte weer vrijgegeven. De variabele index uit
main() blijft onaangetast.
Automatische variabelen zijn dus variabelen waarvoor ruimte wordt
gereserveerd (en weer vrijgegeven) voor het moment dat ze nodig zijn.
Het is belangrijk te onthouden dat de waarden van automatische
variabelen, bij meerdere aanroepen van eenzelfde funktie, niet bewaard
blijven. Ze bevatten dat, wat er op dat moment in het geheugen staat.
C kent nog een type variabele, de static variabele. Door het
gereserveerde woord static voor de declaratie van een
variabele binnen een funktie te plaatsen, wordt een variabele als
zijnde statisch gedefinieerd. Statische variabelen blijven bestaan, ook
tussen verschillende funktie aanroepen in. De ruimte die ze in het
geheugen innemen wordt dus niet telkens vrijgegeven.
Door het gereserveerde woord static voor de declaratie van
een externe variabele te plaatsen (buiten een funktie dus), wordt een
variabele als zijnde private gedefinieerd. Private
variabelen zijn niet bereikbaar vanuit andere programma's. Het is
namelijk mogelijk in C om te refereren aan variabelen uit andere
programma's. Door ze private te maken, worden ze voor de buitenwereld
afgeschermd.
Kijk nog eens naar funktie head2(). Deze bevat de declaratie
van de variabele telling. Hoewel telling reeds
eerder werd gedeclareerd als globale variabele, is het toegestaan dit
nog eens te doen binnen de funktie. Het is een nieuwe variabele die
geen enkele relatie heeft met de globale variabele met dezelfde naam.
De declaratie zorgt er echter voor dat de globale variabele
onbereikbaar is voor head2(). Dit principe geeft je de
mogelijkheid bestaande funkties te gebruiken, zonder je zorgen te
hoeven maken wat de namen van de variabelen in die funkties zijn, er
kan immers geen conflict ontstaan. Je moet wel je aandacht richten op
de variabelen die als interface tussen functies gebruikt worden.
Funktie parameters worden gedefinieerd na de funktie haakjes en voor
de openings accolade. Funktie variabelen worden gedeclareerd aan het
begin van de funktie na de openings accolade en voor enig statement.
Standaard funktie bibliotheken
Elke C compiler wordt geleverd met standaard funkties, die je ter
beschikking staan. Grofweg zijn dit Invoer/Uitvoer funkties, karakter-
en string manipulatie funkties en rekenfunkties.
De meeste moderne compilers voegen daar nog een heleboel funkties aan
toe die niet direct standaard zijn maar je wel helpen al het mogelijke
uit je computer te halen. In het geval van de IBM PC zijn er funkties
die de BIOS funkties van MS-DOS benaderen en funkties voor het
uitgebreid aansturen van de grafische kaarten. Het totaal programma
bevat heel veel van dit soort funktie aanroepen. Kijk dus zo af en toe
eens naar Appendix C, "Totaal programma".
Recursie
Recursie is een programmeer techniek die in eerste instantie moeilijker
lijkt dan het is. Bestudeer het programma RECURSIE.C om er kennis mee
te gaan maken. Het is waarschijnlijk het meeste eenvoudige recursieve
programma dat je kunt schrijven en om die reden van weinig nut. Maar
voor ons doel (er wat van leren) is het uitstekend geschikt.
main ()
{
int index = 0;
index = 8;
tel_af (index);
}
tel_af (telling)
int telling;
{
telling--;
printf ("De waarde van de telling is %d\n", telling);
if (telling > 0)
tel_af (telling);
printf ("De telling is nu %d\n", telling);
}
Recursie is niets meer dan een funktie die zichzelf aanroept. Om die
reden ontstaat er een lus, een iteratie die op de een of andere manier
beëindigd moet worden. In het voorbeeld programma wordt voor dit doel
de variabele telling gebruikt, die bij eerste aanroep de
waarde 8 heeft en in elke vervolgstap 1 lager is. Dit aflagen gebeurd
doordat de funktie tel_af() zichzelf aanroept met de
afgelaagde teller. Er zal uiteindelijk een moment komen dat
telling nul is geworden en de funktie zichzelf niet meer
aanroept. In plaats daarvan zal de funktie terugkeren naar de vorige
stap en vandaar weer naar de vorige stap enz. enz., totdat weer wordt
teruggekeerd naar de aanroep door main().
Voor een beter begrip kun je het beste denken dat er 8 kopieën van de
funktie tel_af() in het geheugen staan, die elkaar een voor
een aanroepen. Dat is niet wat er in werkelijkheid gebeurt, het gaat om
het begrip.
Bij de recursieve aanroep worden alle variabelen en interne vlaggen die
het systeem nodig heeft om te functie te kunnen beëindigen, ergens in
een blok in het geheugen opgeslagen. Bij de volgende recursieve aanroep
gebeurt weer hetzelfde, enz.. Bij de laatste aanroep wordt
teruggekeerd, het geheugen blok opgehaald en opgeruimd. Er wordt weer
teruggekeerd, opgehaald en opgeruimd enz. totdat de eerste weer bereikt
is. De blokken worden intern opgeslagen in een geheugengebied die de
stack wordt genoemd. De stack wordt door het
systeem onderhouden en hier verder niet beschreven.
Wat belangrijk is bij recursief programmeren is dat het programma stopt
met zichzelf aanroepen, bijvoorbeeld door een teller naar nul te laten
lopen en bij deze nulwaarde terug te springen. Als dit niet gebeurt zal
het programma zichzelf aan blijven roepen en daardoor op een gegeven
moment abnormaal eindigen omdat de stack vol is.
Er volgt nu nog een voorbeeld van recursie. Bestudeer daartoe het
programma TERUG.C.
main ()
{
char regel[80];
int index = 0;
strcpy (regel, "Dit is een string.\n");
heenweer (regel, index);
}
heenweer (regel, index)
char regel[];
int index;
{
if (regel[index]) {
printf ("%c", regel[index]);
heenweer (regel, index+1);
}
printf ("%c", regel[index]);
}
Dit programma is nagenoeg gelijk aan het vorige programma, echter hier
wordt gebruik gemaakt van een tabel van karakters (character array).
Elke opvolgende aanroep van de funktie heenweer() drukt een
volgend karakter van de tekst Dit is een string. af.
Telkens als de funktie eindigt, wordt ook een karakter afgedrukt, echter
dit keer van achter naar voren vanwege de recursie.
Maak je nog maar geen zorgen over de tabel van karakters die hier is
toegepast. We willen je er vast mee vertrouwd maken. Er wordt in
Hoofstuk 7, "Strings en Tabellen" uitgebreid op ingegaan.
Compileer en test deze programma's en kijk naar de resultaten.
|