home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
RUN Flagazine: Run 12
/
run12.zip
/
LES12GWB.ASC
< prev
next >
Wrap
Text File
|
1994-05-04
|
27KB
|
404 lines
Les 12 GW/QBasic
DEF FN:
Zelf functies maken
Zowel GW- als QBasic bieden de programmeur veel meer functies dan de be-
langrijkste die tot nu toe in deze vervolgcursus zijn behandeld. Er zijn
legio naslagboeken die ze opsommen en met voorbeeldjes toelichten.
Aanvankelijk zal iedere enigszins gevorderde programmeur menen dat Basic zó
veel functies bevat dat je er alles in kunt programmeren wat een mens maar
bedenken kan. Toch mist de GW/QBasic-programmeur veel van de weelde aan mo-
gelijkheden waarover de QuickBasic en PowerBasic programmeurs beschikken.
U houdt dat nog tegoed tot het moment waarop u zelf gaat merken dat u aan
de Basic-interpreters tekort gaat komen.
Veel functies moeten in gewoon Basic moeizaam worden geprogrammeerd en uit-
geschreven waarvoor in PowerBasic met een enkel commando kan worden vol-
staan. Een voorbeeld is het sorteren van arrays. Daarvoor moet een stukje
programma worden geschreven in een SUB of Subroutine. In PowerBasic bestaat
daarvoor een ingebouwde functie: ARRAY SORT Arraynaam().
Een ander voorbeeld is de processor-onafhankelijke wachtlus.
Wait a second
Wanneer u in uw programma een tekst of een voorstelling enkele seconden
wilt laten staan zonder dat de gebruiker daarop kan ingrijpen, wordt in
GWBasic vaak de loze FOR/NEXT-loop gebruikt:
FOR A=1 TO 50000:NEXT A
Als u dit loopje op uw 286-PC hebt getest en bijgesteld en u bent tevreden
over de wachtduur, houdt u uzelf voor de gek. Als uw programma met dit
soort wachtlussen op een trage PC/XT wordt gedraaid, duren uw wachtlussen
'eeuwigheden'. Op een snelle 486-PC op een kloksnelheid van 50 MHz zijn ze
in minder dan een oogwenk voorbij.
Nee, dan heeft de PowerBasic-programmeur het heel wat gemakkelijker. Om een
wachttijd van twee seconden te krijgen schrijft hij:
DELAY 2
met als resultaat: exact twee seconden oponthoud, ongeacht het type PC en
de kloksnelheid waarop zijn programma draait.
Toch kunt u ook in GW/QBasic een processor-onafhankelijke wachtloop inbou-
wen als u gebruik maakt van de microtimer die op de systeemklok loopt. Deze
timer geeft pulsen die Basic kan tellen. Het gaat dan wel een stuk ingewik-
kelder dan in PowerBasic:
10 REM TIJDLOOP VOOR TWEE SECONDEN
20 T=TIMER + 2:
' SYSTEM TIME PLUS 2 SEC
30 WHILE T > TIMER
40 WEND
Als u in uw programma meermalen een wachttijd van twee seconden wilt ge-
bruiken, maakt u van dit programmastukje uiteraard een subroutine. Maar
wat nu als u de ene keer één, de volgende keer vijf en dan weer drie se-
conden wilt laten wachten? Even veel subroutines met Timers aanmaken?
Natuurlijk niet. U mist nu een functie die met een variabele instelbaar
is, zoals in: DELAY n.
Programeertaalarmoede? Niet helemaal. U kunt functies zelf maken. Dat wil
zeggen dat u GW/QBasic zelf kunt uitbreiden met extra commando's maar dan
moet u ze wel zelf bedenken en definiëren.
Doe 't zelf
Eerlijk gezegd is
DEF
(ine)
F
(unctio)
N
een moeilijk onderwerp. Dat zal ook
wel de reden zijn dat er in GW/QBasic-programma's zo weinig gebruik van
wordt gemaakt. Jammer, want het is een zeer krachtig gereedschap, zij het
dat zelf gemaakte functies in GW/Q maar heel klein kunnen zijn. In een
Basic-compilertaal kunnen DEF FN's zich over meer dan één programmaregel
uitstrekken.
Omdat dit onderwerp wat lastig is, beginnen we met een simpel voorbeeld.
In een programma hebben we om de haverklap een numerieke variabele nodig
waarin een willekeurig gekozen (random) geheel (integer) getal onder de
10 moet staan.
U weet hoe dat normaal moet:
10 RANDOMIZE TIMER
20 X = INT(RND*10)
30 PRINT X
Nu kunnen we hier een subroutine van maken en deze telkens aanroepen als
we een getal onder de 10 in de variabele X willen hebben maar met een zelf
gemaakte functie kan het ook:
10 RANDOMIZE TIMER
20 DEF FN GETAL = (INT(RND*10))
30 PRINT "Een willekeurig getal onder de 10 is:";
40 PRINT FN GETAL
Experimenteer hiermee en als u het begint te begrijpen, bestudeer dan het
volgende voorbeeld.
In een programma willen we een tekstje lang genoeg op 't scherm laten staan
zodat het rustig kan worden gelezen. Daarvoor hebben een leestijd van vijf
seconden getimed. Deze vijf seconden moeten dus processor-onafhankelijk
zijn en op elke PC ook exact vijf seconden duren.
10 REM TEKST BLIJFT 5 SECONDEN OP HET SCHERM STAAN
20 CLS
30 DEF FN WACHT = TIMER + 5
40 LOCATE 10,1
50 PRINT "U krijgt precies vijf seconden de tijd om deze tekst te lezen."
60 T = FN WACHT
70 WHILE T > TIMER
80 LOCATE 1,5:PRINT "T = ";T
90 LOCATE 2,1:PRINT "TIMER = ";TIMER
100 WEND
110 BEEP
120 LOCATE 10,1
130 PRINT SPACE$(80)
140 LOCATE 10,1
150 PRINT "De leestijd van vijf seconden is voorbij."
160 END
Meer DEF FN-voorbeelden
Voorbeelden zijn als plaatjes in een boek: ze kunnen ingewikkelde zaken in
één oogopslag duidelijk maken.
In een financieel programma worden BTW-percentages berekend. Hiervoor maken
we gebruik van een zelf gedefinieerde functie:
100 REM BTW-BEREKENING
110 DEF FN BTW(X) = X * .175
120 INPUT "17,5 % BTW over welk bedrag";Bedrag
130 PRINT "De BTW over";Bedrag;"bedraagt:";
140 PRINT USING "#####.##";FN BTW(Bedrag)
LET OP
Het is mogelijk gegevens uit te wisselen tussen de definiërende functie
(DEF FN) en de vragende functie (FN). Laat ik proberen dat duidelijk te
maken aan de hand van een voorbeeld en een schemaatje.
Eerst het voorbeeld:
We schrijven een educatief programma waarin we de gebruiker bij zijn voor-
en achternaam willen noemen. Zo'n programma begint altijd met een soort
kennismaking:
Hoe is je naam?
[LINE INPUT NAAM$]
De bedoeling is dat de gebruiker zijn voornaam invoert en dat zal meestal
ook wel gebeuren maar niets is zo onvoorspelbaar als het gedrag van een
computergebruiker. We moeten er rekening mee houden dat hij zijn voor- en
achternaam zal invoeren:
Hoe is je naam?
Johan van der Heijden
en het programma antwoord nu heel plichtmatig:
[PRINT]
Dag Johan van der Heijden
Dergelijke dingen kunnen we 'fool proof' (klunsvast) maken met een DEF FN.
Dit is het programma:
100 CLS
110 DEF FN X$(NAAM$)=LEFT$(NAAM$,INSTR(NAAM$," "))
120 LOCATE 12,13:LINE INPUT "Wat is je naam? ";NM$
130 NM$=NM$+" "
140 LOCATE 14,13:PRINT "Hallo ";FN X$(NM$)
De uitvoering van dit programma gaat zoals we dat willen:
Wat is je naam?
Peter van de Boogaard
Hallo Peter
In de DEF FN is een stringbewerking toegepast met LEFT$. Gezocht is naar de
eerste spatie omdat die de voornaam scheidt van de achternaam of -namen.
Dat eerste stuk hebben we nodig omdat dit een voornaam is.
Er kan alleen iets fout gaan wanneer de gebruiker het toch goed doet en al-
léén zijn voornaam invoert. Daarom wordt in regel 130 voor alle zekerheid
nog een spatie aan NM$ toegevoegd. Daarmee kan de DEF FN onder alle omstan-
digheden zijn werk doen.
Om de DEF FN goed te laten functioneren moet hij de invoerstring hebben.
Die ontvangt hij in: (NAAM$) achter X$. Als 'doorgeefluik' fungeert de in-
voerstring NM$. Wanneer FN in regel 140 wordt aangeroepen wordt eerst de
ingevoerde string NM$ in NAAM$ geplaatst. Het resultaat in NAAM$ wordt de
voornaam volgens de formule van de DEF FN. Dit resultaat wordt weer terug-
geplaatst in X$(NM$) in regel 140.
┌───────────────┐ ┌───────────────┐
│ ──┴─ ──────────────┴──────────── │
│ DEF FN X$(NAAM$) = LEFT$(NAAM$,INSTR(NAAM$," ")) │
└──────────────────────────────────┐ │
─┴─ │
LINE INPUT "Wat is uw naam? ";NM$ (Kees de Boer) │
│
│
PRINT "Hallo ";FN X$(NM$) (Kees) │
─────┬─── │
└────────────────────────────┘
┌──────────┐
┌────────────────────────────────┤ INSTRING ├─────────────────────────────┐
│ └──────────┘ │
│ De gebruikte tekstbewerking met INSTR in het bovenstaande schema be- │
│ hoeft enige toelichting. │
│ INSTR is een machtig middel om in een gegeven tekststring (de z.g. │
│ target- of doelstring) de positie van een andere string (de zoek- │
│ string) te bepalen. Zowel target- als zoekstring kunnen gewone strings │
│ zijn zoals "Flagazine", als elementen uit een tekstarray: TXT$(n). │
│ Als we willen weten waar in de string: "RUN Flagazine" de hoofdletter │
│ F van Flagazine begint omdat we het stukje van de string "RUN" willen │
│ weghalen om alleen "Flagazine" over te houden, moeten we eerst de │
│ plaats in de string bepalen: │
│ │
│ Y = INSTR("RUN Flagazine","F") │
│ │
│ In dit geval krijgt de variabele Y de waarde 5. Op positie 5 van │
│ de string: "RUN Flagazine" begint een woord dat met "F" begint. │
│ Vervolgens kunnen we dat stringstukje isoleren met: │
│ │
│ X$ = MID$("RUN Flagazine",Y) │
│ │
│ waarna X$ het deel van de string: "Flagazine" zal bevatten. │
│ Zoals uit de DEF FN-constructie blijkt, kan INSTR ook worden ingebed │
│ in een LEFT$, RIGHT$ of MID$-constructie maar dat is in de praktijk │
│ soms wel even puzzelen en proberen maar lukken doet 't altijd! │
└─────────────────────────────────────────────────────────────────────────┘
Juist die kleine handigheidjes
Een random getal, een stukje tekstbewerking, een berekening met variabelen
en meer van die handigheidjes die je zo maar te binnen kunnen schieten, le-
nen zich vrijwel altijd voor een DEF FN. Ook als onderdeel van ingewikkel-
der berekeningen kunnen ze van groot nut zijn. In sommige gevallen kan een
DEF FN een complete algoritme bevatten en in feite is deze functie daarvoor
ook bedoeld.
Dat wil niet zeggen dat DEF FN zaligmakend is. Soms kunt u kiezen tussen
twee of meer mogelijkheden. Als u van een getal in een variable de schrijf-
wijze met twee decimalen achter de komma wilt weergeven, kunt u dat met een
DEF FN doen maar ook met een PRINT USING "####.##"
Beide hebben hun voor- en nadelen. Bij PRINT USING "####.##" krijgt u van
elk getal de gevraagde schrijfwijze, dus ook van een integer getal. Als
dit getal bijvoorbeeld 100 is dan geeft de PRINT USING dit weer als:
100.00. Het nadeel is hier dat het aantal hekjes voor de punt maatgevend
is. Wordt het weer te geven getal als gevolg van de berekening groter dan
9999 dan hebt u met deze PRINT USING een probleem omdat u dan vóór de deci-
male punt hekjes te kort komt.
Dit nadeel heeft nu niet wanneer u onderstaand programmaatje bestudeert.
Het heeft een DEF FN voor het weergeven van getallen met twee decimalen:
10 CLS:KEY OFF
20 DEF FN AFROND(X)=FIX(X*100 + SNG(X)/2)/100
30 INPUT "Hoeveel guldens zijn er";X
40 PRINT "Dit delen we door 3:";X=X/3:PRINT X
50 PRINT "Ieder krijgt ";CHR$(159);:PRINT FN AFROND(X)
60 END
Het nadeel van deze benadering is dat wanneer de variabele X na deling door
3 toevallig een geheel getal wordt, de FN AFROND geen decimalen met x.00
kan laten zien zoals PRINT USING "####.##" dat weer wel doet.
Nog twee handigheidjes
Ik heb nog twee voorbeelden van 'kleine handigheidjes' die zich bij uitstek
lenen voor DEF FN:
In Turbo- en PowerBasic kunnen we een ingevoerde tekst heel gemakkelijk om-
zetten naar kapitalen met UCASE$ en van kapitalen naar onderkast met LCASE$.
GWBasic is niet zo rijk maar het kan natuurlijk met een DEF FN. In het on-
derstaand programmaatje wordt de invoer van een tekst geaccepteerd. In de
FOR/NEXT-loop wordt eerst gekeken of het teken valt tussen de ASC-waarden
61 tot en met 122 (a tot en met z). Als dit zo is wordt uit de FN de con-
versie naar het kapitale equivalent geprint:
┌─────────────────────────────────────────────────────────────────────────┐
│
100 REM DEF FN VOOR SELECTIEVE TEKSTOMZETTING NAAR KAPITALEN
│
│
110 CLS:KEY OFF:SCREEN 0,0
│
│
120 DEF FN KAPITAAL = ASC(X$)-32:
REM TREK 32 AF VAN DE ASCI-WAARDE │
│
130 PRINT "Voer een tekst in:":LINE INPUT A$:PRINT
│
│
140 FOR A%=1 TO LEN(A$)
│
│
150 X$=MID$(A$,A%,1):X=ASC(X$)
│
│
160 IF X < 123 AND X > 96 THEN PRINT CHR$(FN KAPITAAL); ELSE PRINT X$;
│
│
170 NEXT A%
│
└─────────────────────────────────────────────────────────────────────────┘
Het tweede voorbeeld is eveneens uit het leven gegrepen. Hoe vaak komt het
niet voor dat de netheid van een programma vereist dat tekstregels keurig
middenin de schermregel worden gezet. Vaak laat de programmeur dan zijn
timmermansoog meewerken maar de regel blijft van kracht: Meten is weten;
gissen is missen...
Gecentreerde tekstregels worden voortaan consequent ook DAADWERKELIJK in
het midden geplaatst met de DEF FN uit dit voorbeeldprogrammaatje:
┌────────────────────────────────────────────────────────────────────────┐
│
100 REM DEF FN VOOR REGELCENTRERING
│
│
110 CLS:KEY OFF:SCREEN 0,0
│
│
120 DEF FN MIDDENIN = INT(80-LEN(X$)/2)
│
│
130 X$="Hoofdstuk 1"
│
│
140 LOCATE 3,FN MIDDENIN:PRINT X$
│
│
150 X$="Het ontstaan der soorten door natuurlijke selectie"
│
│
170 LOCATE 5,FN MIDDENIN:PRINT X$
│
│
175 X$="Door Charles Darwin"
│
│
176 LOCATE 7,FN MIDDENIN:PRINT X$
│
│
180 COLOR 0,7:LOCATE 25,1:PRINT SPACE$(80);
│
│
190 X$="Toets <Enter> voor vervolg"
│
│
200 LOCATE 25,FN MIDDENIN:PRINT X$;
│
│
210 WHILE INKEY$<>CHR$(13):WEND:COLOR 7,0:END
│
└────────────────────────────────────────────────────────────────────────┘
Grafisch DEF FN-nen
Er is veel mogelijk met DEF FN's. De ene DEF FN mag de andere aanroepen en
ook is het is mogelijk om handig gebruik van een DEF FN te maken in een
FOR/NEXT-loop. Voordelen van het gebruik van DEF FN's is dat de programma-
code kleiner wordt en het programma zelf merkbaar sneller.
Nevenstaand stukje programma in
100 REM GRAFISCH EFFECTJE
GWBasic speelt zich af in EGA
110 CLS:KEY OFF:SCREEN 9
(Screen 9). In dit programma
120 DEF FN PUNT1=A MOD 6.28
worden door de FN's het begin-
130 DEF FN PUNT2=(A+.25) MOD 6.28
en eindpunt berekend van de cir-
140 FOR A=1 TO 175
kelbogen. Het voordeel van de-
150 CIRCLE(320,175),A,13,FN PUNT1,FN PUNT2
ze werkwijze is dat het pro-
160 NEXT A
gramma klein blijft en aanzien-
lijk sneller loopt dan wanneer een andere methode wordt toegepast.
Compileromgevingen
In 'hogere' Basics als TurboBasic, QuickBasic en PowerBasic en in beschei-
denere mate QBasic is meer mogelijk met DEF FN.
In GWBasic moet de DEF FN beperkt blijven tot één regel. In een compiler-
omgeving daarentegen kunnen uitgebreidere functies worden gecomponeerd.
Een voorbeeld is een zelfgemaakte functie voor het berekenen van facultei-
ten van getallen vanuit een DEF FN in TurboBasic. Faculteiten zijn, zoals
u weet, exploderende vermenigvuldigingen. De faculteit van 5 bijvoorbeeld,
is 1 * 2 * 3 * 4 * 5 en wordt geschreven als: 5! en geeft als uitkomst 120.
De algoritme daarvoor kan in een compilertaal in een meerregelige DEF FN
worden ondergebracht en naar hartelust worden aangeroepen.
Zo ziet de DEF FN voor faculteitsberekeningen eruit in TurboBasic. Let op
dat niet voor niets van double precision variabelen (#) gebruik is gemaakt
omdat bij grotere faculteitsberekeningen de resultaten tot ver boven de ui-
terste grens van integers (65536) zullen uitstijgen. Met dubbele precisie
gaat de getallen tot een hoogte van: TIEN TOT DE MACHT 308! Dit aantal is
zeer groot. Veel groter zelfs dan er aantal elementaire deeltjes zijn in
het heelal!
$STACK &H7FFF
'Er moet nogal wat ruimte op de stack worden gereserveerd
Totaal# = 1
DEF FN Factoren#(I#)
'Aanvang Functie
Totaal# = Totaal# * I#
IF I# > 1 THEN
S# = FN Factoren#(I# - 1)
END IF
FN Factoren# = Totaal#
END DEF
'Einde Functie.
De DEF FN is meerregelig en wordt met END DEF afgesloten. Vervolgens kan
onderstaand programmaatje van deze zelf gedefinieerde functie gebruik ma-
ken:
PRINT "Geef het getal voor de factorenberekening: ";
INPUT J%
PRINT FN Factoren#(J%)
Opmerking: In dit geval wordt de zelf gedefinieerde functie in zichzelf
aangeroepen. Dit wordt recursiviteit genoemd.
Tot besluit
DEF FN's zijn bedoeld om er van te profiteren. Dat betekent dat u, voordat
u gaat programmeren eerste eens even rustig moet nadenken over de te vol-
gen strategie. Programmeren is en blijft plannen nadat het programmeer-
idee is geboren en om een concrete vorm vraagt.
Programmeren is dus niet met een half uitgewerkt idee achter de PC gaan
zitten, GWBasic opstarten en al vast maar beginnen met:
100 CLS:KEY OFF:SCREEN 9
en tijdens het schrijven wel zien hoe het idee verder uitgewerkt wordt.
Deze werkwijze is typisch van onervaren beginners die dan ook typisch
programma's bakken met een spaghetti-structuur. Aan de vele GOTO's is
meestal te zien dat ze tijdens het schrijven van het programma voor al-
lerlei onvoorziene omstandigheden lapmiddelen moeten inbouwen. Zo zeer
zelfs dat ze later geen wijs meer kunnen worden uit hun eigen kronkel-
structuren.
Programmeren is eerst en vooral ontwerpen en plannen. Het coderen is eigen-
lijk maar bijzaak. Tijdens het plannen vraagt de programmeur dingen af als:
Welke subroutines ga ik gebruiken en nu ook: welke functies komen in aan-
merking voor een zelf te maken DEF FN.
In de volgende Les 13 van deze beginnerscursus gaan we weer op de grafische
toer. Gastdocent Koos van Egmond (bekend van Lingo, Boggle, PseudoRubic
en nog meer grafische hoogstandjes uit de Flagazines) zal dan met voorbeel-
den de geheimen van
WINDOW
en
WINDOW SCREEN
ontraadselen: een belangrijk
onderdeel in GW- en andere Basics waar velen mee worstelen.
Het belooft een zeer geanimeerde en bovenal leerzame les te worden.
Tot de volgende les en... vergeet niet te experimenteren met DEF FN. Spel
en experiment leiden naar inzicht en ten slotte naar slim gebruik.
Het wordt nu trouwens ook de hoogste tijd dat de cursisten eens blijk ge-
ven van hun vorderingen in de Basic-programmeertaal. Schroom vooral niet
uw zelf gemaakte progjes op een disketje te zetten en op te sturen naar
RUN, Postbus 338, 2160 AH Lisse of in ons BBS TeleRUN te uploaden.
U krijgt altijd een reactie en diskettes sturen we ook terug, dus daar
hoeft u niet wakker van te liggen.
Uw inzendingen zijn voor de redaktie belangrijke stimulansen om door te
gaan met deze cursus.