|
Functies en Variabelen
Definitie van functies
Bestudeer het programma KWADRSOM.rex als voorbeeld van een REXX programma
met functies. Eigenlijk is dit niet de eerste keer dat we een functie
tegenkomen, omdat het main programma, dat we tot nu toe
steeds gebruikten, technisch gezien ook een functie is. Hetzelfde geldt
voor de Say functie. Deze laatste is een functie uit
een functie-bibliotheek die geleverd werd bij de interpreter.
/* REXX */
Call Kop /* Roep Kop aan */
Do INDEX=1 To 7
Call Kwadr INDEX /* Kwadraat funtie */
End
Call Voet /* Roep Voet aan */
Exit 0
Kop: /* Dit is de 'Kop' funtie */
SOM = 0 /* Initialisatie */
Say 'Getallen kwadrateren en optellen...'
Say
Return 0
Kwadr:
Arg INVOER
KWADRAAT = INVOER * INVOER
SOM = SOM + KWADRAAT
Say 'Het kwadraat van 'INVOER' is 'KWADRAAT
Return 0
Voet: /* Dit is de 'Voet' functie */
Say
Say 'De som der kwadraten is 'SOM
Return 0
Let eens op de statements in dit programma. Het bevat een regel met als
inhoud Call Kop. Op deze manier wordt in REXX een functie
aangeroepen.
Zodra het programma bij dit statement aankomt, wordt de functie met de
naam Kop aangeroepen, de statements van die functie
uitgevoerd en teruggekeerd naar het statement volgend op de aanroep. In
dit geval is dat een Do-End lus die zeven keer wordt doorlopen. Binnen de
lus wordt een andere functie aangeroepen met de naam Kwadr.
Tenslotte wordt nog een functie met de naam Voet aangeroepen
en uitgevoerd. Vergeet op dit moment even de variabele INDEX
die na de functie aanroep van Kwadr
staat. Samenvattend zien we dus dat dit programma een functie voor een
kopregel aanroept, dan zeven keer de kwadraat functie en tenslotte een
functie voor een voetregel.
De Kop functie 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 functies. Merk op dat de naam van de functie wordt gevolgd
door een dubbele punt, een teken voor REXX dat hiet een functie begint.
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. De variabele SOM
is beschikbaar
in alle functies van het programma. Het is een zogenaamde
globale variabele en zijn scope is het hele programma, in
dit geval alle functies. Over de scope van variabelen wordt later
uitgebreider ingegaan. Het tweede statement drukt een kopregel af. De
functie eindigt en keert terug naar main.
In principe kunnen de twee statements van Kop ook direct
in main gezet worden en de functie aanroep in zijn geheel
vervangen. Het programma doet dan nog precies hetzelfde. Het is gedaan
om de werking van functies uit te leggen. Functies zijn namelijk zeer
waardevolle instrumenten in de programmeertaal REXX.
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 REXX constructie Do INDEX=1.
De aanroep van de kwadraat functie bevat iets nieuws, namelijk de
variabele INDEX na de functienaam. Voor de interpreter is dit
een indicatie dat bij de aanroep van Kwadr de waarde van
de variabele INDEX moet worden overgedragen aan de functie.
De functie kan er dan verder mee rekenen.
Kijken we dan naar de functie 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 functie graag willen
hanteren. Het mag elke naam zijn, zolang hij maar aan de regels van een
identifier voldoet. 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 functie zelf wordt de variabele KWADRAAT
gedeclareerd, puur voor gebruikt binnen de functie zelf. De functie
berekend het kwadraat van de doorgegeven parameter en kent de waarde
daarvan toe aan KWADRAAT. 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 functie werd niet de variabele
INDEX zelf gebruikt, 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 functie. Op deze manier is het echter
niet mogelijk een eventueel gewijzigde waarde terug te geven aan de
aanroepende functie. 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 functie Voet
aangeroepen. Deze functie 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 functie 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.rex 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. /* REXX */ Do INDEX=1 To 7 KWADRAAT = Kwadr(INDEX)
/* Kwadraat funtie */ Say 'Het kwadraat van 'INDEX' is 'KWADRAAT End Say
Do INDEX=1 To 7 Say 'Het kwadraat van 'INDEX' is 'Kwadr(INDEX) End Exit
0 Kwadr: Arg INVOER Return (INVOER * INVOER) In main begint
een for lus die zeven keer wordt doorlopen. Het eerste statement van de
for lus luidt KWADRAAT = Kwadr(INDEX), hetgeen nieuw voor je is.
Het deel Kwadr(INDEX) moet niet al te veel problemen opleveren; het
is de aanroep van een kwadraat functie met INDEX als parameter. Kijk
eens naar de functie zelf. Je ziet dat de parameter hier INVOER wordt
genoemd en dat de waarde met zichzelf vermenigvuldigd wordt geretourneerd.
Dan volgt er een nieuw statement, het Return statement. De
waarde die tussen de haakjes staat wordt aan de functie zelf toegekend
en wordt als bruikbare waarde aan het aanroepende programma
teruggegeven. Een functie levert dus een waarde af. In het voorbeeld
krijgt Kwadr(INDEX) dus de waarde van het kwadraat van
INDEX. Deze waarde wordt toegekend middels de variabele
INVOER. Als INDEX de waarde 4 heeft, krijgt de functie
Kwadr als gevolg van de functie aanroep de waarde 16.
Je kunt het ook als volgt bekijken. De combinatie Kwadr(INDEX)
kan gezien worden als een variabele van het type integer en als waarde
het kwadraat van zijn argument. Deze variabele mag overal
staan waar REXX toestaat een normale variabele te programmeren.
Om dit principe nader te tonen is nogmaals een for lus in het programma
opgenomen. De variabele KWADRAAT is nu weg gelaten en aan de
Say functie wordt nu direct het resultaat van de
kwadraat functie aangeboden.
Een Floating Point functie
Bestudeer het programma KWADRFLT.rex voor een voorbeeld van een functie
van het type floating point.
/* REXX */
Do INDEX=3 To 9
Say 'Het kwadraat van 'INDEX+0.5' is 'Kwadr(INDEX+0.5)
End
Exit 0
Kwadr:
Arg INVOER
Return (INVOER * INVOER)
De functie, Kwadr geeft deze keer een floating point
waarde terug. Deze functie kwadrateert de parameter en zal
een correcte waarde terug geven.
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.rex voor een bespreking van wat we in REXX
onder de scope van variabelen verstaan.
/* REXX */
INDEX = 11
Call Head1
Call Head2
Call Head3
Say 'De waarde van INDEX in "main" is 'INDEX
Exit 0
Head1: Procedure
INDEX = 23
Say 'De waarde van INDEX in "Head1" is 'INDEX
Return 0
Head2:
TELLING = 53
Say 'De waarde van TELLING in "Head2" is 'TELLING
TELLER = 77
Return 0
Head3:
Say 'De waarde van TELLER in "Head3" is 'TELLER
Return 0
Een globale variabele is een variabele die gedeclareerd wordt buiten
elke functie. 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 functie gedeclareerd zijn.
We keren terug naar het programma en zien dat er een variabele
INDEX wordt gebruikt in functie Head1. Het woordje
Procedure geeft aan dat de functie zijn eigen lokale variabelen
heeft en geen gebruik maakt van de globale variabelen.
Kijk naar de functie Head1. Deze bevat een
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 functie wordt verlaten, wordt deze
ruimte weer vrijgegeven. De variabele INDEX uit
main blijft onaangetast.
Kijk eens naar functie 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 functie. Het is dezelfde variabele.
Dit principe geeft je de mogelijkheid bestaande functies te gebruiken,
waarbij je dus moet opletten wat de namen van de variabelen in die functies zijn, er
kan immers een conflict ontstaan. Je moet wel je aandacht richten op
de variabelen die als interface tussen functies gebruikt worden.
De functie Head3 illustreert dit principe nog eens. De variabele
TELLER kreeg in Head2 zijn waarde en wordt vervolgens ook in Head3 afgedrukt.
Standaard functie bibliotheken
Elke REXX interpreter wordt geleverd met standaard functies, die je ter
beschikking staan. Grofweg zijn dit Invoer/Uitvoer functies, karakter-
en string manipulatie functies en rekenfuncties.
De meeste moderne interpreters voegen daar nog een heleboel functies 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 functies
die de BIOS functies van MS-DOS benaderen en functies voor het
uitgebreid aansturen van de grafische kaarten. Het totaal programma
bevat heel veel van dit soort functie 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.rex 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.
/* REXX */
Call TelAf 8
Exit 0
TelAf: Procedure
Arg TELLER
Say 'De waarde van de teller is 'TELLER
If (TELLER > 0) Then
Call TelAf TELLER-1
Say 'De teller is nu 'TELLER
Return 0
Recursie is niets meer dan een functie 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 TELLER gebruikt, die bij eerste aanroep de
waarde 8 heeft en in elke vervolgstap 1 lager is. Dit aflagen gebeurd
doordat de functie TelAf zichzelf aanroept met de
afgelaagde teller. Er zal uiteindelijk een moment komen dat
TELLER nul is geworden en de functie zichzelf niet meer
aanroept. In plaats daarvan zal de functie 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
functie TelAf 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.rex.
/* REXX */
REGEL = 'Dit is een string.'
Call HeenWeer 1
Say
Exit 0
HeenWeer: Procedure Expose REGEL
Arg INDEX
LETTER = Substr(REGEL,INDEX,1)
Call CharOut,LETTER
If (LETTER <> '.') Then
Call HeenWeer INDEX+1
Else
Say
Call CharOut,LETTER
Return 0
Dit programma is nagenoeg gelijk aan het vorige programma, echter hier
wordt gebruik gemaakt van een String.
Elke opvolgende aanroep van de functie HeenWeer drukt een
volgend karakter van de tekst Dit is een string. af.
Telkens als de functie eindigt, wordt ook een karakter afgedrukt, echter
dit keer van achter naar voren vanwege de recursie.
Compileer en test deze programma's en kijk naar de resultaten.
|