home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
RUN Flagazine Extra: Special 2
/
run-special-2.zip
/
CURSUS_2.ASC
< prev
next >
Wrap
Text File
|
1992-06-09
|
42KB
|
677 lines
DE GW-BASIC INTERPRETER (3)
BASIC is een tolerante programmeertaal. Alles mag... Ook dom programme-
ren! Willen we van Rotterdam naar Amsterdam via de Afsluitdijk dan zegt
BASIC: Okay, we do it the hard way! en de vertolker doet vervolgens pre-
cies wat we hem in een dom programma voorschrijven.
De FOR/NEXT-kringloop ╔══════════════════════════════╗
║ Dit is een DOM programma ║
Een van de allerbelangrijkste functies ╠══════════════════════════════╣
binnen GWBASIC is de kringloop die we ║ 10 REM MAAK DE TAFEL VAN 10 ║
met FOR .. in gang zetten. Wie deze ║ 20 A=1:B=10 ║
kringloop goed begrijpt kan al voor de ║ 30 PRINT A;" x ";B;" = ";A*B ║
helft programmeren in GWBASIC. In fei- ║ 40 A=A+1 ║
te kun je zeggen dat de FOR/NEXT-loop ║ 50 PRINT A;" x ";B;" = ";A*B ║
de kern van de zaak binnen GWBASIC is. ║ 60 A=A+1 ║
Met de FOR/NEXT-lus gaan we werkelijk ║ 70 PRINT A;" x ";B;" = ";A*B ║
tot automatisering over! ║ 80 A=A+1 ║
Na FOR komt altijd een variabele te ║ 90 PRINT A;" x ";B;" = ";A*B ║
staan. A bijvoorbeeld maar het mag ook ║ 100 A=A+1 ║
B, A% of voluit Teller zijn. Deze va- ║ 110 PRINT A:" x ";B;" = ";A*B║
riabele moet vervolgens worden benoemd. ║ 120 A=A+1 ║
Dat doen we door achter de variabele ║ enz.... ║
het isgelijkteken te typen: ╚══════════════════════════════╝
FOR Teller=
In de vorige les in RUN-Special nr. 1 hebben we vastgesteld dat er in
een programma altijd een bepaalde 'flow' zit die we in een stroomsche-
maatje aanschouwelijk maken nog voordat we gaan programmeren. Daardoor
wordt ook het op te lossen probleem overzichtelijker.
De variabele Teller moet een aanvangswaarde krijgen. Deze aanvangswaarde
hangt af van hoe we de kringloop willen laten draaien. Bij het automa-
tisch maken van bijvoorbeeld de tafel van tien gaat de kringloop uiter-
aard van 1 tot en met 10. Het begin van de kringloop wordt dan:
FOR Teller=1 TO 10
In het onderstaande stroomschema laten we de computer in een FOR/NEXT-
loop tellen van 1 tot en met 10. Het eind van de kringloop wordt aange-
geven met:NEXT Teller. In een kringloop die alléén maar van 1 to 10
telt, gebeurt er wat hieronder schematisch is weergegeven:
┌────────────────────┐ De kringloop begint bij de aanvang van
┌─> FOR Teller=1 TO 10 │ de FOR/NEXT-loop waarin de numerieke va-
│ └─────────┬──────────┘ riabele Teller op de waarde 1 wordt ge-
│ ┌─────────┴──────────┐ zet. Deze variabele wordt geprint (op
│ │ PRINT Teller │ het scherm gezet).
│ └─────────┬──────────┘ Nu botst de kringloop tegen het keer-
│ ┌─────────┴──────────┐ punt: NEXT Teller aan waar de vraag
│ │ Is Teller al 10? ├───┐ wordt gesteld: heeft Teller de waarde
│ └─────────┬──────────┘Ja │ 10 al bereikt. Het antwoord is nu nog
│ │Nee │ nee omdat Teller pas 1 is. Daarom wordt
│ ┌─────────┴──────────┐ │ Teller met 1 opgehoogd, wordt 2 en keert
│ │ Teller=Teller+1 │ │ terug in de kringloop. Daarin wordt de
│ └─────────┬──────────┘ │ waarde van Teller weer geprint. En weer
│ ┌─────────┴──────────┐ │ wordt de vraag gesteld of Teller al 10
└─┤ NEXT Teller │ │ is geworden.
└────────────────────┘ │ Zo lang Teller NIET 10 is, houdt NEXT de
│ kringloop gaande. Pas wanneer Teller WEL
┌────────────────────┐ │ 10 is geworden, zet NEXT het deurtje
│ Rest programma <───┘ open en mag de programma-flow verder
│ │ gaan. De FOR/NEXT-kringloop mag dan wor-
■ ■ den verlaten.
Het programma waarin we de computer van 1 tot en met 10 laten tellen,
ziet er nu ontzettend simpel uit:
┌───────────────────────┐ Gewapend met deze basiskennis kunnen we
│ 10 FOR Teller=1 to 10 │ nu het maken van de tafel van 10 automa-
│ 20 PRINT Teller │ tiseren. Niet zoals in het domme progra-
│ 30 NEXT Teller │ op de eerste pagina, maar zoals het
│ 40 END │ hoort in een FOR/NEXT-lus.
└───────────────────────┘
In een tafel wordt ook van 1 tot ╔════════════════════════════╗
en met 10 geteld maar achter elke ║ De tafel van 10 ║
Teller komt nog wat meer te staan: ╟────────────────────────────╢
het maalteken, de constante waar- ║ 10 CLS ║
mee moet worden vermenigvuldigd, ║ 20 Tafel = 10 ║
het isgelijkteken en tenslotte de ║ 30 FOR Teller = 1 TO Tafel ║
uitkomst van de vermenigvuldiging. ║ 40 PRINT Teller; ║
Ook dat brengen we onder in de ║ 50 PRINT " x "; ║
FOR/NEXT-lus. Daarbij zorgen we ║ 60 PRINT Tafel; ║
ervoor dat de cursor bij een vol- ║ 70 PRINT " = "; ║
gende PRINT-opdracht niet naar ║ 80 PRINT Teller * Tafel ║
een nieuwe regel verspringt door ║ 90 NEXT Teller ║
achter datgene wat er moet wor- ║ 100 END ║
den geprint de puntkomma te plaat- ╚════════════════════════════╝
sten:
┌──────────────┐
│ PRINT Teller;│ Wie nu heel even doordenkt, beseft dat we met deze
│ PRINT " x "; │ programmeerkennis ELKE tafel kunnen laten maken. In
│ PRINT Tafel; │ regel 20 van het voorbeeldprogramma wordt de numerie-
│ PRINT " = "; │ ke variabele Tafel benoemd met de waarde 10.
└──────────────┘ We kunnen Tafel elke gewenste waarde meegeven door aan
het begin van het programma en nog voordat de kringloop begint om INPUT
te laten vragen en het antwoord van de gebruiker in de variabele Tafel
op te nemen. In dit geval kan regel 20 van het programma worden gewij-
zigd in:
20 PRINT "Welke tafel?"; ┌─────────────────■TIP■──────────────────┐
25 INPUT Tafel │ Als we beginnen met programmeren kun- │
of korter: │ nen we de regelnummers met increments │
20 INPUT "Welke tafel";Tafel │ van tien automatisch op het scherm la- │
│ ten zetten met de opdracht: AUTO │
FOR/NEXT-manipulaties └────────────────────────────────────────┘
Elke repeterende werkzaamheid binnen een GWBASIC-programma brengen we
onder in de FOR/NEXT-kringloop. Daarbij kunnen we ook de wijze waarop
moet worden geteld variëren. Stel dat we alle oneven getallen willen
weten tussen de getallen 1 en 50. We moeten dat sprongs- of stapsgewijs
laten tellen. Dat kan met de extra opdracht: STEP, gevolgd door de stap-
waarde. Die is bij het laten printen van alléén de oneven getallen:
STEP 2.
╔═════════════════════════════════════╗ Als we het hiernaast staande
║ 10 REM GEEF ONEVEN GETALLEN WEER ║ programmaatje in GWBASIC invoe-
║ 20 FOR Stapteller = 1 TO 50 STEP 2 ║ ren en laten RUNnen, zal opval-
║ 30 PRINT Stapteller, ║ len dat de oneven getallen net-
║ 40 NEXT Stapteller ║ jes in kolommen van vier op het
╚═════════════════════════════════════╝ scherm komen te staan. Dat komt
doordat in de PRINT-opdracht van regel 30 achter de variabele Stapteller
een komma is geplaatst. Deze komma zorgt voor het afdrukken in TABula-
tor-formaat. Zou in plaats van de komma een puntkomma zijn geplaatst dan
zouden de uitkomsten achter elkaar worden geprint. Staat er geen leeste-
ken achter Stapteller dan worden ze onder elkaar gezet.
Wachtlussen
Sommige programma-afwerkingen gaan zó snel dat de programmeur het nodig
kan vinden de programma-flow wat af te remmen. Het is gewoonte dan ge-
bruik te maken van een loze FOR/NEXT-loop. Het programma wordt gedwongen
een FOR/NEXT-loop af te werken en daarin niets te doen, bijvoorbeeld:
800 FOR I=1 TO 10000:NEXT I ╔═══════════════════════════════╗
De tijd die de computer nodig heeft ║ Tijdonafhankelijke ║
om zo'n loze kringloop te doorlopen ║ wachtlus met TIMER ║
bedraagt één tot enkele seconden, af- ╟───────────────────────────────╢
hankelijk van de snelheid van de pro- ║ 10 PRINT "Hoeveel seconden?" ║
cessor. Aangezien we te maken hebben ║ 20 INPUT S ║
met diverse types PC (de XT, de AT, ║ 30 GOSUB 1000 ║
de 386-PC en de 486-PC) die alle op ║ 40 BEEP ║
verschillende interne kloksnelheden ║ 50 PRINT S;"seconden voorbij" ║
lopen, werkt de wachtlus in de vorm ║ 60 END ║
van een loze FOR/NEXT-loop niet lek- ╠═══════════════════════════════╣
ker. Wie gebruik maakt van een BASIC- ║ 1000 T=TIMER ║
compiler als TurboBASIC of QuickBASIC ║ 1010 WHILE TIMER-T < S ║
gebruikt de wachtlus dan ook niet. Hij ║ 1020 WEND ║
heeft daarvoor een tijdonafhankelijke ║ 1030 RETURN ║
wachtopdracht: DELAY x, waarin de va- ╚═══════════════════════════════╝
riabele x het aantal seconden bedraagt. DELAY (onbekend in GWBASIC) kun-
nen we wel namaken in het hiernaast staande Programmaatje. Het maakt
gebruik van de GWBASIC-functie: TIMER. Dat is de zogenoemde microtimer
die in veel RUN-programma's ook dienst doet om RANDOMIZE van een wille-
keurig zaadgetal te voorzien.
Terugtellen
We kunnen het verloop van een programmadeel binnen een FOR/NEXT-lus ook
in omgekeerde volgorde laten afwerken. De reeds genoemde extra opdracht:
STEP speelt daarin een belangrijke rol en moet bij omgekeerde volgorden
altijd worden benoemd. De simpelste vorm van een teruglopende FOR/NEXT-
lus maakt direct duidelijk wat we bedoelen. We hebben de PC in GWBASIC
al laten (op)tellen: van 1 tot en met 10. In één programmaregel weerge-
geven gaat dat als volgt:
┌──────────────────────────────────────────────────────────────────────┐
│ 10 FOR Teller=1 TO 10:PRINT Teller:NEXT Teller │
└──────────────────────────────────────────────────────────────────────┘
Willen we cijfers in omgekeerde volgorde op het scherm zien dan moeten
we de variabele Teller ook terug laten tellen.
ECHTER:
┌──────────────────────────────────────────────────────────────────────┐
│ 10 FOR Teller=10 TO 1:PRINT Teller:NEXT Teller │
└──────────────────────────────────────────────────────────────────────┘
zal niet werken. GWBASIC geeft weliswaar geen foutmelding maar op het
scherm verschijnt niets. De teruglopende FOR/NEXT-lus is namelijk incom-
pleet. We zijn de STEP-waarde vergeten die bij teruglopende FOR/NEXT-
lussen verplicht is èn altijd een negatieve waarde krijgt. Terugtellen
van 10 tot en met 1 gaat aldus:
┌──────────────────────────────────────────────────────────────────────┐
│ 10 FOR Teller=10 TO 1 STEP -1:PRINT Teller:NEXT Teller │
└──────────────────────────────────────────────────────────────────────┘
Nu ligt het voor de hand dat wanneer we alléén de even cijfers in omge-
keerde volgorde willen zien, de programmaregel aldus moet zijn:
┌──────────────────────────────────────────────────────────────────────┐
│ 10 FOR Teller=10 TO 1 STEP -2:PRINT Teller:NEXT Teller │
└──────────────────────────────────────────────────────────────────────┘
en alle oneven cijfers:
┌──────────────────────────────────────────────────────────────────────┐
│ 10 FOR Teller=9 TO 1 STEP -2:PRINT Teller:NEXT Teller │
└──────────────────────────────────────────────────────────────────────┘
OPGAVE: ╔══════════════════════════════╗
Type het hiernaast staande voorbeeld- ║ 100 CLS:KEY OFF:WACHT=.2 ║
programma over in GWBASIC. RUN het. ║ 110 FOR A=1 TO 80 ║
Bestudeer het. Geef vervolgens LIST ║ 120 LOCATE 1,A ║
en ga de programma-flow na. Nu moet ║ 130 PRINT CHR$(219); ║
u aan de hand van de listing begrij- ║ 140 LOCATE 5,1 ║
pen wat er gebeurt. Als u niet voor ║ 150 PRINT "LOCATE 1,";A ║
honderd procent snapt wat het pro- ║ 160 GOSUB 500 ║
grammaatje doet, is het verstandig ║ 170 NEXT A ║
deze les opnieuw door te nemen. ║ 180 BEEP ║
║ 190 LOCATE 5,1 ║
Als dit programma goed loopt, wordt ║ 200 PRINT "Druk 'n toets" ║
in de FOR/NEXT-loop van regel 110 ║ 210 WHILE INKEY$="":WEND ║
tot en met 170 een balk bovenin het ║ 220 FOR A=80 TO 1 STEP -2 ║
scherm getrokken. De tijdonafhanke- ║ 230 LOCATE 1,A ║
lijke wachtlus uit de subroutine in ║ 240 PRINT " "; ║
500 zorgt voor de vertraging. Als de ║ 250 LOCATE 5,1 ║
balk is getrokken, volgt een BEEP en ║ 260 PRINT "LOCATE 1,";A ║
de vraag van het programma om een ║ 270 GOSUB 500 ║
toets in te drukken. Vervolgens gaat ║ 280 NEXT A ║
een volgende FOR/NEXT-loop in omge- ║ 290 END ║
keerde volgorde aan het werk. Van ║ 500 T=TIMER ║
achter naar voren wordt in de balk ║ 510 WHILE TIMER-T<WACHT ║
en afwisselend om de twee schermpo- ║ 520 WEND ║
sities (STEP -2) een spatie (" ") ║ 530 RETURN ║
geprint. Een kartelbalk is het uit- ╚══════════════════════════════╝
eindelijke resultaat.
Valt het u op dat het een goede programmeergewoonte is om extra ruimte
of indents te gebruiken in de regels die zich tussen FOR en NEXT afspe-
len? Dit is niet verplicht maar het verhoogt de leesbaarheid van het
programma.
Een ander type kringloop: WHILE/WEND
De FOR/NEXT-loop is in principe een eindige kringloop. Hij loopt steeds
TOT EN MET iets. Zodra het einddoel wordt bereikt, wordt de lus door
NEXT beëindigd. Men kan ook zeggen dat een FOR/NEXT-loop zich beweegt
van A naar B en er naar streeft om B te bereiken.
De WHILE/WEND-loop die we in de voorbeeldprogrammaatjes al diverse keren
zijn tegengekomen, streeft een ander doel na. Binnen dit type loop
vraagt het programma zich voortdurend af of een bepaalde situatie nog
steeds van kracht is. Letterlijk zegt de WHILE/WEND-loop tegen zichzelf:
ZOLANG (WHILE) een situatie van kracht is, DOE dan wat er in de loop
staat. BLIJF dit doen (WEND) totdat die situatie verandert.
Bijvoorbeeld:
ZOLANG er geen toets wordt ingedrukt DOE dan niets
of anders gezegd:
ZODRA er een toets wordt ingedrukt GA dan verder met het programma.
Als we dit even tot ons laten doordringen wordt deze programmaregel
leesbaar:
┌──────────────────────────────────────────────────────────────────────┐
│ 100 WHILE INKEY$="":WEND │
└──────────────────────────────────────────────────────────────────────┘
Deze permanente wachtlus waarin op een toetsaanslag wordt gewacht, is
een stuk eleganter dan het vaak gebruikte alternatief:
┌──────────────────────────────────────────────────────────────────────┐
│ 100 I$=INKEY$:IF I$="" THEN 100 │
└──────────────────────────────────────────────────────────────────────┘
Met dezelfde, simpele WHILE/WEND-constructie kunnen we ook gemakkelijk
op een bepaalde intoetsing laten wachten, bijvoorbeeld van de Enter-
toets: CHR$(13) of van de Esc-toets: CHR$(27):
┌──────────────────────────────────────────────────────────────────────┐
│ 100 PRINT "Druk op <Enter>" │
│ 110 WHILE INKEY$<>CHR$(13):WEND │
└──────────────────────────────────────────────────────────────────────┘
In dit geval reageert het programma uitsluitend op het indrukken van de
Enter-toets. Alle andere intoetsingen worden genegeerd.
╔═══════════════════╗ In het programmaatje hiernaast laten we een be-
║ 100 CLS:KEY OFF ║ paalde situatie voortduren. Zolang die situatie
║ 110 WHILE B<10000 ║ van kracht is moet er gebeuren wat er tussen
║ 120 A=A+1 ║ WHILE en WEND staat.
║ 130 B=A*A ║ In gewone taal: Zolang de variabele B kleiner is
║ 140 PRINT B ║ dan 10000 moet A met 1 worden opgehoogd; B krijgt
║ 150 WEND ║ de inhoud van de vermenigvuldiging A maal A en de
╚═══════════════════╝ uitkomst van B moet kolomsgewijs op het scherm
worden afgedrukt. Zodra B de waarde 10000 heeft bereikt kan WEND de
kringloop niet meer tegenhouden en terugsturen.
De WHILE/WEND-kringloop is bij uitstek geschikt om tot in het oneindige
naar iets te zoeken. Priemgetallen bijvoorbeeld. Of alle getallen onder
de 1000 die deelbaar zijn door 13:
Bij het begin van dit voor- ╔═══════════════════════════════════════╗
beeldprogramma wordt de va- ║ 100 REM Zoek getallen deelbaar door 13║
riabele A eerst op 1000 ge- ║ 110 A=1000 ║
zet. Dan wordt de WHILE/WEND ║ 120 WHILE A>13 ║
in gang gezet. Zolang A groter║ 130 A=A-1 ║
blijft dan 13 wordt A telkens ║ 140 B=A/13 ║
met 1 verminderd. Het resul- ║ 150 IF B=INT(B) THEN PRINT A, ║
terende getal wordt door 13 ║ 160 WEND ║
gedeeld en de uitkomst wordt ╚═══════════════════════════════════════╝
in de variabele B geplaatst. Dan vraagt het programma zich binnen de
WHILE/WEND-loop af of de inhoud van B een integer getal is, dus zonder
restgetallen. Een geheel getal dus. Als dat zo is, is A deelbaar door 13
en wordt dat getal geprint. Afhankelijk van de waarde die A heeft,
kaatst WEND de verwerking terug naar WHILE. We weten dat WEND het deur-
tje pas openzet en de kringloop beëindigt als zij merkt dat A kleiner is
geworden dan 13.
Zoeken binnen WHILE/WEND
Zoek naar iets net zo lang totdat je het gevonden hebt... Dat is een
uitstekende opdracht om binnen een WHILE/WEND-kringloop toe te passen.
╔═════════════════════════════════╗ Hiernaast staat een programma dat u
║ 10 RANDOMIZE TIMER:CLS ║ in GWBASIC moet intypen en RUNnen.
║ 20 WHILE A<>100 ║ Het is een WHILE/WEND-loop waarin
║ 30 B=B+1 ║ naar het getal 100 wordt gezocht in
║ 40 A=INT(RND*500) ║ een willekeurige reeks van getallen
║ 50 LOCATE 10,27 ║ die we door een random generator
║ 60 PRINT "Aantal loops:";B ║ laten creëren. In regel 10 laten we
║ 70 LOCATE 10,27 ║ de microtimer van GWBASIC (TIMER)
║ 80 PRINT "Inhoud van A:";A ║ het zaadgetal voor de randomizer
║ 90 WEND ║ geven. Het scherm wordt gewist en
║ 100 BEEP:END ║ de WHILE/WEND-loop begint.
╚═════════════════════════════════╝ De bedoeling is dat het getal 100
wordt gevonden in een willekeurig te genereren reeks van getallen.
Hoeveel maal de RND-functie in regel 40 in actie komt, komen we te weten
door het tellertje in regel 30. Elke keer wordt op schermpositie 10,27
(regel 10, kolom 27) deze telling bijgehouden achter de aanduiding: Aan-
tal loops. Daaronder, op LOCATE 12,27, komt achter de aanduiding: Inhoud
van A, de inhoud van variabele A uit regel 40 te staan.
Het zoeken naar het getal 100 gebeurt uit een willekeurige reeks van 500
(regel 40). De loop kan kort, vrij lang of heel lang duren; dat is af-
hankelijk van het toeval. In elk geval blijft de WHILE/WEND-loop net zo
lang lopen totdat -toevallig- het getal 100 door de randomizer is geko-
zen.
Voordat u een repeterende functie gaat programmeren moet u altijd even
nadenken en uzelf afvragen: welk type kringloop is voor mijn doel het
meest geschikt: de FOR/NEXT- of de WHILE/WEND-loop.
Geneste loops
Zoals al eerder benadrukt, is de FOR/NEXT-loop zeer veelzijdig. Behalve
dat is hij ook bijzonder plooibaar. Er zijn lange programma's denkbaar
waarin van alles en nog wat gebeurt en die zich vanaf het begin (RUN)
tot het eind (END) afspelen binnen één zeer omvangrijke FOR/NEXT- of
WHILE/WEND-loop.
Een kringloop die met FOR .. begint, kan in zichzelf ook weer een
FOR/NEXT-loop in zich hebben. En die ook weer, enzovoorts. We spreken
dan van een geneste kringloop. Het programmeren van een geneste kring-
loop kan behoorlijk lastig zijn en de programmeur moet zijn hersens er
goed bij houden onder andere omdat de eerste of Hoofdloop pas mag eindi-
gen als de binnenste loop is afgemaakt.
Men kan zich een geneste loop voorstellen als een stel tandraderen die
in elkaar draaien. Duidelijk voorbeeld is de digitale klok die seconden,
minuten en uren registreert. Dit moet in drie geneste loops gebeuren: de
binnenste voor het tellen van de seconden; een middelste voor het tellen
van de minuten en een buitenste voor de uren.
Schematisch:
┌───────────────┐
│ Uur=0 │
└───────┬───────┘
┌───────┴───────┐
│ Minuut=0 │
└───────┬───────┘
┌─────────────────┐ ┌───────┴───────┐
┌───┤ Minuut=Minuut+1 │ │ Sec=0 │
│ └────────┬────────┘ └───────┬───────┘
│ ┌────────┴────────┐ N │
│ │ Is Minuut 60? ├─┐ │
│ └────────┬────────┘ │ │
│ │J │ ┌───────┴───────┐
│ ┌────────┴────────┐ ├─> Sec=Sec+1 <───┐
│ │ Minuut=0 │ │ └───────┬───────┘ │
│ └────────┬────────┘ │ ┌───────┴───────┐N │
│ ┌────────┴────────┐ │ │ Is Sec 60? ├───┘
│ │ Uur=Uur+1 │ │ └───────┬───────┘
│ └────────┬────────┘ │ │J
│ └──────────┘ ┌───────┴───────┐
│ │ Sec=0 │
│ └───────┬───────┘
└─────────────────────────────────┘
Dit schema behoeft enige bestudering. Dat spreekt vanzelf. Bekijk het op
uw gemak en volg de 'flow' van het diagram. We zien dan dat de buitenste
loop, die van de secondentelling, het drukst in de weer is. De middelste
loop krijgt alleen na het vollopen van de secondentelling (Sec=60) even
iets te doen. De buitenste of hoofdloop komt om de 60x60 seconden of om
de 60 minuten even in actie.
Dit gaan we programmeren in een geneste FOR/NEXT-kringloop:
╔════════════════════════════════════════════╗ In dit voorbeeldpro-
║100 REM Uren, minuten, seconden ║ gramma loopt een digi-
║110 CLS:KEY OFF:WACHT=1 ║ tale klok. Type het
║120 LOCATE 9,20:PRINT "Loop 1 - Seconden:" ║ over en RUN het. Ver-
║130 LOCATE 10,20:PRINT "Loop 2 - Minuten :" ║ geet niet de apostrofe
║140 LOCATE 11,20:PRINT "Loop 3 - Uren :" ║ (') te typen direct
║150 FOR UREN=1 TO 24 ║ achter regel 180. Dit
║160 FOR MINUTEN=1 TO 60 ║ is een REMmetje. Het
║170 FOR SECONDEN=1 TO 60 ║ schakelt deze regel uit
║180 ' GOSUB 280 ║ en daarmee ook de gang
║190 SEC=SEC+1 ║ naar de subroutine 280
║200 LOCATE 9,40:PRINT SEC ║ waar een tijdonafhanke-
║210 NEXT SECONDEN ║ lijke WHILE/WEND-wacht-
║220 SEC=0:MIN=MIN+1 ║ lus wordt aangeroepen.
║230 LOCATE 10,40:PRINT MIN ║ Deze wachtlus staat op
║240 NEXT MINUTEN ║ één seconde: de tijd
║250 MIN=0:UUR=UUR+1 ║ die tussen de seconden
║260 LOCATE 11,40:PRINT UUR ║ moet verstrijken. Omdat
║270 NEXT UREN ║ het dan veel te lang
║280 T=TIMER:WHILE TIMER-T<WACHT:WEND:RETURN ║ duurt om deze geneste
╚════════════════════════════════════════════╝ FOR/NEXT-loop aan het
werk te zien, is de subroutine met het REMmetje op nonactief gezet.
Om een realistische registratie van seconden, minuten en uren te krijgen
moet de apostrofe met Del worden verwijderd of met een spatie worden
overschreven.
Merk ook op dat de overzichtelijkheid wordt gediend door deze geneste
FOR/NEXT-loop met inspringingen of indents te markeren. Het wordt daar-
door mogelijk de loop in ons voorstellingsvermogen aan het werk te zien.
Dat vereist de nodige ervaring maar het gebeurt vooral om het 'debuggen'
ofte wel het opsporen en corrigeren van programmeerfouten en -onvolko-
menheden te vergemakkelijken.
Zo, ik dacht dat we het hier voorlopig maar bij moeten laten. Het was
een lange en pittige les en ik moedig u aan om juist deze les enorm goed
te bestuderen. Als de FOR/NEXT-loop en de WHILE/WEND-lus gesneden koek
voor u wordt, bent u al verder dan halverwege om een GWBASIC-programmeur
te worden.
U kunt uzelf trouwens ook testen of u al op deze goede weg bent. Met de
kennis die in de drie lessen tot nu toe is aangedragen moet het al moge-
lijk zijn om zelf een probleempje te bedenken en dat in een GWBASIC-pro-
grammaatje voor eens en altijd op te lossen.
Mag ik u op weg helpen met de volgende opdracht?
┌────────┐
┌───────────────────────────────┤Huiswerk├─────────────────────────────┐
│ └────────┘ │
│Als het u ernst is met het leren programmeren dan moet u het geleerde │
│zo veel mogelijk in praktijk brengen met kleine vingeroefeningen. Veel│
│proberen (en veel fouten maken) is de weg die naar het einddoel voert.│
│Als huiswerk geef ik op: programmeer een dobbelsteen. In een FOR/NEXT-│
│loop wordt heel snel van 1 tot en met 6 geteld totdat er op een toets │
│wordt gedrukt. Op dat moment komt de dobbelsteen tot rust en staat op │
│het scherm of het een een, twee, drie, vier, vijf of zes is geworden. │
│Denk eerst even na en maak een stroomschemaatje dat er als volgt zou │
│kunnen uitzien: │
│ ┌───────────────────┐ │
│ ┌───> Zolang geen toets │ │
│ │ └────────┬──────────┘ │
│ │ ┌───┴───┐ │
│ │ │ LOOP │ │
│ │ └───┬───┘ │
│ │ ┌───┴───┐ j │
│ │ │ Toets?├───────┐ │
│ │ └───┬───┘ │ │
│ │ │n │ │
│ │ ┌──────┴───────┐ │ │
│ │ │ 1,2,3,4,5of6?│ │ │
│ │ └──────┬───────┘ │ │
│ │ ┌────┴─────┐ │ │
│ └───────┤ Doorgaan │ │ │
│ └──────────┘ │ │
│ ┌───────┐ │ │
│ │ STOP <───────┘ │
│ └───────┘ │
└──────────────────────────────────────────────────────────────────────┘
In de volgende les (Deel 4 alweer) komt weer een pittig onderwerp aan de
orde: tekstbewerking. Dat is het gedoe dat u zo vaak in RUN-programma's
in het AD tegenkomt met LEFT$, RIGHT$ en MID$. Maar we duiken nog verder
in de goed gevulde gereedschapskist van GWBASIC totdat we genoeg gereed-
schappen kunnen hanteren om een kleine programmeerwedstrijd onder de
cursisten te houden.
TOT SLOT:
Op deze RUN-Special staat het programmaatje CAI (Computer Aided In-
struction), in GWBASIC (CAI.BAS). In dit programmaatje wordt de
FOR/NEXT-loop van het maken van een willekeurige tafel gesimuleerd. U
kunt de loop voor uw ogen zien werken.
CAI.BAS is een bijzonder programma. Het simuleert in GWBASIC... GWBASIC
zelf! Het geeft een listinkje van een programma dat tafels maakt. Na de
invoer wordt de FOR/NEXT-loop gestart en kunt u de kringloop zien afspe-
len.
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
DE GWBASIC INTERPRETER (4)
Tekstbewerking
Deze vierde les gaat over tekstBEwerking; niet over tekstVERwerking. Wie
de lessen van deze cursus tot nu toe aandachtig en zelf werkzaam heeft
gevolgd, is al een eind op streek. Hij of zij beschikt al over de hulp-
middelen om zelf kleine programmaatjes (vingeroefeningen) te doen. Met
de kennis en ervaring die deze les biedt, wordt het al mogelijk zelf
programma's te schrijven die de moeite van het bewaren op schijf waard
zijn.
De ASCII-tabel
Iedere programmeur in onverschillig welke taal heeft de beroemde ASCII-
tabel altijd binnen handbereik. Deze tabel bevat de internationaal ge-
standaardiseerde codering voor de cijfers 1 tot en met 0, de hoofdlet-
ters A tot en met Z, de kleine of onderkastletters a tot en met z, een
groot aantal speciale en exotische tekens als é, ï, ƒ, ¿ en æ.
In de ASCII-tabel (ASCII is de afkorting voor American Standard Code for
Information Interchange) staan ze allemaal en heeft elk teken een num-
mer.
De nummers van de tekens corresponderen met de wijze waarop in
GWBASIC een teken zichtbaar kan worden gemaakt op het beeldscherm of op
de printer. Op het beeldscherm maken we de letter e zichtbaar door op
het toetsenbord de e-toets in te drukken maar onder GWBASIC kan het ook
aldus:
PRINT CHR$(101)
omdat nr. 101 van de ASCII-tabel de letter e is. CHR$( gevolgd door een
ASCII-code en een haakjes sluiten haalt uit de karakterset het gevraagde
teken op.
Met het geleerde uit de vorige les (de FOR/NEXT-kringloop) kunnen we de
afdrukbare ASCII-tekens op het beeldscherm zichtbaar maken.
╔═══════════════════════════════════════════╗ We doen dit met neven-
║ Maak ASCII-Tabel zichtbaar ║ staand programma pagina-
╟───────────────────────────────────────────╢ gewijs zodat een nauwge-
║ 10 CLS:KEY OFF ║ zette bestudering moge-
║ 20 B%=0:C%=6 ║ lijkt wordt. Daartoe dient
║ 30 GOSUB 110 ║ het tellertje Q% dat niet
║ 40 B%=8:C%=8 ║ meer dan 66 tekens tege-
║ 50 GOSUB 110 ║ lijk op één scherm af-
║ 60 B%=14:C%=27 ║ drukt en er vervolgens
║ 70 GOSUB 110 ║ voor zorgt dat met een
║ 80 B%=32:C%=255 ║ druk op een toets de vol-
║ 90 GOSUB 110 ║ gende serie wordt geprint.
║ 100 END ║ De variabelen B% en C% wor-
║ 110 FOR A%=B% TO C% ║ den zodanig gekozen dat zij
║ 120 Q%=Q%+1 ║ de niet afdrukbare ASCII-
║ 130 PRINT "CHR$(";A%;") = ";CHR$(A%);" ", ║ tekens overslaan.
║ 140 IF Q%=66 THEN Q%=0:GOTO 150 ELSE 160 ║ Het belangrijkste deel van
║ 150 WHILE INKEY$="":WEND:PRINT ║ het programmaatje is, zo-
║ 160 NEXT A% ║ als u ziet ondergebracht in
║ 170 RETURN ║ de subroutine die in regel
╚═══════════════════════════════════════════╝ 110 begint en die in to-
taal driemaal met verschillende variabelen wordt aangeroepen.
Dat is al met al een hele uitwijding over de ASCII-tabel: basiskennis
die we nodig hebben om met succes tekstbewerking in GWBASIC te kunnen
toepassen.
Het woord RUN
Wanneer we het woord RUN opslaan in een string- of tekstvariabele, kun-
nen we met het woord gaan spelen: A$="RUN". We kunnen nu van het woord
in A$ een gedeelte zichtbaar maken, te rekenen vanaf de meest linkse
(LEFT) positie. We moeten dan natuurlijk wèl aangeven welk gedeelte.
Alléén de R is de meest linkse letter en daarvoor maken we gebruik van
de functie: LEFT$(A$,1). Hier staat letterlijk: laat van het woord in A$
het gedeelte zien TOT EN MET 1.
Het is gemakkelijk in te zien dat:
PRINT LEFT$(A$,2)
het stukje van woord RUN tot en met de tweede letter laat zien: RU dus.
Als u LEFT$(X$,x) begrijpt, moet het mogelijk zijn het resul-
taat van het kleine programmaatje ╔══════════════════════╗
hiernaast zelf uit de regels af te ║ 10 A$="RUN" ║
leiden zonder het programma in te ty- ║ 20 PRINT LEFT$(A$,3) ║
pen en daadwerkelijk te RUNnen. ║ 30 PRINT LEFT$(A$,2) ║
Dat is simpel. Als u dat ook vindt dan ║ 40 PRINT LEFT$(A$,1) ║
wordt het volgende onderwerp even simpel ║ 50 END ║
want dat gaat over RIGHT$(X$,x) en u ╚══════════════════════╝
raadt al dat het hier gaat om het spiegelbeeld van LEFT$(X$,x).
In woorden uitgedrukt betekent RIGHT$(X$,x): het gedeelte van het woord
in X$, te rekenen vanaf de meest rechtse (RIGHT) positie. Zonneklaar zal
het dus zijn dat
PRINT RIGHT$(A$,1)
de meest rechtse letter van RUN in A$ zal op leveren: de N.
PRINT RIGHT$(A$,2)
levert dan het woorddeel op dat één positie naar rechts eindigt: UN.
Met LEFT$ en RIGHT$ beschikken we over twee methoden om een woord of zin
in een stringvariabele van twee kanten binnen te gaan en delen daarvan
te isoleren.
We veranderen de stringvariabele A$ in:
A$="RUN Flagazine". U moet nu de volgende opdracht uitvoeren: Maak uit
A$ twee nieuwe stringvariabelen: B$ en C$. In B$ moet alléén de deel-
tekst: RUN worden gebracht en in C$ alléén de deeltekst: Flagazine.
Ga uw gang...
Gelukt? De juiste wijze van werken is als volgt:
╔═══════════════════════╗ Had u dat ook? Natuurlijk. Dat kan moeilijk
║ 10 A$="RUN Flagazine" ║ anders want het werken van LEFT$ en RIGHT$ is
║ 20 PRINT A$ ║ simpel. Met name LEFT$ wordt veel gebruikt om
║ 30 PRINT LEFT$(A$,3) ║ het eerste ingetypte teken van een invoer-
║ 40 PRINT RIGHT$(A$,9) ║ string te weten te komen. Hoe vaak komt het
║ 50 END ║ niet voor dat een programma de beroemde (j/n)-
╚═══════════════════════╝ vraag stelt. We weten nooit wat de gebruiker
zal doen. Typt hij voluit ja of JA of j of J als hij 'ja' bedoelt of
nee, NEE, n of N als hij 'nee' wil zeggen? Voorbeeld:
100 PRINT "Wilt u nog een berekening laten uitvoeren (j/n)";
110 INPUT A$
┌───┐
┌────────────────────────────────┤TIP├─────────────────────────────────┐
│ └───┘ │
│ Er bestaat een elegante methode om de (j/n) invoer klunsvast in een │
│ GWBASIC-programma op te nemen: niet met LEFT$(X$,x) maar met de zoge-│
│ noemde INSTR-functie. Ter vergelijking laten we eerst de 'normale' │
│ en omslachtige methode zien: │
│ │
│ 100 INPUT "Nog een keer spelen (j/n)";A$ │
│ 110 T$=LEFT$(A$,1):REM VOOR HET GEVAL DAT Ja OF Ja graag IN A$ STAAT │
│ 120 IF T$<>"J" AND T$<>"j" AND T$<>"N" AND T$<>"n" THEN BEEP:GOTO 100│
│ 130 IF T$="J" OR T$="j" THEN RUN ELSE CLS:END │
│ │
│ Dit hele verhaal met AND's en OR's kan veel simpeler door de eerste │
│ letter van de invoer in A$ te isoleren in T$ en vervolgens met INSTR │
│ te kijken welke waarde T$ heeft. Bestudering van onderstaande listing│
│ en natuurlijk het uitproberen daarvan op de eigen PC zal zéér verhel-│
│ derend werken. │
│ │
│ 100 INPUT "Nog een keer spelen (j/n)";A$ │
│ 110 T$=LEFT$(A$,1) │
│ 120 IF INSTR("JjNn",T$) = 0 OR T$ = "" THEN BEEP:GOTO 100 │
│ 130 IF INSTR("JjNn",T$) < 3 THEN PRINT "Ja dus...":END │
│ 140 PRINT "Nee dus...":END │
│ │
│ Eerst worden in regel 120 mogelijke baldadigheden afgevangen. Elke │
│ invoer in T$, die niet J, j, N of n is of een lege string is die het │
│ gevolg is van een druk op <Enter> worden opgemerkt en met een BEEP en│
│ een terugverwijzing naar regel 100 afgestraft. In 130 wordt gekeken │
│ of T$ een J of een j bevat. INSTR vergelijkt en zal een J vinden op │
│ positie 1 en een j op positie 2. In dat geval levert INSTR een waar- │
│ de op die kleiner is dan 3. Staat er geen J of j in T$ dan faalt re- │
│ gel 130 en komt regel 140 aan de beurt. In T$ kan alleen maar een N │
│ een n staan. Het antwoord op de invoervraag luidt derhalve ontken- │
│ nend. │
└──────────────────────────────────────────────────────────────────────┘
Nu wordt het moeilijk: MID$(X$,x,x)
Voordat we ons in de problemen van MID$ storten nog even iets vooraf.
Het kan onder bepaalde omstandigheden noodzakelijk zijn in een programma
te weten te komen hoe LANG een tekst is die een gebruiker in een in te
voeren string heeft gestopt. Het programma accepteert elke tekst in bij-
voorbeeld:
INPUT A$
of
LINE INPUT A$
Alvorens zo'n string met LEFT$, RIGHT$ of MID$ te lijf te gaan, zullen
we foutmeldingen moeten voorkomen door eerst te bepalen uit hoeveel
ASCII-tekens de string bestaat. We kunnen dat zeer snel en eenvoudig aan
de weet komen met de functie LEN.
We weten niet WAT de gebruiker heeft ingevoerd in A$ maar we kun-
nen er wèl achter komen uit hoeveel tekens A$ bestaat. Wanneer de ge-
bruiker bijvoorbeeld: WEET IK VEEL in A$ heeft ingevoerd dan zal:
PRINT LEN(A$) onthullen dat het aantal tekens in A$: twaalf is. Dit we-
tende hoeven we niet in het duister te tasten om te weten wat het eer-
ste, tweede ... laatste woord is dat de gebruiker heeft ingevoerd. Deze
woorden worden immers door een CHR$(32) -een spatie- gescheiden. We ko-
men daar straks nog op terug.
Ik wil (afhankelijk van LEN(X$)) weten welke de vierde, de vijfde of de
n-de letter van een string is. Sterker nog: ik wil weten wat de gebrui-
ker in A$ invoert tussen bijvoorbeeld de derde en de tiende positie van
A$.
U raadt het: daarvoor is MID$(X$,x,x) bedoeld. Gelijk maar een voorbeeld
om het helder te maken: Ik schrijf een programma om kinderen (en volwas-
senen) de schrijfwijze van moeilijke Nederlandse woorden bij te brengen.
De vraag is: hoe schrijf je 'onmiddellijk'? Dat woord bevat altijd twee
d's en twee l's. Hoe schrijft de leerling het. Hij schrijf het altijd
fout wanneer de negende letter geen l is. We controleren dat door de
leerling zijn zienswijze op het woord in A$ te laten invoeren. We con-
troleren zijn invoer op de juiste schrijfwijze met:
100 IF MID$(A$,9,1) <> "l" THEN PRINT "FOUT! Probeer opnieuw!"
Wat hebben we het programma laten zeggen:
ALS de tekst in A$, te rekenen vanaf de negende positie en over een be-
reik van 1 positie ONGELIJK is aan de letter 'l' DAN print "FOUT! Pro-
beer opnieuw.
De anatomie van MID$(X$,x,x) laat zich in gewone woorden aldus omschrij-
ven: bepaal het tekstdeel in X$, vanaf positie x over een x aantal te-
kens. In de string:
A$ = "Mijn naam is Nico Baaijens" zal:
PRINT MID$(A$,14,4)
als resultaat opleveren..... Precies: Nico.
Snapt u dit? Wat zal dan:
PRINT MID$(A$,5,4) te zien geven?
Met MID$ kunnen we overigens ook de eerste of de laatste letter van een
string te weten komen, hoewel LEFT$ en RIGHT$ daarin gespecialiseerd
zijn.
A$="Ja"
PRINT MID$(A$,1,1)
levert hetzelfde resultaat op als
PRINT LEFT$(A$,1)
Huiswerk
Schrijf een programmaatje waarin om invoer van een tekst wordt gevraagd
en stel vast of in die invoer het woord RUN voorkomt. Mijn interpretatie
van de opgave is de volgende:
100 CLS:KEY OFF
110 PRINT "Geef een tekst waarin het woord RUN voorkomt"
120 LINE INPUT A$
130 FOR A%=1 TO LEN(A$):REM WE TASTEN DE TOTALE INVOERSTRING AF
140 IF MID$(A$,A%,3)="RUN" THEN GOTO 170
150 NEXT A%
160 PRINT "Het woordje RUN heeft u niet gebruikt":END
170 PRINT "Het woordje RUN gevonden op positie";A%:END
Tot besluit een wijs woord van Confucius:
Hij die wat hij leert niet in praktijk brengt, vergeet en heeft niets
geleerd.
(Wordt vervolgd)