Programmeren VBA 1
Programmeren VBA 1
Colofon: Uitgave 1.1 Nummer Auteur Profieldeel Profiel Prijs
: : : : : :
© M.M. Witkam, juni 2002 1021 drs. M.M. Witkam Wiskunde
Niets uit deze uitgave mag verveelvuldigd en/of openbaar gemaakt worden door middel van druk, fotokopie, microfilm of op welke andere wijze dan ook, zonder voorafgaande schriftelijke toestemming van de auteur.
Programmeren VBA 1
INHOUDSOPGAVE 1. 2 3 4 5 6
Inleiding Variabelen, in- en uitvoer .............................................................................. 1 Typen variabelen en constanten, bewerkingen, functies............................... 9 Herhalingsstructuren....................................................................................17 Keuzestructuren ...........................................................................................43 Procedures, functies.....................................................................................59 Opdrachten ..................................................................................................81 Appendix
i
Programmeren VBA 1
ii
Programmeren VBA 1
Inleiding Dit is de cursus Programmeren VBA 1. In deze cursus leer je de basisvaardigheden van het ‘procedureel’ programmeren in de taal Visual Basic for Applications (VBA), een onderdeel van Microsoft Office. De cursus is gebaseerd op het programma Word. Je maakt hier kennis met variabelen en constanten, in- en uitvoer, de verschillende herhalings- en keuzestructuren, en het gebruik van procedures en functies. In het eerste hoofdstuk maak je voornamelijk kennis met de werkwijze bij het programmeren in VBA, en met elementaire in- en uitvoer. In hoofdstuk 2 staat een meer systematische beschrijving van enkele gegevenstypen, hun bewerkingen en functies. In het derde hoofdstuk wordt uitgebreid aandacht besteed aan methoden om (een deel van) een programma meer dan één keer te laten uitvoeren. Daarbij gaat het om herhalingen zolang aan een bepaalde voorwaarde is voldaan, totdat aan een bepaalde voorwaarde is voldaan, en constructies waarbij het aantal herhalingen vastligt. In hoofdstuk 4 worden de technieken behandeld om (een deel van) een programma voorwaardelijk te laten uitvoeren. Hoofdstuk 5, over procedures en functies, geeft de hulpmiddelen om programma’s op een goede manier te structureren. Na het bestuderen van deze hoofdstukken ben je in staat om een ‘groter’ programmeerprobleem zelfstandig op te lossen. In hoofdstuk 6 staan een paar van zulke problemen. Om zelfstandig met deze cursus te kunnen werken moet je een paar kleine aanpassingen in Word aanbrengen. Die staan voor Word 97 en 2000 beschreven in de appendix.
iii
Programmeren VBA 1
iv
Programmeren VBA 1
Hoofdstuk 1
1. Variabelen, in- en uitvoer In dit hoofdstuk maak je kennis met de gang van zaken bij het werken met Visual Basic for Applications of VBA, de programmeertaal van Microsoft Word, en met de eerste beginselen van het programmeren in VBA. Bij elk hoofdstuk hoort een Worddocument met voorbeeldprogramma’s. In die voorbeelden worden principes van VBA gedemonstreerd. Hieronder staat beschreven hoe je die voorbeelden oproept, bekijkt en laat uitvoeren. 1. Open het bestand Pv1-1v.doc. Je krijgt dan de volgende waarschuwing te zien.
Macro’s inschakelen dat gebruikt wordt voor de ‘uitvoer’ van de programma’s of macro’s. 2. Om de programma’s of macro’s te bekijken, te bewerken of te laten uitvoeren, geef je het commando Extra, Macro, Macro’s (of Alt+F8).
Kies daarbij altijd Macro’s in (document). 3. Selecteer Voorbeeld01, en druk op Uitvoeren. In het eerst nog lege document verschijnt dan het ‘sommetje’: 3,7 + 4,4 = 8,1. Dit is het resultaat van de in voorbeeld 1 opgenomen instructies. Het is natuurlijk de bedoeling dat je dergelijke instructies leert begrijpen en dat je ze zelf kunt opstellen.
1
Programmeren VBA 1
Hoofdstuk 1
4. Om de programma-instructies te zien – en eventueel te wijzigen – ga je als volgt te werk. Geef weer het commando Extra, Macro, Macro’s, en selecteer weer Voorbeeld01. Druk nu op Bewerken. Je komt dan in de ‘Visual Basic-editor’.
Zorg ervoor dat je in de Visual Basic-editor de ‘projectverkenner’ kunt zien (commando: Beeld, Projectverkenner, of Ctr+R), en ook het ‘venster eigenschappen’ (commando: Beeld, Venster Eigenschappen, of F4). Kies zo nodig in de projectverkenner de ‘module’ Voorbeelden. In de Visual Basic-editor kun je de programma’s of macro’s bekijken en wijzigen. Met Alt+F11 kun je ‘switchen’ tussen het document en de Visual Basic-editor. Om de programma’s te laten uitvoeren moet je altijd terug naar het document. Voorbeeld 1 Sub Voorbeeld01() ' een optelling ' declaratie van de variabelen (termen a en b, en som s): Dim a As Single, b As Single, s As Single ' de toewijzingsinstructies: a = 3.7: b = 4.4 s = a + b ' uitvoer: pvUitvoer a, " + ", b, " = ", s, ".": pvNR End Sub ' Voorbeeld01 Toelichting Sub ... End Sub Daartussen staan de instructies van de procedure met de naam Voorbeeld01. ' een optelling Alles na ' wordt genegeerd (‘commentaar’). Hier staat meestal nadere toelichting. Dim a As Single, b As Single, s As Single 2
Programmeren VBA 1
Hoofdstuk 1
De variabelendeclaratie: er worden drie variabelen gedeclareerd, a, b en s, alle van het type Single (een reëel getal). a = 3.7: b = 4.4 De variabele a krijgt de waarde 3,7 (dus met een punt, niet met een komma): een toewijzing. Net zo krijgt b de waarde 4,4. De instructies mogen op één regel staan als er een dubbele punt tussen staat. s = a + b De variabele s krijgt als waarde van de som van de waarden van a en b. pvUitvoer a, " + ", b, " = ", s, "." Er wordt tekst ingevoegd in het ‘actieve document’: de waarde van a, de tekst " + " (zonder de aanhalingstekens), de waarde van b, de tekst " = ", de waarde van s, en een punt aan het einde van de zin. pvNR Ga verder op een nieuwe regel (alinea). Het is natuurlijk ook van belang dat je de structuur van het geheel begrijpt. Daarom staat hieronder nogmaals het programma. Sub Voorbeeld01() ' een optelling ' declaratie van de variabelen (termen a en b, en som s): Dim a As Single, b As Single, s As Single ' de toewijzingsinstructies: a = 3.7: b = 4.4 s = a + b ' uitvoer: pvUitvoer a, " + ", b, " = ", s, ".": pvNR End Sub ' Voorbeeld01 Het geheel wordt ‘omhuld’ door de regels Sub Voorbeeld01() en End Sub; daarom springen de andere regels ook in. De eerste instructie is een declaratie, waarmee wordt aangekondigd welke variabelen worden gebruikt. Daarna zijn er de toewijzingsinstructies, waarmee echt iets berekend wordt. Tenslotte is er de uitvoerinstructie, waarmee het sommetje in het document wordt gezet. De juiste volgorde is natuurlijk van groot belang. De variabelen die je wilt gebruiken moeten eerst aangekondigd zijn, en er moet natuurlijk eerst gerekend worden en daarna pas geschreven, globaal dus als volgt. Sub Voorbeeld01() declaraties rekenen uitvoer End Sub ' Voorbeeld01 Het programma in voorbeeld 1 werkt alleen met de waarden a = 3,7 en b = 4,4. Wil je andere getallen laten optellen, dan moet je het programma veranderen. Het is handiger als je de waarden kunt opgeven terwijl het programma loopt.
3
Programmeren VBA 1
Hoofdstuk 1
Voorbeeld 2 Sub Voorbeeld02() ' invoer en uitvoer ' declaratie van de variabele: Dim a As Single ' invoer: pvInvoer "Geef een waarde:", a ' uitvoer: pvUitvoer "De waarde is ", a, ".": pvNR End Sub ' Voorbeeld02 Toelichting pvInvoer "Geef een waarde", a Hierdoor verschijnt een venstertje met de titel ‘Invoer’, en met als tekst ‘Geef een waarde’. Hierin vul je een waarde in, en je klikt op OK (of je drukt op Enter). Deze waarde wordt toegewezen aan de variabele a. Als je op Annuleren klikt, of iets invult dat geen getal voorstelt, dan krijgt a de waarde 0. Je gebruikt nu gewoon de komma (dus niet de punt).
Voorbeeld 3 Sub Voorbeeld03() ' invoer, berekening en uitvoer ' de termen en de som: Dim a As Single, b As Single, s As Single ' invoer: pvInvoer "Eerste getal:", a pvInvoer "Tweede getal:", b ' berekening: s = a + b ' uitvoer: pvUitvoer a, " + ", b, " = ", s, ".": pvNR End Sub ' Voorbeeld03 Hier heb je dus de volgende structuur. Sub Voorbeeld03() declaraties invoer rekenen uitvoer End Sub ' Voorbeeld03
4
Programmeren VBA 1
Hoofdstuk 1
Voorbeeld 4 Het onderstaande programma voor de stelling van Pythagoras heeft precies dezelfde structuur. Sub Voorbeeld04() ' de stelling van Pythagoras ' de drie zijden: Dim a As Single, b As Single, c As Single ' invoer van de rechthoekszijden: pvInvoer "De ene rechthoekszijde:", a pvInvoer "De andere rechthoekszijde:", b ' berekening van de schuine zijde: c = Sqr(a ^ 2 + b ^ 2) ' uitvoer: pvUitvoer "De ene rechthoekszijde is ", a, ".": pvNR pvUitvoer "De andere rechthoekszijde is ", b, ".": pvNR pvUitvoer "De schuine zijde is ", c, ".": pvNR End Sub ' Voorbeeld04 Toelichting c = Sqr(a ^ 2 + b ^ 2) Hierin stelt Sqr de wortelfunctie voor, dus Sqr(x) is x . Enkele andere functies: Sin(x) sin x Cos(x) cos x Tan(x) tan x Hierbij moet de hoek x in radialen worden opgegeven. Atn(x) arctan x Dit is de hoek α (in radialen) waarvan de tangens gelijk is aan x: α = arctan x ⇔ tan α = x. x Exp(x) e Log(x) ln x (dus met grondtal e, niet met grondtal 10) Abs(x) |x| a ^ 2 betekent a2. De tekens voor de bewerkingen of operatoren zijn: + optellen - aftrekken * vermenigvuldigen / delen ^ machtsverheffen Hierbij gelden de gewone ‘voorrangsregels’; in volgorde 1 haakjes 2 machtsverheffen 3 vermenigvuldigen, delen 4 optellen, aftrekken Denk erom dat je in VBA niet kunt zeggen c^2 = a ^ 2 + b ^ 2 Je moet dit altijd eerst expliciet maken: c2 = a2 + b2 ⇒ c = a2 + b2 .
5
Programmeren VBA 1
Hoofdstuk 1
In de Visual Basic-editor kun je uitleg krijgen over de gebruikte taalconstructies. Selecteer bijvoorbeeld het woord Sub en druk op F1. Je ziet dan de syntaxis van de constructie, dat wil zeggen de manier waarop de instructie in VBA moet worden gebruikt.
Sub (Instructie) Declareert de naam, argumenten en programmacode waaruit het hoofddeel van de procedure Sub is opgebouwd. Syntaxis: [Private | Public] [Static] Sub naam [(argumenten)] [instructies] [Exit Sub] [instructies] End Sub Elementen tussen vierkante haken [ en ] kunnen in de constructie voorkomen, maar het hoeft niet. Een verticale streep | (of) geeft een keuzemogelijkheid aan. Vetgedrukte woorden moeten letterlijk zo gebruikt worden. Cursief gedrukte woorden moeten op de juiste manier worden gebruikt. In voorbeeld 1: Sub Voorbeeld01() ... End Sub ' Voorbeeld01 Nu worden [Private | Public] [Static] dus niet gebruikt. Er staat letterlijk Sub, en daarna als naam Voorbeeld01. Een ‘naam’ moet beginnen met een letter, en moet verder bestaan uit letters en/of cijfers. Er wordt geen onderscheid gemaakt tussen hoofdletters en kleine letters. Dus Voorbeeld01, VOORBEELD01 en VOORbeeld01 stellen allemaal hetzelfde voor. Ook [(argumenten)] ontbreekt hier. De haakjes zet Visual Basic er zelf achter; er had dus eigenlijk ([argumenten]) moeten staan. Op de plaats van de puntjes staan nu wel de instructies. Ook [Exit Sub] en [instructies] daarna ontbreken hier. Het geheel wordt beëindigd met het verplichte End Sub. Voor de duidelijkheid staat daar als commentaar achter dat dit het einde is van Voorbeeld1. We gebruiken Sub voorlopig alleen zo: Sub naam () instructies End Sub
Je kunt op deze manier alleen toelichting krijgen op taalconstructies die tot de eigenlijke Visual Basic-taal behoren. Zou je hetzelfde proberen bij pvUitvoer of pvNR, dan krijg je de mededeling:
Sleutelwoord niet gevonden
6
Programmeren VBA 1
Hoofdstuk 1
Opgaven Bij de macro’s in het bestand Pv1-1o.doc staat van de eerste vier opgaven al het begin. Je kunt die macro’s verder bewerken. In de Visual Basic-Editor kun je, terwijl je bezig bent, op taalfouten laten controleren met het commando Foutopsporing … compileren. Sla de wijzigingen regelmatig op. Test ook elk geschreven programma op correcte berekening. Bij enkele opgaven zijn daarvoor suggesties gegeven.
Opgave 1 Als a, b en c de zijden van een driehoek zijn, dan wordt de oppervlakte van de driehoek gegeven door 1 O = s(s − a)(s − b)(s − c) met s = 2 (a + b + c). Schrijf een programma met als invoer de lengtes van de drie zijden, en als uitvoer de oppervlakte van de driehoek. Gebruik voorbeeld 4 als model, dus neem de volgende structuur. Sub Opgave01() declaraties invoer rekenen uitvoer End Sub ' Opgave01 Test het programma met de waarden a = 3, b = 4 en c = 5, waarbij een waarde van de oppervlakte O = 6 hoort. Test het programma ook met de waarden a = 5, b = 12 en c = 13. Opgave 2 Als ha de hoogtelijn op de zijde a is, hb de hoogtelijn op de zijde b, en hc de hoogtelijn op de zijde c, dan is 1 1 1 O = 2 ⋅ a ⋅ ha = 2 ⋅ b ⋅ hb = 2 ⋅ c ⋅ hc Gebruik dit, en de formule uit opdracht 1, om de hoogtelijnen van een driehoek te laten berekenen. Invoer: de lengtes van de drie zijden a, b en c. Uitvoer: de lengtes van de hoogtelijnen. Maak de formules eerst expliciet. Test het programma met dezelfde waarden als in opgave 1. Reken daarvoor eerst, met de hand, de lengtes van de hoogtelijnen uit. Opgave 3 De vervangingsweerstand R van twee parallel geschakelde weerstanden R1 en R2 wordt gegeven door R1 ⋅ R2 1 1 1 = + of R = R R1 R2 R1 + R2 ⋅ Schrijf een programma met als invoer de grootte van de weerstanden R1 en R2, en als uitvoer de vervangingsweerstand R. Bedenk zelf bruikbare testwaarden. Doe hetzelfde voor drie weerstanden: 1 1 1 1 = + + R R1 R2 R3 ⋅
7
Programmeren VBA 1
Hoofdstuk 1
Opgave 4 Als van een driehoek de zijden b en c en de ingesloten hoek α gegeven zijn, dan is de zijde a te berekenen uit a2 = b2 + c2 − 2bc cos α dus a = b2 + c2 − 2bc cos α . Schrijf een programma met als invoer de zijden b en c en de hoek α (in graden), en als uitvoer de zijde a. Zet de hoek eerst om in radialen (vermenigvuldig met π/180). Definieer π als: Const Pi As Single = 3.14159265358979 Test het programma met de waarden b = 3, c = 4, α = 90° en b = 1, c = 1, α = 60°. Opgave 5 Wat is de uitvoer van het volgende programma? Sub Opgave05() Dim a As Single, b As Single, c As Single Dim d As Single, e As Single, f As Single Dim p As Single, q As Single, r As Single a = 2: b = 3: c = 4 d = b * c e = a ^ b f = a + b * c p = b / a * c q = Sqr(c) r = Atn(a / 2) pvUitvoer "d = ", d: pvNR pvUitvoer "e = ", e: pvNR pvUitvoer "f = ", f: pvNR pvUitvoer "p = ", p: pvNR pvUitvoer "q = ", q: pvNR pvUitvoer "r = ", r: pvNR End Sub ' Opgave05
8
Programmeren VBA 1
Hoofdstuk 2
2. Typen variabelen en constanten, bewerkingen, functies In het vorige hoofdstuk zagen declaraties van variabelen er zo uit: Dim a As Single, b As Single, c As Single De syntaxis van zo’n declaratie is (voorlopig): Dim varnaam As type [, varnaam As type] . . . Daarin moet varnaam beginnen met een letter; verder mogen er letters en/of cijfers in voorkomen. Het mag niet een van de Visual Basic ‘sleutelwoorden’ zijn, zoals Sub of End. Verder moet blijkbaar van elke variabele apart een type genoemd zijn. Kijk voor meer informatie in de Visual Basic-editor: Help, Inhoudsopgave en Index, Index, Dim(instructie).
Je kunt ook constanten declareren: Const Pi As Single = 3.14159265358979 De syntaxis is: Const constnaam As type = expressie [, constnaam As type = expressie] . . . Ook constnaam beginnen met een letter; verder mogen er letters en/of cijfers in voorkomen. Kijk voor meer informatie in de Visual Basic-editor: Help ... Const(instructie).
We gebruiken (voorlopig) de volgende gegevenstypen. Voor gehele getallen: gehele getallen van 0 t/m 255 1 byte Byte gehele getallen van –32.768 t/m 32.767 2 bytes Integer gehele getallen van –2.147.483.648 t/m 2.147.483.647 4 bytes Long Daarin is een byte hetzelfde als 8 bits. Een gegevenstype van 1 byte kan 28 = 256 verschillende waarden hebben. Een gegevenstype van 2 bytes kan 216 = 65536 verschillende waarden hebben. Bij ‘Integer’ is dat dus (bijna) eerlijk verdeeld over positieve en negatieve getallen. Een gegevenstype van 4 bytes kan 232 = 4.294.967.296 verschillende waarden hebben. Ook bij ‘Long’ is dat dus verdeeld over de positieve en negatieve getallen. Met deze drie typen wordt binnen het bereik exact gerekend, dus zonder afrondingsfouten en dergelijke. Als uitkomsten van berekeningen buiten het bereik vallen, dan treedt er een fout op (‘overloop’ of ‘overflow’); zie voorbeeld 1. Kijk voor meer informatie in de Visual Basic-editor: Help ... Byte(gegevenstype), Integer(gegevenstype) en Long(gegevenstype).
Voor reële getallen: reële getallen tussen 1,401298×10–45 en 3,402823×1038 en tussen 4 bytes –3,402823×1038 en –1,401298×10–45 (en 0 natuurlijk) reële getallen tussen 4,94065645841247×10–324 en 8 bytes Double 1,79769313486232×10308 en tussen –1,79769313486232×10308 en –4,94065645841247×10–324 (en 0 natuurlijk) In plaats van 1,401298×10–45 gebruikt Visual Basic de notatie 1.401298E–45: de ‘drijvende komma notatie’ (‘floating point’). Als getallen te groot of te klein worden schakelt Visual Basic automatisch over op deze notatie. Met deze typen wordt, ook binnen het bereik, niet exact gerekend. Er zullen altijd afrondingsfouten optreden; zie voorbeeld 2. Single
Kijk voor meer informatie in de Visual Basic-editor: Help ... Single(gegevenstype), Double(gegevenstype) en Overzicht gegevenstypen.
9
Programmeren VBA 1
Hoofdstuk 2
Verder is er het gegevenstype Boolean; dit kan de ‘waarden’ True en False (waar en onwaar) hebben. Ook is er het gegevenstype String; in variabelen van dit type kan tekst (een ‘tekenreeks’) worden bewaard. Er zijn nog meer typen, maar hiermee redden we het voorlopig wel. Je kunt je natuurlijk afvragen waarvoor al die gegevenstypen nodig zijn. Dat er verschillende typen zijn voor tekst en voor getallen is wel logisch: met tekst doe je andere dingen dan met getallen. Maar vijf verschillende typen voor getallen lijkt een beetje veel van het goede. Je zou zelfs kunnen overwegen om gewoon elk getal als Double te declareren, want daar ‘past’ elk ander getaltype in. Het nadeel van die aanpak is dat er geen verschil meer is tussen exact rekenen (bij gehele getallen) en benaderend rekenen (bij reële getallen). Verder wordt met de ‘kleine’ typen sneller gerekend dan met de grote. Ook met gehele getallen wordt sneller gerekend dan met reële getallen. Tenslotte kun je met een ‘groot’ type makkelijker problemen krijgen als je veel variabelen gebruikt, en dat gaat een keer gebeuren. Je kunt je dus het beste vooraf realiseren of je een geheel of een reëel type nodig hebt, en hoe groot het moet zijn. Voorbeeld 1 Sub Voorbeeld01() ' rekenen met gehele getallen ' drie Byte-variabelen: Dim a1 As Byte, b1 As Byte, s1 As Byte ' drie Integer-variabelen: Dim a2 As Integer, b2 As Integer, s2 As Integer a1 = 100: b1 = 200 s1 = a1 + b1 a2 = 100: b2 = 200 s2 = a2 + b2 pvUitvoer a1, " + ", b1, " = ", s1, ".": pvNR pvUitvoer a2, " + ", b2, " = ", s2, ".": pvNR End Sub ' Voorbeeld01 Hier worden dus drie variabelen gedeclareerd van het type Byte, en drie van het type Integer. De eerste twee variabelen krijgen een waarde, en de derde krijgt als waarde de som van de eerste twee. Als je dit laat uitvoeren krijg je de mededeling: Fout 6 tijdens uitvoering: ‘Overloop’ De knop ‘Help’ geeft nadere toelichting (de waarde van a1 + b1 is hier te groot voor een Byte). Kies ‘Foutopsporing’. Je ziet dan in de programmatekst een gele pijl bij de regel s1 = a1 + b1 Als je daar een accent voor zet, dan wordt de regel verder genegeerd. Kies nu uit het menu Foutopsporing: Stap uit (Ctrl+Shift+F8). Je kunt nu het programma wel laten uitvoeren. Maar het resultaat van de eerste berekening klopt nu niet. Voorbeeld 2 Sub Voorbeeld02() ' rekenen met reële getallen ' drie Double-variabelen: Dim a As Double, b As Double, s As Double a = 1E+15: b = 0.01 s = a + b pvUitvoer a, " + ", b, " = ", s, ".": pvNR End Sub ' Voorbeeld02 10
Programmeren VBA 1
Hoofdstuk 2
Hier worden dus drie variabelen gedeclareerd van het type Double. De variabele a krijgt de waarde 1E+15 = 1.000.000.000.000.000, en b krijgt de waarde 0,01. De variabele s zou dus de waarde 1.000.000.000.000.000,01 moeten hebben, maar de uitvoer van het programma is: 1E+15 + 0,01 = 1E+15. De waarde 1.000.000.000.000.000,01 past dus niet helemaal binnen een variabele van het type Double; de waarde wordt afgerond. Voorbeeld 3 Sub Voorbeeld03() ' Boolean-variabelen ' een reële en een Boolean variabele Dim a As Single, b As Boolean pvInvoer "Geef een getal:", a pvUitvoer "a = ", a: pvNR b = (a < 10) pvUitvoer "a < 10 is ", b, ".": pvNR End Sub ' Voorbeeld03 Dit programma vraagt om een waarde voor de reële variabele a. Deze waarde wordt vergeleken met de waarde 10, en het resultaat (waar of niet-waar) wordt opgeslagen in de Boolean variabele b: b = (a < 10) Voorbeeld 4 Sub Voorbeeld04() ' String-variabelen ' drie String-variabelen: Dim a As String, b As String, c As String pvInvoer "Geef wat tekst:", a pvInvoer "Geef nog wat tekst:", b c = a & b pvUitvoer "Bij elkaar: ", c, ".": pvNR End Sub ' Voorbeeld04 In dit programma komen drie String-variabelen voor. Van de eerste twee wordt de ‘waarde’ gevraagd. Het effect van c = a & b is dat die twee ‘waarden’ aan elkaar geplakt (‘aaneengeschakeld’) worden. In het vorige hoofdstuk zijn al enkele bewerkingen en functies genoemd. De bewerkingen die we (voorlopig) tegenkomen zijn: ^ * / \ Mod + &
machtsverheffen vermenigvuldigen delen ‘gehele deling’ (zie voorbeeld 5) rest bij ‘gehele deling’ (zie voorbeeld 5) optellen aftrekken Strings aaneenschakelen (zie voorbeeld 4) 11
Programmeren VBA 1
Hoofdstuk 2
Samen met constanten, variabelen en functies zijn dit dus de bouwstenen van expressies ofwel (rekenkundige) uitdrukkingen. Kijk voor meer informatie in de Visual Basic-editor: Help ... operatoren, overzicht en operatorvolgorde
Voorbeeld 5 Sub Voorbeeld05() ' de bewerkingen \ en Mod ' vier Integer-variabelen: Dim a As Integer, b As Integer, c As Integer, d As Integer a = 23: b = 4 c = a \ b: d = a Mod b pvUitvoer "a = ", a, ", b = ", b, ".": pvNR pvUitvoer "a \ b = ", c, ", a Mod b = ", d, ".": pvNR a = -23: b = 4 c = a \ b: d = a Mod b pvUitvoer "a = ", a, ", b = ", b, ".": pvNR pvUitvoer "a \ b = ", c, ", a Mod b = ", d, ".": pvNR End Sub ' Voorbeeld05 Dus als a = 23 en b = 4, dan is a \ b = 5 en a Mod b = 3. Dit zijn de uitkomsten van de gehele deling met rest: 23 3 4 = 5 + 4 of 23 = 5 × 4 + 3. Bij positieve gehele getallen a en b zijn altijd gehele getallen q en r (quotiënt en rest) te vinden zodat a = q ⋅ b + r met 0 ≤ r < b (het delingsalgoritme). Bij negatieve a doet Visual Basic het blijkbaar iets anders: −23 = −5 × 4 − 3 dus niet: −23 = −6 × 4 + 1 (met 0 ≤ r < 4). De (wiskundige) functies die we regelmatig zullen gebruiken zijn: Abs(x) |x| Atn(x) arctan x (in radialen) Cos(x) cos x (x in radialen) x Exp(x) e Fix(x) het deel van x voor de komma (punt) Int(x) het gehele deel van x (zie voorbeeld 6) Log(x) ln x Rnd een willekeurig reëel getal tussen 0 en 1 (zie voorbeeld 6); Rnd is een afkorting van ‘Random’ (willekeurig). Sgn(x) het teken van x (–1 als x < 0, 1 als x > 0, 0 als x = 0) Sin(x) sin x (x in radialen) Sqr(x) x Tan(x) tan x (x in radialen) Kijk voor meer informatie in de Visual Basic-editor: Help ... wiskundige functies
12
Programmeren VBA 1
Hoofdstuk 2
Voorbeeld 6 Sub Voorbeeld06() ' de functies Rnd en Int ' twee Single-variabelen: Dim a As Single, b As Single ' twee Integer-variabelen: Dim c As Integer, d As Integer Randomize a = 10 * Rnd: b = -10 * Rnd c = Int(a): d = Int(b) pvUitvoer "a = ", a, ", b = ", b, ".": pvNR pvUitvoer "De ‘gehele delen’ zijn ", c, " en ", d, ".": pvNR End Sub ' Voorbeeld06 Door de instructie Randomize wordt de ‘voortbrenger van toevalsgetallen’ (‘random number generator’) op een beginwaarde gezet (anders krijg je steeds dezelfde serie ‘toevalsgetallen’, en dat is niet erg toevallig). Het is verstandig dit altijd te doen voordat je de functie Rnd gebruikt. Door de instructies a = 10 * Rnd: b = -10 * Rnd krijgt a een willekeurige waarde tussen 0 en 10, en b een waarde tussen −10 en 0. Door de instructie c = Int(a) krijgt c als waarde het gehele deel van a (het grootste gehele getal ≤ a), en door de instructie d = Int(b) krijgt d als waarde het gehele deel van b (het grootste gehele getal ≤ b). Dus als bijvoorbeeld a = 7,832 en b = −4,165. dan is c = 7 en d = −5. Als je dit programma nog een paar keer laat lopen, kun je zien dat de waarden van a en b, en dus ook van c en d, steeds anders zijn. Een nuttige toepassing van de functie Int staat in het volgende voorbeeld. Voorbeeld 7 Sub Voorbeeld07() ' afronden ' het getal a en het afgeronde getal b: Dim a As Double, b As Long pvInvoer "Geef een niet-geheel getal:", a b = Int(a + 0.5) pvUitvoer "a = ", a, ".": pvNR pvUitvoer "Afgerond: ", b, ".": pvNR End Sub ' Voorbeeld07 Bijvoorbeeld: a = 3,1 ⇒ a + 0,5 = 3,6 ⇒ b = 3; a = 3,7 ⇒ a + 0,5 = 4,2 ⇒ b = 4; a = 3,5 ⇒ a + 0,5 = 4,0 ⇒ b = 4; a = −3,1 ⇒ a + 0,5 = −2,6 ⇒ b = −3; a = −3,6 ⇒ a + 0,5 = −3,1 ⇒ b = −4. 13
Programmeren VBA 1
Hoofdstuk 2
Voorbeeld 8 Sub Voorbeeld08() ' stap voor stap Dim a As Integer, b As Integer, c As Integer, d As Integer a = 3 b = 4 c = a + b d = a ^ 2 * b + c a = d - c c = Sqr(a) b = c + d d = b - a a = Sqr(b) pvUitvoer a, " ", b, " ", c, " ", d: pvNR End Sub ' Voorbeeld08 Dit is op zich een onzinnig programma. Om te zien wat het doet kun je het eens met de hand narekenen. Je maakt dan een toestandstabel van het programma, dat wil zeggen een lijstje waarin je kunt zien wat op elk moment de toestand (de waarden van alle variabelen) van het programma is. Je krijgt dan: instructie a = 3 b = 4 c = a + b d = a ^ 2 * b + c a = d - c c = Sqr(a) b = c + d d = b - a a = Sqr(b)
a 3 3 3 3 36 36 36 36 7
b — 4 4 4 4 4 49 49 49
c — — 7 7 7 6 6 6 6
d — — — 43 43 43 43 13 13
Je kunt dit ook doen in de Visual Basic-editor. Geef (in Voorbeeld08) het commando Foutopsporing Stap (of druk op F8). Geef daarna steeds het commando Foutopsporing Stap over (Shift+F8). Het programma wordt dan regel voor regel uitgevoerd. Door de muisaanwijzer op de naam van een variabele te plaatsen zie je de waarde die de variabele op dat moment heeft. Je kunt ook een lijstje krijgen van alle variabelen en hun waarde, ongeveer zoals hierboven. Geef daarvoor het commando Beeld, Venster Locale variabelen, en loop het programma (opnieuw) stap voor stap door. Deze werkwijze kan handig zijn bij het zoeken naar fouten in programma’s. Als je het stap voor stap doorlopen van het programma wilt bekijken, en ook de uitvoer van het programma in het Worddocument, dan kun je die twee vensters onder elkaar zetten. Misschien moet je de scherminstellingen veranderen (hogere resolutie) om alles behoorlijk in beeld te krijgen.
14
Programmeren VBA 1
Hoofdstuk 2
Opgaven Opgave 1 Pas voorbeeld 5 zo aan dat je zelf waarden voor a en b kunt invoeren. Laat het programma enkele malen lopen, vul positieve waarden in voor a en b, en kijk of de resultaten overeenkomen met wat je verwacht. Probeer ook eens wat negatieve waarden, en kijk wat er gebeurt als b = 0. Opgave 2 Pas voorbeeld 7 zo aan dat het getallen afrondt op 2 cijfers achter de komma nauwkeurig. Probeer het ook zo aan te passen dat het getallen afrondt op n cijfers achter de komma (de waarde van n moet dan ook ingevoerd worden). Opgave 3 De oplossingen van de vergelijking ax2 + bx + c = 0 worden gegeven door −b + D −b − D x1 = en x2 = met D = b2 − 4ac. 2a 2a Schrijf een programma met als invoer de coëfficiënten a, b en c, en als uitvoer de oplossingen x1 en x2. Test het programma in ieder geval met de volgende waarden: a = 1, b = −3, c = 2 (D = 1, oplossingen x1 = 2 en x2 = 1); 5
1
a = 6, b = −7, c = −5 (D = 169, oplossingen x1 = 3 en x2 = − 2 ); a = 1, b = 4, c = 1 (D = 12, oplossingen x1 = −2 + 3 en x2 = −2 − 3); a = 1, b = 2, c = 1 (D = 0, oplossingen x1 = −1 en x2 = −1); a = 1, b = 6, c = 13 (D = −16, geen oplossingen). Kijk ook eens wat er gebeurt als a = 0. Opgave 4 Schrijf een programma met als invoer een reëel getal x tussen −1 en 1, en als uitvoer arcsin x. Gebruik daarbij: sin α x = sin α, cos α = 1 − sin2 α en tan α = ⋅ cos α Je kunt dan, als je tan α weet, de functie Atn gebruiken. Opgave 5 Schrijf een programma met als invoer een tijdstip in uren u0, minuten m0 en seconden s0, en een tijdsverschil in uren u1, minuten m1 en seconden s1. De uitvoer van het programma moet zijn de tijd in uren u2, minuten m2 en seconden s2 die je krijgt als je bij de ingevoerde eerste tijdstip het ingevoerde tijdsverschil optelt. Bijvoorbeeld: 12:43:34 + 2:51:43 = 14:94:77 = 14:95:17 = 15:35:17 Gebruik een geschikt gegevenstype, en de bewerkingen \ en Mod.
15
Programmeren VBA 1
Hoofdstuk 2
Opgave 6 Geef de toestandstabellen van de onderstaande stukjes programma. (1) a = 3 b = 4 a = b b = a (2) a = 3 b = 4 c = a a = b b = c (3) a = 3 b = 4 a = a + b b = a - b a = a - b Wat kun je concluderen?
16
Programmeren VBA 1
Hoofdstuk 3
3. Herhalingsstructuren Voorbeeld 1a Hieronder staat een programma – met voor de duidelijkheid wat extra lijntjes – om te berekenen wanneer een bedrag bij een bepaald rentepercentage minstens verdubbeld is. Sub Voorbeeld01a() ' renteberekening ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 ' beginbedrag en bedrag: Dim b0 As Double, b As Double ' rentepercentage en rentefactor: Dim p As Double, r As Double ' aantal jaren: Dim n As Long ' invoer: pvInvoer "Beginbedrag:", b0 pvInvoer "Rentepercentage (per jaar):", p ' berekening en uitvoer: r = 1 + p / 100 n = 0: b = b0 Do While b < 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop pvUitvoer "Aantal jaren: ", n: pvNR pvUitvoer "Eindbedrag: ": pvUitvoerR b, ac: pvNR End Sub ' Voorbeeld01a Toelichting pvUitvoer "Beginbedrag: ": pvUitvoerR b0, ac: pvNR Hierin wordt de instructie pvUitvoerR b0, ac gebruikt. Deze zorgt ervoor dat de waarde van het (reële) getal b0 met ac = 2 cijfers achter de komma wordt geschreven; dat staat wat mooier bij zulke getallen. r = 1 + p / 100 Dit is de vermenigvuldigingsfactor die hoort bij het percentage p. Dus bij een rente van (bijvoorbeeld) 5% hoort een factor 1,05. Het bedrag wordt dan elk jaar met 1,05 vermenigvuldigd. n = 0: b = b0 Dit is de initialisatie van de herhaling. De variabelen worden op de juiste beginwaarde gezet. Zolang er nog geen rente berekend is, is het bedrag gelijk aan het beginbedrag, en het aantal jaren gelijk aan 0. Do While b < 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop Dit is de eigenlijke herhaling (de lus of loop). De regels tussen Do While b < 2 * b0 en Loop worden uitgevoerd zolang (while) aan de voorwaarde b < 2 ⋅ b0 voldaan is. n = n + 1: b = b * r 17
Programmeren VBA 1
Hoofdstuk 3
Hier wordt het aantal jaren n met 1 opgehoogd, en er wordt over 1 jaar rente berekend. De toewijzing n = n + 1 lijkt op het eerste gezicht een beetje vreemd, maar er staat letterlijk dat n de waarde krijgt die n + 1 op dat moment heeft. Als dus op dat moment n = 0, dan krijgt n de waarde 1, enzovoort. Net zo betekent de toewijzing b = b * r dat b de waarde krijgt die b ⋅ r op dat moment heeft. Dus als op dat moment b = 1000 en r = 1,05 dan krijgt b de waarde 1050. pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Tijdens de herhaling wordt hier niet alleen gerekend maar ook geschreven, namelijk het aantal jaren n en het bedrag b op dat moment. vbTab is een constante in Visual Basic die een ‘Tab’ voorstelt; je krijgt zo min of meer een tabel. Voor meer informatie over vbTab en dergelijke constanten: selecteer (in de Visual Basic-editor) het woord vbTab, en druk op F1.
Je kunt je de structuur van dit programma als volgt voorstellen. Sub Voorbeeld01a() declaraties invoer berekening en uitvoer End Sub ' Voorbeeld01a Het gedeelte ‘berekening en uitvoer’ heeft daarin de volgende herhalingsstructuur.
berekening rentefactor, initialisatie Do While b < 2 * b0 verhoog aantal jaren en bedrag uitvoer lopend aantal jaren en bedrag Loop uitvoer aantal jaren en eindbedrag In de programmatekst, en in de beschrijving van de syntaxis, springt het ‘blok’ (de body van de herhaling) tussen Do en Loop daarom altijd een stukje in. Dit verhoogt de leesbaarheid van de programmatekst. Je ziet dan in één keer welke Do bij welke Loop hoort. Dat is natuurlijk vooral van belang als één van de instructies ertussen weer een herhaling is. De syntaxis van deze herhalingsstructuur, zoals wij die zullen gebruiken, is Do {While | Until} voorwaarde [instructies] Loop of Do [instructies] Loop {While | Until} voorwaarde De accolades { } hierin betekenen dat het deel daartussen verplicht is. Er moet daar dus òf het ‘sleutelwoord’ While òf Until staan. De voorwaarde is een ‘Boolean expressie’, dus een uitdrukking die de ‘waarde’ True (waar) of False (niet-waar) heeft. In voorbeeld 1 is deze expressie b < 2 ⋅ b0. De instructies tussen Do en Loop worden uitgevoerd zolang de voorwaarde waar is (bij While) of totdat de voorwaarde niet-waar is (bij Until).
18
Programmeren VBA 1
Hoofdstuk 3
Het verschil tussen de eerste versie (met While of Until achter Do) en de tweede (met While of Until achter Loop) is dat in de eerste versie al aan het begin gecontroleerd wordt of de voorwaarde waar is, en in de tweede versie pas aan het einde. Dat betekent dat in de eerste versie de instructies misschien wel helemaal niet worden uitgevoerd (bijvoorbeeld als de voorwaarde achter While meteen al False is). In de tweede versie worden de instructies altijd minstens één keer uitgevoerd. Voorbeeld 1b Hier wordt dezelfde berekening als in voorbeeld 1a uitgevoerd, maar nu met Until. Sub Voorbeeld01b() ' renteberekening ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 ' beginbedrag en bedrag: Dim b0 As Double, b As Double ' rentepercentage en rentefactor: Dim p As Double, r As Double ' aantal jaren: Dim n As Long ' invoer: pvInvoer "Beginbedrag:", b0 pvInvoer "Rentepercentage (per jaar):", p ' berekening en uitvoer: r = 1 + p / 100 n = 0: b = b0 Do Until b >= 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop pvUitvoer "Aantal jaren: ", n: pvNR pvUitvoer "Eindbedrag: ": pvUitvoerR b, ac: pvNR End Sub ' Voorbeeld01b Toelichting Do Until b >= 2 * b0 De herhaling wordt nu uitgevoerd totdat (until) aan de voorwaarde b ≥ 2 ⋅ b0 voldaan is (>= is de Basic-notatie voor ≥). Het effect is precies hetzelfde als eerst, want de voorwaarde b ≥ 2 ⋅ b0 in voorbeeld 1b is precies de (logische) ontkenning van de voorwaarde b < 2 ⋅ b0 in voorbeeld 1a. Dus als de voorwaarde geformuleerd is met While, dan wordt de herhaling uitgevoerd zolang de voorwaarde True is, dus totdat de voorwaarde False wordt. Als de voorwaarde geformuleerd is met Until, dan wordt de herhaling uitgevoerd zolang de voorwaarde False is, dus totdat de voorwaarde True wordt. Het bezwaar van de constructies in de voorbeelden 1a en 1b is dat het programma in een ‘eindeloze lus’ komt als het rentepercentage negatief of 0 is. Het programma blijft dan maar doorrekenen, want aan de voorwaarde b < 2 ⋅ b0 in voorbeeld 1a wordt altijd voldaan (aan de voorwaarde b ≥ 2 ⋅ b0 in voorbeeld 1b wordt nooit voldaan).
19
Programmeren VBA 1
Hoofdstuk 3
Je kunt in zo’n geval het programma afbreken met de toetsencombinatie Ctrl+Break. Je krijgt dan een venstertje te zien met de mededeling ‘De uitvoering van de programmacode is onderbroken’ en de keuzes ‘Doorgaan’, ‘Stop’, ‘Foutopsporing’ en ‘Help’. Meestal is ‘Stop’ de verstandigste keuze. Het is natuurlijk mooier als je van tevoren zeker weet dat dit soort onjuiste invoer niet voor kan komen. In voorbeeld 2 wordt, met behulp van een herhalingsstructuur, verkeerde invoer onmogelijk gemaakt. Voorbeeld 2 Sub Voorbeeld02() ' renteberekening, controle op invoer ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 ' beginbedrag en bedrag: Dim b0 As Double, b As Double ' rentepercentage en rentefactor: Dim p As Double, r As Double ' aantal jaren: Dim n As Long ' voor de functie MsgBox: Dim msg As Integer ' invoer en controle: pvInvoer "Beginbedrag:", b0 pvInvoer "Rentepercentage (per jaar):", p Do While p <= 0 msg = MsgBox("Het percentage moet positief zijn.", _ vbOKOnly + vbExclamation, "Fout") pvInvoer "Rentepercentage (per jaar):", p Loop ' berekening en uitvoer: r = 1 + p / 100 n = 0: b = b0 Do While b < 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop pvUitvoer "Aantal jaren: ", n: pvNR pvUitvoer "Eindbedrag: ": pvUitvoerR b, ac: pvNR End Sub ' Voorbeeld02 Toelichting Er is nu een extra variabele msg gedeclareerd die gebruikt wordt in de functie MsgBox: msg = MsgBox("Het percentage moet positief zijn.", _ vbOKOnly + vbExclamation, "Fout") Dit zou eigenlijk op één regel moeten staan, maar die wordt dan moeilijk leesbaar. Daarom wordt met het symbool _ aangegeven dat de instructie nog verder gaat. Na de (eerste) invoer van het rentepercentage p volgt een herhaling die doorgaat zolang p ≤ 0. Er verschijnt een foutmelding, en er wordt opnieuw om een percentage gevraagd. Om-
20
Programmeren VBA 1
Hoofdstuk 3
dat dit doorgaat zolang p ≤ 0 is het na afloop zeker dat p > 0, dus dat de verdere berekening niet in een eindeloze herhaling vervalt. De syntaxis van de ‘functie-aanroep’ MsgBox is varnaam = MsgBox (prompt [, buttons] [, title] [, helpfile, context]) We gebruiken hier varnaam = MsgBox (prompt, buttons, title) Daarin is het ‘argument’ prompt de tekst die in het venstertje verschijnt, dus hier “Het percentage moet positief zijn.”. Het argument buttons geeft aan wat voor knoppen en welk pictogram er in het venstertje verschijnen; hier dus alleen een OK-knop en een uitroepteken. Het argument title geeft de tekst in de titelbalk van het venstertje; hier dus “Fout”. De functiewaarde wordt toegewezen aan de variabele msg. We doen er hier niets mee, maar deze waarde geeft aan op welke knop is gedrukt. De ‘hoofdstructuur’ is hier hetzelfde als die in voorbeeld 1a. Het gedeelte ‘invoer’ heeft nu ook een herhalingsstructuur.
invoer percentage Do While p <= 0 foutmelding (nieuwe) invoer percentage Loop Voorbeeld 3 Dit programma is een voorbeeld van het gebruik van de functie MsgBox. Het zet een willekeurig getal van 1 tot en met 10 in het document, en vraagt of er nog een moet komen. Sub Voorbeeld03() ' MsgBox-functie gebruiken ' een getal: Dim i As Integer ' het resultaat van MsgBox: Dim antwoord As Integer Randomize pvUitvoer "Getallen van 1 t/m 10": pvNR Do i = Int(10 * Rnd) + 1 pvUitvoer vbTab, i ' Ja- en Nee-knop; met vraagteken: antwoord = MsgBox("Nog een?", vbYesNo + vbQuestion, _ "Getallen van 1 t/m 10") Loop Until antwoord = vbNo pvNR End Sub ' Voorbeeld03 Toelichting antwoord = MsgBox("Nog een?", vbYesNo + vbQuestion, _ "Getallen van 1 t/m 10") Er verschijnt een venstertje met een vraagteken als pictogram, een Ja-knop en een Nee-knop, en met de tekst ‘Nog een?’. Het resultaat van de functie MsgBox wordt opgeslagen in de variabele antwoord. 21
Programmeren VBA 1
Hoofdstuk 3
Als op de Ja-knop (of op Enter, of op J) is gedrukt, dan krijgt antwoord de waarde vbYes; als op de Nee-knop is gedrukt (of op N), dan krijgt antwoord de waarde vbNo. De herhaling wordt dus uitgevoerd totdat op de Nee-knop wordt gedrukt. Dit is een handige manier om te vragen of een herhaling nog een keer moet worden uitgevoerd. We gebruiken weer de syntaxis varnaam = MsgBox (prompt, buttons, title) Daarin is het ‘argument’ prompt weer de tekst die in het venstertje verschijnt, dus nu ‘Nog een?’. Het argument buttons geeft aan wat voor knoppen en welk pictogram er in het venstertje verschijnen; nu dus een Ja- en een Nee-knop en een vraagteken. Het argument title geeft de tekst in de titelbalk van het venstertje; nu dus “Getallen van 1 t/m 10”. De functiewaarde wordt toegewezen aan de variabele antwoord. Deze waarde geeft aan op welke knop is gedrukt. Voor meer informatie over MsgBox: selecteer (in de editor) het woord MsgBox, en druk op F1.
De syntaxis van Do ... Loop was: Do {While | Until} voorwaarde [instructies] Loop
of
Do [instructies] Loop {While | Until} voorwaarde
In de eerste versie wordt vooraf gecontroleerd hoe het met de voorwaarde zit, en in de tweede versie achteraf. Dat betekent dat in de eerste versie de herhaling nul of meer keer (dus misschien helemaal niet) wordt doorlopen, en in de tweede versie één of meer keer (dus minstens één keer). We bekijken daarvan nog een voorbeeld. Voorbeeld 4 Sub Voorbeeld04() ' controle aan het begin Dim i As Integer i = 10 Do While i < 10 i = i + 1: pvUitvoer Loop ' deze herhaling wordt pvNR i = 10 Do i = i + 1: pvUitvoer Loop While i < 10 ' deze herhaling wordt pvNR End Sub ' Voorbeeld04
of aan het einde van de herhaling
i 0 keer uitgevoerd
i 1 keer uitgevoerd
Toelichting De eerste herhaling wordt nu helemaal niet uitgevoerd, want i < 10 is meteen al False. De tweede herhaling wordt één keer uitgevoerd, want aan het einde is i < 10 False.
22
Programmeren VBA 1
Hoofdstuk 3
Een herhalingsstructuur wordt ook wel eens schematisch weergegeven. De constructie Do {While | Until} voorwaarde [instructies] Loop ziet er dan zo uit: Z O L A N G v o o rw a a r d e instructie instructie instructie
T O T D A T v o o rw a a rd e instructie instructie instructie
De constructie Do [instructies] Loop {While | Until} voorwaarde ziet er zo uit: instructie instructie instructie Z O L A N G v o o rw a a r d e
instructie instructie instructie T O T D A T v o o rw a a rd e
Een (globaal) schema van voorbeeld 2 is dan: Invoer b0, p Bereken r ZOLANG p ≤0 Foutmelding Invoer p Bereken r ZOLANG b < 2 * b0 Rekenen (b = b * r) Tussenresultaten Uitkomsten
In de beschrijving van de herhalingsstructuren is een aantal keren de voorwaarde genoemd als een ‘Boolean expressie’ of logische expressie, dus een uitdrukking waarvan de waarde True of False kan zijn. In voorbeeld 1a was dat b < 2 ⋅ b0. Hierin worden twee expressies met elkaar vergeleken met vergelijkingsoperatoren zoals ‘kleiner dan’ of ‘groter dan’ of ‘gelijk aan’ (zie het overzicht hieronder). Als je ingewikkelder voorwaarden wilt formuleren, zoals 0 ≤ x ≤ 1, dan moet je die eerst splitsen in eenvoudiger voorwaarden: 0 ≤ x ≤ 1 ⇔ x ≥ 0 ∧ x ≤ 1. De Basic-notatie daarvoor is dan: x >= 0 And x <= 1 Daarin is And de logische operator die overeenkomt met het symbool ∧ (zie verder het overzicht hieronder). Je hebt dus te maken met allerlei soorten bewerkingen: rekenkundige operatoren, vergelijkingsoperatoren en logische operatoren. In uitdrukkingen waarin die door elkaar voorkomen geldt de volgorde zoals in de onderstaande lijst: eerst rekenkundige operatoren, dan vergelijkingsoperatoren en tenslotte logische operatoren. Binnen één categorie is de volgorde van bo-
23
Programmeren VBA 1
Hoofdstuk 3
ven naar beneden (dus eerst machtsverheffen, dan tegengestelde, dan vermenigvuldigen of delen, enz.). rekenkundig machtsverheffen (^) tegengestelde (-) vermenigvuldigen en delen (*, /) gehele deling ( \) rest bij gehele deling (Mod) optellen en aftrekken (+, –) aaneenschakeling tekenreeksen (&)
vergelijking gelijk aan (=) niet gelijk aan(<>) kleiner dan (<) groter dan(>) kleiner of gelijk aan (<=) groter of gelijk aan (>=) Like, Is
logisch Not And Or Xor Eqv Imp
Kijk voor meer informatie in de Visual Basic-editor: Help ... Index, operatoren, Operatorvolgorde.
In de expressie x >= 0 And x <= 1 worden eerst de vergelijkingsoperatoren afgehandeld, dus er wordt eerst gekeken of x ≥ 0 en x ≤ 1 True dan wel False zijn. Daarna wordt op deze logische ‘waarden’ de bewerking And losgelaten. Het resultaat daarvan is: x≥0 x≤1 x≥0 ∧ x≤1 False False False False True False True False False True True True De betekenis van de logische operatoren is: p False False True True
q False True False True
Not p True True False False
p And q False False False True
p Or q False True True True
p Xor q False True True False
p Eqv q True False False True
p Imp q True True False True
Hierboven hebben we geschreven: x >= 0 And x <= 1 Als je zou schrijven 0 <= x <= 1 dan wordt dit van links naar rechts ‘berekend’. De uitkomst van 0 ≤ x is False of True, dus daarna zou berekend worden False ≤ 1 of True ≤ 1, wat natuurlijk onzinnig is. Kijk goed uit met de twee betekenissen van het symbool =. In een logische expressie betekent het ‘is gelijk aan’, maar in een toewijzingsinstructie betekent het ‘krijgt de waarde van’. Zo zou je, als b een Boolean-variabele is en i een Integer-variabele, mogen schrijven: b = i = 1 De eerste = is dan een toewijzing en de tweede een logische operator. Als i = 1 dan krijgt b de waarde True, en als i ≠ 1 dan krijgt b de waarde False. In zo’n geval is het verstandiger te schrijven: b = (i = 1)
24
Programmeren VBA 1
Hoofdstuk 3
Voorbeeld 5a Hieronder worden som en gemiddelde van een (in te voeren) aantal getallen berekend. De getallen zelf worden ‘geproduceerd’ met de functie Rnd, die willekeurige getallen oplevert. Zo hoef je niet (veel) getallen in te voeren om het programma in werking te zien. De variabelen hebben hier eens wat langere namen gekregen. Sub Voorbeeld05a() ' som en gemiddelde van getallen (Do ... Loop) ' tellertje, aantal getallen, som van de getallen: Dim teller As Long, aantal As Long, som As Long ' de getallen, het gemiddelde: Dim getal As Integer, gem As Double ' invoer: pvInvoer "Aantal getallen:", aantal ' initialisatie: Randomize som = 0 teller = 1 ' berekening som en uitvoer getallen: ' stel rechtertabstops in om de 1 cm, 16 keer: pvZetRTabs 1, 16 Do While teller <= aantal ' een geheel getal tussen 1 en 10: getal = Int(10 * Rnd) + 1 pvUitvoer vbTab, getal som = som + getal teller = teller + 1 Loop pvNR pvWisTabs ' berekening gemiddelde: gem = som / aantal ' uitvoer resultaten: pvUitvoer "Het aantal getallen is ", aantal, ".": pvNR pvUitvoer "De som van de getallen is ", som, ".": pvNR pvUitvoer "Het gemiddelde van de getallen is " pvUitvoerR gem, 3: pvUitvoer ".": pvNR End Sub ' Voorbeeld05a Toelichting pvZetRTabs 1, 16 Er worden nogal wat getallen geschreven, dus er worden wat (rechter) tabstops ingesteld; deze worden later weer ‘gewist’. som = 0 De initialisatie: in het begin zijn er nog geen getallen bekend. Er is ook nog niets opgeteld, dus de som heeft nog de waarde 0. teller = 1 Do While teller <= aantal ... teller = teller + 1
25
Programmeren VBA 1
Hoofdstuk 3
Loop Hierdoor loopt de teller op van 1 tot en met het aantal getallen. getal = Int(10 * Rnd) + 1 De functie Rnd geeft een willekeurige waarde op het interval [0, 1〉, dus 10 × Rnd geeft een waarde op het interval [0, 10〉. Dat betekent dat Int(10 × Rnd) een geheel getal is van 0 tot en met 9. Door daarbij 1 op te tellen krijg je een geheel getal van 1 tot en met 10. som = som + getal De (nieuwe) som wordt gelijk aan de (oude) som plus het gevonden getal. Dit is, in combinatie met de initialisatie som = 0, de gebruikelijke manier om getallen op te tellen. Aan het begin van de herhaling is de som dus 0, en aan het einde zijn alle gevonden getallen bij de som opgeteld. In voorbeeld 5a moesten we zelf wat administratie bijhouden over het aantal getallen. De herhaling was daar geformuleerd als: teller = 1 Do While teller <= aantal ... teller = teller + 1 Loop Voor dit soort situaties, waarin van tevoren bekend is hoe vaak de herhaling gaat plaatsvinden, kent Visual Basic een andere constructie. Voorbeeld 5b Sub Voorbeeld05b() ' som en gemiddelde van getallen (Do ... Loop) ' tellertje, aantal getallen, som van de getallen: Dim teller As Long, aantal As Long, som As Long ' de getallen, het gemiddelde: Dim getal As Integer, gem As Double ' invoer: pvInvoer "Aantal getallen:", aantal ' initialisatie: Randomize som = 0 ' berekening som en uitvoer getallen: ' stel rechtertabstops in om de 1 cm, 16 keer: pvZetRTabs 1, 16 For teller = 1 To aantal ' een geheel getal tussen 1 en 10: getal = Int(10 * Rnd) + 1 pvUitvoer vbTab, getal som = som + getal teller = teller + 1 Next teller pvNR pvWisTabs ' berekening gemiddelde: gem = som / aantal
26
Programmeren VBA 1
Hoofdstuk 3
' uitvoer resultaten: pvUitvoer "Het aantal getallen is ", aantal, ".": pvNR pvUitvoer "De som van de getallen is ", som, ".": pvNR pvUitvoer "Het gemiddelde van de getallen is " pvUitvoerR gem, 3: pvUitvoer ".": pvNR End Sub ' Voorbeeld05b Toelichting De twee constructies teller = 1 Do While teller <= aantal ... teller = teller + 1 Loop
For teller = 1 To aantal ... en Next teller
hebben precies hetzelfde effect: de teller loopt op van 1 tot en met het aantal getallen. In de tweede constructie gaan het initialiseren van de teller en het ophogen ‘vanzelf’. In een schema geven we deze constructie wel aan zoals hiernaast.
Invoer aantal som = 0 VOOR t = 1 TOT n Maak getal Uitvoer getal Tel op bij som Bereken gemiddelde Uitvoer som, gemiddelde
De syntaxis van de For ... Next-instructie luidt: For teller = beginwaarde To eindwaarde [Step stap] [instructies] Next teller Hier is de eenvoudigste vorm gebruikt: For teller = beginwaarde To eindwaarde [instructies] Next teller Daarbij wordt een stapgrootte van 1 gebruikt. In het algemeen kan de stapgrootte positief of negatief zijn. Bij een positieve stapgrootte wordt de herhaling uitgevoerd zolang de teller ≤ de eindwaarde is; bij een negatieve stapgrootte wordt de herhaling uitgevoerd zolang de teller ≥ de eindwaarde is. Het is niet verstandig om de waarde van de teller binnen de herhaling te wijzigen. Voorbeeld 6 Soms gebeurt er toch niet helemaal wat je bedoelt. Zoals gezegd wordt met reële variabelen (Single en Double) niet exact gerekend. Het kan dan gebeuren dat de herhaling eerder (of later) stopt dan je zou verwachten. Sub Voorbeeld06() ' For ... Next met gehele en niet-gehele variabele ' aantal cijfers achter de komma: Const ac As Integer = 15 ' getal, tellertje: Dim x As Double, i As Integer
27
Programmeren VBA 1
Hoofdstuk 3
' dit gaat t/m 3: pvUitvoer "Met een gehele variabele:": pvNR For i = 0 To 10 x = 2 + i / 10 pvUitvoerR x, ac: pvNR Next i pvNR ' dit gaat maar t/m 2,9: pvUitvoer "Met een reële variabele:": pvNR For x = 2 To 3 Step 0.1 pvUitvoerR x, ac: pvNR Next x End Sub ' Voorbeeld06 In de eerste herhaling loopt de gehele variabele i van 0 tot en met (precies) 10, waardoor x alle waarden van 2,0 tot en met 3,0 aanneemt. In de tweede herhaling loopt de reële variabele x in principe van 2,0 tot en met 3,0 met stappen van 0,1. In werkelijkheid is de stapgrootte net iets groter dan 0,1. De herhaling stopt nu al bij 2,9. Dit komt doordat het getal 0,1 in de computer niet exact kan worden voorgesteld. De interne voorstelling is namelijk binair (tweetallig), en in die voorstelling is 0,1 een repeterende breuk (0,0001100110011001100110011001100…) die alleen benaderd kan worden. Blijkbaar is de benadering iets te groot. Voorbeeld 7 In een herhaling zoals For teller = beginwaarde To eindwaarde [instructies] Next teller mag een instructie tussen For en Next natuurlijk weer een herhalingsstructuur zijn. In het volgende programma wordt dit gebruikt om een tabel met beschikbare tekens op te schrijven, ongeveer zoals met het programma ‘Speciale tekens’ (charmap.exe, een handig programma; ik heb het in de map ‘Opstarten’ staan zodat ik de tekens van het lettertype ‘Symbol’ snel kan opzoeken). ! " # $ %& ' ( ) * @A B C D E F G H I J ` a b c d e f g h i j ‚ ƒ „ …† ‡ ˆ ‰Š ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª À Á Â Ã Ä Å ÆÇ È É Ê à á â ã ä å æ ç è é ê Of in het lettertype ‘Symbol’: ! ∀ # ∃ %& ∋ ( ) ∗ ≅ Α Β Χ ∆ Ε Φ Γ Η Ι ϑ α β χ δ ε φ γ η ι ϕ
+ K k ‹ « Ë ë
, - . L MN l m n Œ ¬ - ® Ì Í Î ì í î
/ O o ¯ Ï ï
0 P p ° Ð ð
1 Q q ‘ ± Ñ ñ
2 R r ’ ² Ò ò
3 S s “ ³ Ó ó
4 T t ” ´ Ô ô
5 U u • µ Õ õ
6 V v – ¶ Ö ö
7 8 WX w x —˜ · ¸ × Ø ÷ ø
9 : Y Z y z ™š ¹ º Ù Ú ù ú
; [ { › » Û û
< \ | œ ¼ Ü ü
= ] } ½ Ý ý
> ^ ~ ¾ Þ þ
? _ • Ÿ ¿ ß ÿ
+ , − . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? Κ Λ ΜΝ Ο Π Θ Ρ Σ Τ Υ ς Ω Ξ Ψ Ζ [ ∴] ⊥ _ κ λ µ ν ο π θ ρ σ τ υ ϖ ω ξ ψ ζ { | } ∼
ϒ ′ ≤ ⁄ ∞ ƒ ♣ ♦ ♥ ♠ ↔← ↑ → ↓ ° ± ″ ≥ × ∝ ∂ • ÷ ≠ ≡ ≈ … ↵ ℵ ℑ ℜ ℘ ⊗ ⊕ ∅ ∩ ∪ ⊃ ⊇ ⊄ ⊂ ⊆ ∈ ∉ ∠ ∇ ∏ √ ⋅ ¬ ∧ ∨ ⇔⇐ ⇑ ⇒ ⇓ ◊ 〈 ∑ 〉 ∫ ⌠ ⌡ 28
Programmeren VBA 1
Hoofdstuk 3
Elk teken heeft daarin een code (de ANSI-code) van 0 tot en met 255. De leesbare tekens hebben een code van 32 tot en met 255. De codes 0 tot en met 31 zijn gereserveerd voor ‘onleesbare’ tekens. Bij
De tabel bestaat uit 7 rijen van elk 32 tekens. De eerste rij bevat de tekens met code 32 tot en met 63, enzovoort. Sub Voorbeeld07() ' ANSI-tabel ' nummer van rij en kolom, tellertje: Dim rij As Integer, kolom As Integer, i As Integer ' één letterteken: Dim c As String * 1 ' uitvoer tabel: pvZetLTabs 0.5, 32 For rij = 1 To 7 For kolom = 0 To 31 ' schrijf één rij i = 32 * rij + kolom ' nummer van het letterteken c = Chr(i) ' letterteken nr. i pvUitvoer c, vbTab Next kolom pvBS ' veeg de laatste tab uit pvNR ' nieuwe regel Next rij pvWisTabs End Sub ' Voorbeeld07 Toelichting Dim c As String * 1 De variabele c moet het teken met een gegeven ANSI-code bevatten. Dit is dus een String met een lengte van 1 (teken). In het algemeen is een String met (maximaal) n tekens, waarin n een positieve constante is, String * n. For rij = 1 To 7 ... Next rij De ‘buitenste’ herhaling; de variabele rij loopt van 1 tot en met 7. For kolom = 0 To 31 ... Next kolom De ‘binnenste’ herhaling; de variabele kolom loopt van 0 tot en met 31. i = 32 * rij + kolom Voor rij nummer 1 telt dit dus van 32 tot en met 63, voor rij nummer 2 van 64 tot en met 95, enzovoort. c = Chr(i) Deze functie geeft het teken c dat hoort bij de ANSI-code i. pvBS Back Space: de laatste (overbodige) tab op de regel wordt weer uitgeveegd.
29
Programmeren VBA 1
Hoofdstuk 3
In een schema geven we deze ‘dubbele herhaling’ zo aan: V O O R rij = 1 T O T 7 V O O R kol = 0 T O T 31 Bereken code Bereken letter Uitvoer letter Nieuwe regel
Voorbeeld 8 In voorbeeld 5b hebben we van een aantal getallen de som en het gemiddelde berekend. Hier berekenen we die ook, maar we berekenen bovendien hoeveel elk getal afwijkt van het gemiddelde. In voorbeeld 5b hadden we de gevonden getallen maar even nodig om ze op te tellen bij de som. Daarna konden we het getal verder ‘vergeten’. We hadden dus voldoende aan één enkele variabele getal, waarin steeds het nieuwe getal werd opgeslagen. Nu moeten we elk getal ook echt ‘onthouden’ om later het verschil met het gemiddelde, dat pas aan het einde bekend is, te kunnen berekenen. We zullen dus ‘veel’ variabelen moeten declareren, en we weten van tevoren nog niet eens hoeveel. We gebruiken daarvoor een rij (array) of matrix. Sub Voorbeeld08() ' afwijkingen van het gemiddelde ' met een 1-dimensionale matrix (array) ' aantal cijfers achter de komma: Const ac As Integer = 3 ' de matrix: ' (het is nu nog niet bekend hoe groot de matrix wordt) Dim a() As Integer ' tellertje, aantal, de getallen: Dim k As Integer, n As Integer, getal As Integer ' som, gemiddelde, verschil: Dim som As Long, gem As Double, verschil As Double ' invoer aantal getallen: pvInvoer "Aantal getallen:", n ' initialisatie: ' (het is nu wel bekend hoe groot de matrix wordt) ReDim a(1 To n) Randomize som = 0 ' berekening som en uitvoer getallen: pvZetRTabs 1.6, 10 For k = 1 To n ' een geheel getal tussen 1 en 10: getal = Int(10 * Rnd) + 1 pvUitvoer vbTab, getal a(k) = getal som = som + getal Next k pvNR pvWisTabs
30
Programmeren VBA 1
Hoofdstuk 3
' berekening gemiddelde: gem = som / n ' berekening verschillen; uitvoer resultaten: pvUitvoer "Het aantal getallen is ", n, ".": pvNR pvUitvoer "De som van de getallen is ", som, ".": pvNR pvUitvoer "Het gemiddelde van de getallen is " pvUitvoerR gem, ac: pvUitvoer ".": pvNR pvUitvoer "De afwijkingen van het gemiddelde zijn:": pvNR pvZetRTabs 1.6, 10 For k = 1 To n verschil = a(k) - gem pvUitvoer vbTab: pvUitvoerR verschil, ac Next k pvNR pvWisTabs End Sub ' Voorbeeld08 Toelichting Dim a() As Integer Hier wordt de rij (matrix) a gedeclareerd. Het aantal getallen is nu nog niet bekend. ReDim a(1 To n) Nu is het aantal getallen n wel bekend, dus we kunnen aangeven hoeveel variabelen we willen declareren. We hebben nu de beschikking over de variabelen a(1), a(2), … , a(n). In wiskundige notatie zou je schrijven: a1, a2, … , an. For k = 1 To n getal = Int(10 * Rnd) + 1 ... a(k) = getal ... Next k Door de instructie a(k) = getal wordt het k-de getal bewaard in de variabele a(k). For k = 1 To n verschil = a(k) - gem ... Next k De oorspronkelijke getallen zijn nog bekend, en het gemiddelde nu ook, dus we kunnen het verschil berekenen. Dit verschil hoeft alleen geschreven te worden, en het wordt dus verder niet onthouden.
31
Programmeren VBA 1
Hoofdstuk 3
Een schema van dit programma: Initialisatie VOOR k = 1 TOT n Maak getal Uitvoer getal Onthoud getal Tel op bij som Bereken gemiddelde Uitvoer som, gemiddelde VOOR k = 1 TOT n Bereken verschil Uitvoer verschil
Voorbeeld 9 Je kunt ook een tweedimensionale matrix declareren. Hieronder is dat gedaan om bij te houden hoe vaak een bepaalde combinatie van de aantallen ogen voorkomt bij het gooien met twee dobbelstenen. De matrix a bestaat nu uit 6 rijen en 6 kolommen. Je kunt je hierbij een rechthoek voorstellen: a(1, 1) a(1, 2) a(1, 3) a(1, 4) a(1, 5) a(1, 6) a(2, 1) a(2, 2) a(2, 3) a(2, 4) a(2, 5) a(2, 6) a(3, 1) a(3, 2) a(3, 3) a(3, 4) a(3, 5) a(3, 6) a(4, 1) a(4, 2) a(4, 3) a(4, 4) a(4, 5) a(4, 6) a(5, 1) a(5, 2) a(5, 3) a(5, 4) a(5, 5) a(5, 6) a(6, 1) a(6, 2) a(6, 3) a(6, 4) a(6, 5) a(6, 6) In a(4, 2) houden we dan bij hoe vaak de combinatie (4, 2) is voorgekomen. Sub Voorbeeld09() ' een aantal keren gooien met 2 dobbelstenen; bereken hoe vaak ' elke combinatie voorkomt ' kleinste een grootste aantal ogen op een dobbelsteen: Const laag As Integer = 1, hoog As Integer = 6 ' tellen hoe vaak elke combinatie voorlomt: Dim a(laag To hoog, laag To hoog) As Long ' eerste dobbelsteen m, tweede dobbelsteen n: Dim m As Integer, n As Integer ' aantal keren gooien, tellertje: Dim aantal As Long, i As Long ' initialisatie: Randomize For m = laag To hoog For n = laag To hoog a(m, n) = 0 Next n Next m ' invoer aantal keren: pvInvoer "Aantal keren gooien met twee dobbelstenen:", _ aantal
32
Programmeren VBA 1
Hoofdstuk 3
' gooien en tellen: For i = 1 To aantal m = Int(hoog * Rnd) + 1: n = Int(hoog * Rnd) + 1 a(m, n) = a(m, n) + 1 Next i ' uitvoer tekst: pvUitvoer aantal, " keer gooien met twee dobbelstenen. " pvUitvoer "Geteld is hoe vaak elke combinatie voorkomt." pvNR ' uitvoer tabel met resultaten; ' de eerste rij van de tabel: pvZetRTabs 1.5, hoog + 1 pvUitvoer vbTab For n = laag To hoog pvUitvoer vbTab, n Next n pvNR ' de eigenlijke tabel: For m = laag To hoog pvUitvoer vbTab, m For n = laag To hoog pvUitvoer vbTab, a(m, n) Next n pvNR Next m End Sub ' Voorbeeld09 Toelichting Const laag As Integer = 1, hoog As Integer = 6 Dim a(laag To hoog, laag To hoog) As Long Hier wordt de tweedimensionale matrix a gedeclareerd. We weten nu wel van tevoren hoeveel elementen er nodig zijn. Als de dobbelstenen toevallig eens 10 zijkanten hebben, dan kun je dat simpel veranderen door de waarde van de constante hoog te wijzigen. For m = laag To hoog For n = laag To hoog a(m, n) = 0 Next n Next m De waarden van de elementen van a worden (met een dubbele herhaling) op 0 geïnitialiseerd. For i = 1 To aantal m = Int(hoog * Rnd) + 1: n = Int(hoog * Rnd) + 1 a(m, n) = a(m, n) + 1 Next i Er wordt gegooid met de beide dobbelstenen. Dat wil hier zeggen dat m en n een gehele waarde van 1 tot en met 6 krijgen. De waarde van het bijbehorende element van de matrix a wordt met 1 opgehoogd. pvUitvoer vbTab For n = laag To hoog pvUitvoer vbTab, n Next n
33
Programmeren VBA 1
Hoofdstuk 3
pvNR Dit geeft de ‘koprij’ van de tabel: de waarden 1 tot en met 6 van de tweede steen. For m = laag To hoog pvUitvoer vbTab, m For n = laag To hoog pvUitvoer vbTab, a(m, n) Next n pvNR Next m Dit geeft de eigenlijke tabel. Het eerste getal in elke rij (regel) is het aantal ogen (1 t/m 6) van de eerste steen. Je krijgt dus een ‘tabel’ van de vorm 1 2 3 4 5 6 1 2 3 4 5 6 (zonder lijntjes) waarbij in de ‘voorkolom’ het aantal ogen van de eerste steen staat, en in de ‘koprij’ het aantal ogen van de tweede steen. Omdat (in theorie) elke uitkomst even waarschijnlijk is, zou elk van de 36 mogelijke uitkomsten ongeveer even vaak moeten voorkomen. Door bijvoorbeeld een aantal keren gooien van 360 (of 3600, of 36000) op te geven kun je dat redelijk controleren. Kijk voor meer informatie in de Visual Basic-editor: Help, Inhoudsopgave en Index, Index, Matrices.
(matrices is het meervoud van matrix) Kijk eventueel ook bij Dim en ReDim.
Kijk uit met het declareren van grote meerdimensionale matrices. Als je bijvoorbeeld zou declareren Dim a(1 To 1000, 1 To 1000) As Long dan vraag je ruimte voor 1.000.000 variabelen van het gegevenstype Long van 4 bytes, dus 4 megabyte geheugen. Bovendien vraagt het bewerken van een dergelijke grote hoeveelheid gegevens veel rekenwerk. Zoiets loopt nog sneller uit de hand als je meerdimensionale matrices gebruikt. Bij de declaratie Dim a(1 To 50, 1 To 50, 1 To 50) As Double vraag je ruimte voor 50 × 50 × 50 = 125.000 Double-variabelen van 8 bytes, dus ook alweer 1 megabyte.
34
Programmeren VBA 1
Hoofdstuk 3
Opgaven Een paar aanwijzingen bij deze opgaven: • Ga niet meteen coderen (programmatekst schrijven). Probeer eerst het probleem te begrijpen. Maak zo nodig wat kladjes om het probleem met de hand op te lossen. • Maak een bewuste keuze voor de herhalingsstructuur. Opgave 1 Van een schuld los je per jaar een bepaald percentage af. Schrijf een programma met als invoer de schuld en het percentage, en als uitvoer het aantal jaren dat nodig is om de schuld (minstens) te halveren, en de resterende schuld. Gebruik voorbeeld 1a of 2 als model. Gebruik de volgende variabelen: s0 de oorspronkelijke schuld Double de (steeds afnemende) schuld Double s het aflossingspercentage Double p de factor waarmee de schuld afneemt Double f het aantal jaren Long n Opgave 2 a. Schrijf een programma om de tafel van vermenigvuldiging van een getal te laten schrijven, zoals hiernaast (met ook de tussenliggende regels). Invoer: een geheel getal; uitvoer: de tafel van dat getal. b. Schrijf een programma om de tafels van vermenigvuldiging van 1 tot en met 20 te laten schrijven. Laat tussen twee tafels een regel leeg. Gebruik de volgende variabelen: het getal waarvan de tafel verschijnt Long n een tellertje (1 t/m 10) Integer i
1×7=7 2 × 7 = 14 3 × 7 = 21 …
10 × 7 = 70
Opgave 3 Schrijf een programma om het produkt en het meetkundig gemiddelde van getallen te laten berekenen. Laat de getallen maken met de functie Rnd (gebruik voorbeeld 5b als model). Kies geschikte gegevenstypen voor de variabelen. Invoer: het aantal getallen; uitvoer: het produkt en het meetkundig gemiddelde van de getallen (en eventueel de getallen zelf). Denk om de juiste initialisatie(s). N.B. het meetkundig gemiddelde van de getallen a1, a2, … , an is het getal n
1 )n
a1 ⋅ a2 ⋅ … ⋅ an = (a1 ⋅ a2 ⋅ … ⋅ an Gebruik de volgende variabelen: het aantal getallen n een tellertje (1 t/m n) i het getal a het produkt p mg het meetkundig gemiddelde
Long Long Integer Double Double
35
Programmeren VBA 1
Hoofdstuk 3
Opgave 4 Wat is de uitvoer van het onderstaande programma (maak een toestandstabel)? Sub Opgave04() Dim i As Integer, j As Integer, k As Integer j = 1: k = 0 For i = 1 To 5 k = k + j j = j + 2 pvUitvoer k: pvNR Next i End Sub ' Opgave04 Kun je bewijzen dat dit ook zo is als i loopt van 1 tot en met 100? Opgave 5 a. Schrijf het onderstaande stukje programma in de vorm van een Do ... Loop-instructie, met While en met Until. Dim x As Double For x = 4 To 1 Step -0.25 pvUitvoer x, vbTab, x^2: pvNR Next x b. Schrijf het onderstaande stukje programma in de vorm van een For ... Next-instructie. Gebruik voor de herhaling een Integer-variabele i met een stapgrootte 1 (bijvoorbeeld: x = 10 ⇔ i = 0, x = 9,5 ⇔ i = 1 enz.). Dim x As Double x = 10 Do While x >= 3 pvUitvoer x, vbTab, x ^ 3: pvNR x = x - 0.5 Loop c. Schrijf het onderstaande stukje programma (voorbeeld 1) in de vorm van een For ... Nextinstructie. Bereken (met logaritmen en met de functie Int) het aantal keren dat de herhaling moet worden doorlopen. ac = 2: n = 0: b = b0 Do While b < 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop
36
Programmeren VBA 1
Hoofdstuk 3
Opgave 6 Het inprodukt van twee 2-dimensionale vectoren a1 b1 a = a en b = b 2 2 is het getal a1b1 + a2b2. Net zo is het inprodukt van twee 3-dimensionale vectoren aa1 bb1 a = 2 en b = 2 a3 b3 het getal a1b1 + a2b2 + a3b3. Schrijf een programma met als invoer de twee vectoren en als uitvoer het inprodukt. Declareer de dimensie n van de vectoren (hier dus n = 2 resp. n = 3) als een constante. Variabelen: a() de eerste vector (matrix) Double b() de tweede vector (matrix) Double het inprodukt Double inprod een tellertje (1 t/m n) Integer i Opgave 7 Schrijf een programma dat een aantal keren gooit met twee dobbelstenen en de som van de aantallen ogen berekent (dus minimaal 2 en maximaal 12). Het programma moet bijhouden hoe vaak elke som is voorgekomen. Declareer daarvoor een ééndimensionale matrix. Zie ook voorbeeld 9. Invoer: het aantal keren gooien met de twee dobbelstenen; uitvoer: een lijstje met de mogelijke sommen (2 tot en met 12) en het aantal keren dat elke som is voorgekomen. N.B. de uitkomsten zijn nu niet allemaal even waarschijnlijk. Gebruik de volgende variabelen: a() de sommen tellen (matrix: 2-12) Long aantal ogen eerste steen Integer m aantal ogen tweede steen Integer n som van de aantallen ogen Integer som aantal keren gooien Long aantal tellertje type i Opgave 8* De standaarddeviatie σn van n getallen is als volgt te berekenen: • bereken het gemiddelde van de getallen: µ =
∑x
n • bereken van elk van de getallen x de afwijking van het gemiddelde: x − µ • bereken het kwadraat van die afwijkingen: (x − µ)2 ∑ (x − µ)2 • tel de kwadraten op en deel door het aantal: n • σn is de wortel daaruit:
Dus σn2 =
∑ (x −
∑ (x − µ)2 n
µ)2
n
37
Programmeren VBA 1
Hoofdstuk 3
a. Schrijf een programma om de standaarddeviatie van n getallen te berekenen. Invoer: het aantal getallen (minstens 2), en eventueel de getallen zelf (of gebruik Rnd); uitvoer: de standaarddeviatie van de getallen. Gebruik voorbeeld 8 als model; declareer een matrix om de getallen te ‘onthouden’. b. Er bestaat een iets andere formule voor de standaarddeviatie, namelijk:
∑ x2 ∑ x2 2 σn2 = n − µ2, dus σn = n −µ Daarbij heb je dus niet de afzonderlijke afwijkingen x − µ van het gemiddelde nodig, maar alleen de som van de kwadraten van de getallen ∑ x2 n en het gemiddelde µ zelf. Schrijf een programma om op deze manier de standaarddeviatie te laten berekenen. Gebruik géén matrix. Opgave 9* De driehoek van Pascal is het volgende schema getallen: 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1 1 8 28 56 70 56 28 8 1 Aan de linker- en rechterrand staan enen. Verder ontstaat elk getal door de twee getallen erboven bij elkaar op te tellen. De bovenste ‘rij’ heeft nummer 0, de rij daaronder nummer 1 enzovoort. De derde rij is ook als volgt te vinden: 3
2
1
4
3
2
n
n–1
×1 ×2 ×3 1 → 3 → 3 → 1 De vierde rij ontstaat net zo: 1
×1 ×2 ×3 ×4 1 → 4 → 6 → 4 → 1 en de n-de rij: n–2
n–3
1
×1 × 2 × 3 × 4 ×n 1 → x → x → x → x → … → 1 Schrijf een programma om de driehoek van Pascal te laten schrijven. Invoer: het aantal rijen n (maximaal 29 als de getallen Double zijn); uitvoer: de rijen 0 tot en met n van de driehoek (elk op een aparte regel).
38
Programmeren VBA 1
Hoofdstuk 3
Opgave 10* Als je twee positieve gehele getallen deelt, dan krijg je in principe een repeterende breuk. Zo 153 is bijvoorbeeld 22 = 6,954545454545 … Je vindt dat met een ‘staartdeling’: 22 / 1 5 3 \ 6,95454 1 3 2 2 1 0 1 9 8 1 2 0 1 1 0 1 0 0 8 8 1 2 0 1 1 0 1 0 0 8 8 1 2 Als m de teller van de breuk is, en n de noemer, dan is het deel voor de komma gelijk aan m \ n In het voorbeeld: 153 \ 22 = 6 Je houdt dan nog een rest over, namelijk m Mod n In het voorbeeld: 153 Mod 22 = 21 Met die rest herhaal je steeds opnieuw: • • • • • • • • • • • • • • •
vermenigvuldig de rest met 10: bereken het quotiënt: bereken de rest: vermenigvuldig de rest met 10: bereken het quotiënt: bereken de rest: vermenigvuldig de rest met 10: bereken het quotiënt: bereken de rest: vermenigvuldig de rest met 10: bereken het quotiënt: bereken de rest: vermenigvuldig de rest met 10: bereken het quotiënt: bereken de rest:
210 210 \ 22 = 9 210 Mod 22 = 12 120 120 \ 22 = 5 120 Mod 22 = 10 100 100 \ 22 = 4 100 Mod 22 = 12 120 120 \ 22 = 5 120 Mod 22 = 10 100 100 \ 22 = 4 100 Mod 22 = 12
noteren: 9
noteren: 5
noteren: 4
noteren: 5
noteren: 4
Schrijf een programma met als invoer twee positieve gehele getallen m en n (Integer), en als m uitvoer de repeterende breuk voor n ⋅ Je mag ervan uitgaan dat de lengte van het repeterende stuk niet langer is dan n, en de lengte van het niet-repeterende stuk hoogstens 15.
39
Programmeren VBA 1
Hoofdstuk 3
Opgave 11* Een (wiskundige) matrix is een rechthoekig schema van getallen, zoals 1 –2 1 3 2 3 1 –1 2 1 of 1 2 De eerste matrix is een ‘2×3-matrix’ (2 rijen, 3 kolommen) en de tweede matrix is een ‘3×2matrix’ (3 rijen, 2 kolommen). Je kunt deze matrices vermenigvuldigen door inprodukten (zie opgave 6) te berekenen van rijen uit de eerste matrix met kolommen uit de tweede matrix: 1 –2 1 3 2 3 1 1 ⋅ 1 + 3 ⋅ 3 + 2 ⋅ 1 1 ⋅ (–2) + 3 ⋅ 1 + 2 ⋅ 2 12 5 = –1 ⋅ 1 + 2 ⋅ 3 + 1 ⋅ 1 –1 ⋅ (–2) + 2 ⋅ 1 + 1 ⋅ 2 = 6 6 . –1 2 1 ⋅ 1 2 In het algemeen is het produkt van een k×m-matrix met een m×n-matrix een k×n-matrix. Het aantal kolommen m van de eerste matrix moet gelijk zijn aan het aantal rijen m van de tweede; anders is de matrixvermenigvuldiging niet gedefinieerd. In het algemeen verloopt de vermenigvuldiging als volgt: a11 a12 … b11 b12 … a11b11 + a12b21 + … a11b12 + a12b22 + … … a a … ⋅ b b … = a b + a b + … a b + a b + … … 22 21 21 12 22 22 21 22 21 22 21 11 … … … … … … … … … Dus het getal in de i-de rij en j-de kolom is ai 1b1 j + ai 2b2 j + … + ai mbm j Schrijf een programma met als invoer de waarden van k, m en n. Het is misschien het eenvoudigst om de matrices te laten ‘vullen’ met willekeurige (niet te grote) gehele getallen (bijvoorbeeld 5 * Rnd - 2 om gehele getallen van –2 tot en met 2 te krijgen). De uitvoer van het programma moet zijn de ‘gevulde’ matrices, en de uitkomst van de matrixvermenigvuldiging. Opgave 12* Een kettingbreuk is een breuk van de vorm 1 a+ 1 b+ 1 c+ 1 d+ … Vaak wordt zo’n breuk geschreven als 1 1 1 1 a+b+ c+ d+ … of [a; b, c, d, …] Zo is bijvoorbeeld: 3 = 1,732050807569 = 1 + 0,732050807569 1 1 = 1 + 1,366025403784 = 1 + 1 + 0,366025403784 1 1 =1+ =1+ 1 1 1 + 2,732050807569 1 + 2 + 0,732050807569
40
Programmeren VBA 1
Hoofdstuk 3
1
=1+ 1+
1 2 + 1,366025403784 1
=1+ 2+
1+
1 1 2 + 1 + 0,366025403784 1
=1+
1
1+
1
=1+
1
1 1 1 + 2,732050807569
1
1+ 2+
1 1 1 + 2 + 0,732050807569
Dus 1 1 1 1 3=1+1+ 2+ 1+ = [1; 1, 2, 1, 2, …] … Je kunt die getallen vinden met een rekenmachine: 3 ≈ 1,732050807569 – 1 = 0,732050807569 1/x 1,366025403784 – 1 = 0,366025403784 1/x 2,732050807569 – 2 = 0,732050807569 1/x 1,366025403784 – 1 = 0,366025403784 1/x 2,732050807569 – 2 = 0,732050807569 1/x … Je herhaalt dus steeds: • noteer het ‘gehele deel’ (het deel voor de komma, functie Int) • haal het ‘gehele deel’ eraf • bereken het omgekeerde Kettingbreuken van (niet-rationale) getallen n waarin n een positief geheel getal is, gaan altijd repeteren, zoals je hier kunt zien bij 3 . Schrijf een programma voor het berekenen van de kettingbreuk van n . Invoer is het (gehele) getal n, en het aantal cijfers in de kettingbreuk. Uitvoer is de kettingbreuk in de vorm [a; b, c, d, …] Gebruik zo nauwkeurig mogelijke variabelen. Bedenk wel dat de resultaten na veel herhalingen onnauwkeurig worden.
41
Programmeren VBA 1
42
Programmeren VBA 1
Hoofdstuk 4
4. Keuzestructuren Voorbeeld 1 Hieronder staat een programma om te beoordelen of een cijfer onvoldoende of voldoende is. Sub Voorbeeld01() ' voldoende/onvoldoende (If ... Then ... Else) ' het cijfer: Dim cijfer As Single ' invoer: pvInvoer "Geef een cijfer tussen 1 en 10:", cijfer ' uitvoer: pvUitvoer cijfer If cijfer < 5.5 Then pvUitvoer " is onvoldoende." Else pvUitvoer " is voldoende." End If pvNR End Sub ' Voorbeeld01 Toelichting If getal < 5.5 Then pvUitvoer " is onvoldoende." Else pvUitvoer " is voldoende." End If Dit is de keuzestructuur in het programma. Als (If) de waarde van de variabele cijfer kleiner is dan 5,5 dan (Then) verschijnt de tekst ‘is onvoldoende’, en anders (Else) de tekst ‘is voldoende’. De constructie wordt beëindigd met End If. Het is verstandig de regel(s) tussen If en Else, en die tussen Else en End If, te laten inspringen, om de structuur beter leesbaar te maken. In een schema wordt deze keuzestructuur zo weergegeven:
De syntaxis van de ‘If...Then...Else-Instructie’ is hier: If voorwaarde Then [instructies] Else [andersinstructies] End If Daarin is voorwaarde weer een ‘Boolean expressie’ of logische expressie, dus een uitdrukking die de waarde True of False kan hebben.
43
Programmeren VBA 1
Hoofdstuk 4
Voorbeeld 2 In het onderstaande programma wordt het maximum van een stel (willekeurige) getallen berekend. Dit lijkt een beetje op het berekenen van de som van getallen (hoofdstuk 3, voorbeeld 5b). Sub Voorbeeld02() ' het grootste getal (If ... Then) ' de kleinste Integer: Const MinInt As Integer = -32768 ' de getallen, het aantal: Dim getal As Integer, aantal As Integer ' tellertje: Dim i As Integer ' het grootste getal: Dim max As Integer ' initialisatie: max = MinInt ' voorlopig de grootste ' invoer aantal getallen: pvInvoer "Aantal getallen:", aantal ' maak (en schrijf) getallen: pvZetRTabs 1.6, 10 For i = 1 To aantal getal = Int(1000 * Rnd) - 500 pvUitvoer vbTab, getal If getal > max Then max = getal End If ' of op één regel: ' If getal > max Then max = getal ' dan hoeft er geen End If bij Next i pvNR ' uitvoer resultaat: pvWisTabs pvUitvoer "Het grootste getal is ", max, ".": pvNR End Sub ' Voorbeeld02 Toelichting Const MinInt As Integer = -32768 ' de kleinste Integer De kleinst mogelijke Integer-waarde. Zolang er nog geen getallen bekend zijn is dit het grootste getal. Je kunt dit vergelijken met de berekening van de som van getallen: zolang er geen getallen bekend zijn is de som gelijk aan 0 (hoofdstuk 3, voorbeeld 5). Het is verstandig om dit soort waarden niet letterlijk in de programmatekst op te nemen, bijvoorbeeld max = -32768, maar als een constante te declareren. Dat is zeker het geval als zo’n waarde vaker voorkomt, en de tekst wordt beter leesbaar. If getal > max Then max = getal End If
44
Programmeren VBA 1
Hoofdstuk 4
Dit is nu de keuzestructuur. Als een gevonden getal groter is dan het grootste tot nu toe gevonden getal, dan wordt dat de nieuwe grootste, en anders hoeft er niets te gebeuren. Het Else-deel ontbreekt nu. De syntaxis is hier dus: If voorwaarde Then [instructies] End If In een schema ziet de keuzestructuur er zo uit:
Voorbeeld 3 In dit voorbeeld wordt van een stel getallen bepaald hoeveel er positief zijn, hoeveel negatief, en hoeveel gelijk aan nul. Sub Voorbeeld03() ' positief, negatief of nul (If ... Then ... ElseIf ... Else) ' de getallen, het aantal: Dim getal As Integer, aantal As Integer ' tellertje: Dim i As Integer ' aantal positieve/negatieve getallen: Dim AantalPos As Integer, AantalNeg As Integer ' aantal nullen: Dim AantalNul As Integer ' initialisatie: Randomize AantalPos = 0: AantalNeg = 0: AantalNul = 0 ' invoer aantal: pvInvoer "Aantal getallen:", aantal ' maak (en schrijf) getallen; ' tel de aantallen: pvZetRTabs 1.6, 10 For i = 1 To aantal ' een getal van -5 t/m 5: getal = Int(11 * Rnd) - 5 pvUitvoer vbTab, getal If getal > 0 Then AantalPos = AantalPos + 1 ElseIf getal < 0 Then AantalNeg = AantalNeg + 1 Else AantalNul = AantalNul + 1 End If Next i pvNR
45
Programmeren VBA 1
Hoofdstuk 4
' uitvoer resultaten: pvWisTabs pvUitvoer "Het aantal pvUitvoer "Het aantal pvNR pvUitvoer "Het aantal pvNR pvUitvoer "Het aantal pvNR End Sub ' Voorbeeld03
getallen is ", aantal, ".": pvNR positieve getallen is ", AantalPos, "." negatieve getallen is ", AantalNeg, "." nullen is ", AantalNul, "."
Toelichting AantalPos = 0: AantalNeg = 0: AantalNul = 0 De ‘tellers’ worden geïnitialiseerd. If getal > 0 Then AantalPos = AantalPos + 1 ElseIf getal < 0 Then AantalNeg = AantalNeg + 1 Else AantalNul = AantalNul + 1 End If Er zijn nu drie mogelijkheden: positief, negatief of nul. Bij de eerste If wordt gekeken of het getal positief is. Als dat niet zo is, dan wordt bij de volgende ElseIf gekeken of het getal negatief is. Als ook dat niet het geval is, dan moet het getal gelijk aan nul zijn. Zeker bij een ‘meervoudige’ keuzestructuur zoals hier, is het verstandig om het meest waarschijnlijke geval vooraan te zetten, en het minst waarschijnlijke geval achteraan. Je moet dan natuurlijk wel een idee hebben van de kansen op de verschillende mogelijkheden. Hier gaat het om gehele getallen van –5 tot en met 5, dus 11 verschillende waarden. Van deze 11 waarden zijn er 5 positief, 5 negatief en 1 nul. Daarom wordt eerst getest op positief of negatief, en pas aan het einde op nul. Als het om willekeurige reële getallen zou gaan, dan zou ongeveer de helft positief zijn, en de helft negatief. De kans op een waarde nul is dan wel heel erg klein. Het bijbehorende schema:
De keuzestructuur is hier bovendien onderdeel van een herhalingsstructuur. Je zou het hele programma schematisch zo kunnen weergeven:
46
Programmeren VBA 1
Hoofdstuk 4
De volledige syntaxis van de ‘If...Then...ElseIf...Else-Instructie’ is dus: If voorwaarde Then [instructies] [ElseIf voorwaarde-n Then [andersindieninstructies] ... [Else [andersinstructies]] End If Kijk voor meer informatie in de Visual Basic-editor: Help Index If ... Then ... Else (instructie).
Voorbeeld 4a In het onderstaande programma wordt uit het rangnummer van een maand ‘berekend’ hoeveel dagen die maand heeft. Sub Voorbeeld04a() ' aantal dagen in een maand (If ... Then ... ElseIf ... Else) ' nummer van de maand, aantal dagen: Dim m As Integer, d As Integer ' de namen van de maanden (als matrix): Dim maandnaam As Variant ' voor MsgBox: Dim msg As Integer ' de namen van de maanden; ' het eerste element heeft nummer 0 (vandaar ""): maandnaam = Array("", "januari", "februari", "maart", _ "april", "mei", "juni", _ "juli", "augustus", "september", _ "oktober", "november", "december") ' invoer (en controle) nummer van de maand: pvInvoer "Nummer van de maand:", m Do While (m < 1) Or (m > 12) msg = MsgBox("Een nummer van 1 tot en met 12.", _ vbOKOnly + vbExclamation, "Fout") pvInvoer "Nummer van de maand:", m Loop
47
Programmeren VBA 1
Hoofdstuk 4
' berekening aantal dagen: If m = 1 Or m = 3 Or m = 5 Or m = 7 Or m = 8 Or m = 10 _ Or m = 12 Then d = 31 ElseIf m = 4 Or m = 6 Or m = 9 Or m = 11 Then d = 30 Else d = 28 ' geen schrikkeljaar End If ' uitvoer: pvUitvoer "De maand ", maandnaam(m), " heeft ", d, " dagen." pvNR End Sub ' Voorbeeld04a Toelichting Dim maandnaam As Variant maandnaam = Array("", "januari", "februari", "maart", _ "april", "mei", "juni", _ "juli", "augustus", "september", _ "oktober", "november", "december") Dit is een ‘aardigheidje’ om bij het rangnummer de naam van de maand te vinden. Kijk voor meer informatie in de Visual Basic-editor: Help Index Array (Functie).
pvInvoer "Nummer van de maand:", n Do While (m < 1) Or (m > 12) msg = MsgBox("Een nummer van 1 tot en met 12.", _ vbOKOnly + vbExclamation, "Fout") pvInvoer "Nummer van de maand:", m Loop Hiermee wordt alleen ‘geldige’ invoer (een getal van 1 tot en met 12) doorgelaten. De rest van het programma zou anders niet (helemaal) kloppen. If m = 1 Or m = 3 Or m = 5 Or m = 7 Or m = 8 Or m = 10 _ Or m = 12 Then d = 31 ElseIf m = 4 Or m = 6 Or m = 9 Or m = 11 Then d = 30 Else d = 28 ' geen schrikkeljaar End If De keuzestructuur; dit ziet er niet geweldig uit, dus in voorbeeld 4b lossen we dat anders op. Voorbeeld 4b Sub Voorbeeld04b() ' aantal dagen in een maand (Select Case) ' nummer van de maand, aantal dagen: Dim m As Integer, d As Integer ' de namen van de maanden (als matrix): Dim maandnaam As Variant ' voor MsgBox: Dim msg As Integer
48
Programmeren VBA 1
Hoofdstuk 4
' de namen van de maanden; ' het eerste element heeft nummer 0 (vandaar ""): maandnaam = Array("", "januari", "februari", "maart", _ "april", "mei", "juni", _ "juli", "augustus", "september", _ "oktober", "november", "december") ' invoer (en controle) nummer van de maand: pvInvoer "Nummer van de maand:", m Do While (m < 1) Or (m > 12) msg = MsgBox("Een nummer van 1 tot en met 12.", _ vbOKOnly + vbExclamation, "Fout") pvInvoer "Nummer van de maand:", m Loop ' berekening aantal dagen: Select Case m Case 1, 3, 5, 7, 8, 10, 12 d = 31 Case 4, 6, 9, 11 d = 30 Case 2 d = 28 ' geen schrikkeljaar End Select ' uitvoer: pvUitvoer "De maand ", maandnaam(m), " heeft ", d, " dagen." pvNR End Sub ' Voorbeeld04b Toelichting Select Case m Case 1, 3, 5, 7, 8, 10, 12 d = 31 Case 4, 6, 9, 11 d = 30 Case 2 d = 28 ' geen schrikkeljaar End Select Dit heeft precies hetzelfde effect als de If...Then...ElseIf...Else constructie in voorbeeld 4a, maar het ziet er een stuk overzichtelijker uit. Vooral in situaties waarbij het gaat om een beperkt aantal mogelijkheden zonder veel regelmaat is deze constructie aan te bevelen. Merk op dat ook hier de gevallen genoemd zijn in afnemende waarschijnlijkheid. Deze constructie wordt in een schema zo aangegeven:
49
Programmeren VBA 1
Hoofdstuk 4
De (volledige) syntaxis van de Select Case-Instructie is: Select Case testexpressie [Case expressielijst-n [instructies-n]] ... [Case Else [andersinstructies]] End Select Kijk voor meer informatie in de Visual Basic-editor: Help Index Select (instructie).
Voorbeeld 5 In dit programma wordt berekend hoeveel reële oplossingen de vergelijking ax2 + bx + c = 0 (a ≠ 0) heeft. Sub Voorbeeld05() ' aantal oplossingen van een tweedegraads vergelijking ' coëfficiënten van ax² + bx + c = 0: Dim a As Double, b As Double, c As Double ' discriminant: Dim d As Double ' invoer: pvInvoer "ax² + bx + c = 0; geef de waarde van a:", a pvInvoer "ax² + bx + c = 0; geef de waarde van b:", b pvInvoer "ax² + bx + c = 0; geef de waarde van c:", c pvUitvoer "De vergelijking ax² + bx + c = 0, met " pvUitvoer "a = ", a, ", b = ", b, " en c = ", c, " " 'berekening discriminant: d = b ^ 2 - 4 * a * c ' uitvoer: Select Case d Case Is > 0 pvUitvoer "heeft twee reële oplossingen." Case Is < 0 pvUitvoer "heeft geen reële oplossingen." Case Else pvUitvoer "heeft één reële oplossing." End Select pvNR End Sub ' Voorbeeld05 Toelichting Select Case d Case Is > 0 ... Case Is < 0 ... Case Else ... End Select
50
Programmeren VBA 1
Hoofdstuk 4
Hier wordt de keuze gemaakt afhankelijk van de waarde van de variabele d. Het komt hier op hetzelfde neer als: If d > 0 Then ... ElseIf d < 0 Then ... Else ... End If Bedenk wel dat elke keuzestructuur een programma (minstens) twee keer zo ingewikkeld maakt omdat er steeds (minstens) twee manieren zijn om het programma te doorlopen. Dus als een programma bijvoorbeeld drie If ... Then ... Else-instructies bevat, dan zijn er acht manieren om het programma te doorlopen.
Het is dus altijd verstandig om vooraf te overwegen of er wel een keuzestructuur nodig is. Het is zeker niet verstandig om ergens vlug even een If-instructie toe te voegen om ‘op alles voorbereid’ te zijn. Hierna volgt een voorbeeld van een programma waar een If-instructie overbodig is Voorbeeld 6a Het volgende programma berekent bij een gegeven begintijdstip en een tijdsverschil (in uren en minuten) de bijbehorende eindtijd. Sub Voorbeeld06a() ' rekenen met tijden (overbodige keuzestructuur) ' begintijd: Dim u0 As Byte, m0 As Byte ' tijdsverschil: Dim u1 As Byte, m1 As Byte ' eindtijd: Dim u2 As Byte, m2 As Byte ' invoer: pvInvoer "Begintijd; uren", u0 pvInvoer "Begintijd; minuten", m0 pvInvoer "Tijdsverschil; uren", u1 pvInvoer "Tijdsverschil; minuten", m1 ' berekening: u2 = u0 + u1: m2 = m0 + m1
51
Programmeren VBA 1
Hoofdstuk 4
If m2 >= 60 Then u2 = u2 + 1: m2 = m2 - 60 End If ' uitvoer: pvUitvoer "Begintijd: ", u0, ":", m0, ".": pvNR pvUitvoer "Tijdsverschil: ", u1, ":", m1, ".": pvNR pvUitvoer "Eindtijd: ", u2, ":", m2, ".": pvNR End Sub ' Voorbeeld06a Toelichting u2 = u0 + u1: m2 = m0 + m1 If m2 >= 60 Then u2 = u2 + 1: m2 = m2 - 60 End If Dit lijkt heel degelijk: tel het tijdsverschil op bij de begintijd, en pas zo nodig de uren en minuten aan. Voorbeeld 6b Hier gebeurt hetzelfde als in het vorige voorbeeld, maar nu zonder If. Sub Voorbeeld06b() ' rekenen met tijden (zonder keuzestructuur) ' begintijd: Dim u0 As Byte, m0 As Byte ' tijdsverschil: Dim u1 As Byte, m1 As Byte ' eindtijd: Dim u2 As Byte, m2 As Byte ' invoer: pvInvoer "Begintijd; uren", u0 pvInvoer "Begintijd; minuten", m0 pvInvoer "Tijdsverschil; uren", u1 pvInvoer "Tijdsverschil; minuten", m1 ' berekening: u2 = u0 + u1: m2 = m0 + m1 u2 = u2 + m2 \ 60: m2 = m2 Mod 60 ' uitvoer: pvUitvoer "Begintijd: ", u0, ":", m0, ".": pvNR pvUitvoer "Tijdsverschil: ", u1, ":", m1, ".": pvNR pvUitvoer "Eindtijd: ", u2, ":", m2, ".": pvNR End Sub ' Voorbeeld06b Toelichting u2 = u0 + u1: m2 = m0 + m1 u2 = u2 + m2 \ 60: m2 = m2 Mod 60 Hier wordt de berekening uitgevoerd met behulp van de bewerkingen \ en Mod. Als 0 ≤ m2 < 60, dan is m2 \ 60 = 0 en m2 Mod 60 = m2. Als 60 ≤ m2 < 120, dan is m2 \ 60 = 1 en m2 Mod 60 = m2 − 60. Het aanpassen van de uren en minuten gaat nu dus ‘vanzelf ’. 52
Programmeren VBA 1
Hoofdstuk 4
Opgaven Opgave 1 a. In opgave 1 van hoofdstuk 1 werd de oppervlakte van een driehoek, met zijden a, b en c, berekend met de formule: 1 O = s(s − a)(s − b)(s − c) met s = 2 (a + b + c). Dit gaat natuurlijk niet goed als de uitdrukking onder de wortel negatief is; er is dan met de gegeven a, b en c geen driehoek mogelijk. Schrijf een programma met als invoer de lengtes van de drie zijden, en als uitvoer de oppervlakte van de driehoek, of de mededeling dat met deze gegevens geen driehoek mogelijk is. Gebruik de volgende variabelen: a, b, c de drie zijden Double de halve omtrek Double s O2 het kwadraat van de oppervlakte Double de oppervlakte Double O b. In opgave 4 van hoofdstuk 1 werd de derde zijde a van een driehoek met zijden b en c, en ingesloten hoek α berekend met de cosinusregel: a2 = b2 + c2 − 2bc cos α dus a = b2 + c2 − 2bc cos α . Ook dit gaat mis als de uitdrukking onder de wortel negatief is; er is dan met de gegevens geen driehoek mogelijk. Schrijf een programma met als invoer de zijden b en c en de hoek α (in graden), en als uitvoer de zijde a, of de mededeling dat er geen driehoek mogelijk is. Gebruik de volgende variabelen: b, c de twee gegeven zijden Double de ingesloten hoek Double alfa het kwadraat van de derde zijde Double a2 de derde zijde Double a Definieer π als: Const Pi As Double = 3.14159265358979 Opgave 2 a. Schrijf een programma om twee variabelen te ‘sorteren’. Invoer: de twee waarden a en b; uitvoer: de gesorteerde variabelen (de kleinste waarde in a, de grootste waarde in b). Kijk zo nodig nog eens naar opgave 6 van hoofdstuk 2. Gebruik de volgende variabelen: a, b de twee variabelen Double hulpvariabele voor verwisseling Double hulp b. Doe hetzelfde voor drie variabelen a, b en c. Opgave 3 Een jaar is een schrikkeljaar als het jaartal deelbaar is door 4. Maar de jaartallen die door 100 deelbaar zijn, zijn geen schrikkeljaar. Uitzondering op die laatste regel zijn de jaartallen die door 400 deelbaar zijn, want die zijn weer wel schrikkeljaren. Dus 1952 is een schrikkeljaar, 1900 is geen schrikkeljaar en 2000 is wel een schrikkeljaar. Schrijf een programma met als invoer een jaartal, en als uitvoer de mededeling of het wel of niet een schrikkeljaar is. Gebruik daarbij een Boolean-variabele die True is als het jaar een schrikkeljaar is, en anders False.
53
Programmeren VBA 1
Hoofdstuk 4
Geef eerst een oplossing met If-instructies, en probeer ook om het zonder If-instructies te formuleren. Gebruik de volgende variabelen: het jaartal Integer j Boolean schrikkel wel of niet schrikkeljaar Opgave 4 De dag van de week waarop een bepaalde datum valt, is met een formule te berekenen. Deze formule drukt de dag van de week uit in de waarden van d (dag), m (maand) en j (jaar). Deze formule is geldig vanaf het jaar 1583. De maanden januari en februari worden als de 13e en 14e maand van het voorafgaande jaar beschouwd, dus als m = 1 of m = 2 , tel dan 12 op bij m en trek 1 af van j. Bereken vervolgens: a = j \ 100 − j \ 400 b = d + 2m + (3m + 3) \ 5 + j + j \ 4 − a + 1 c = b Mod 7 Nu geeft de waarde van c aan op welke dag de datum valt, namelijk: 0 betekent zondag, 1 betekent maandag, enz. Schrijf een programma met als invoer een datum (dag, maand, jaar) en als uitvoer de dag van de week waarop die datum valt. Laat de invoer van het jaar controleren (1583 of later), en laat ongeldige invoer weigeren (gebruik MsgBox, zie ook voorbeeld 4a). Gebruik de volgende variabelen: d, m, j dag, maand, jaar Integer a, b, c zoals in de formule Integer voor MsgBox Integer msg Opgave 5 In voorbeeld 2 van dit hoofdstuk is van een aantal getallen het grootste getal gevonden. Deze ‘berekening’ lijkt op die van het berekenen van de som van een aantal getallen (hoofdstuk 3, voorbeeld 5). In voorbeeld 8 van hoofdstuk 3 is van een aantal getallen het gemiddelde berekend, en daarna van elk getal de afwijking van het gemiddelde. Daarvoor was een matrix (array) nodig. a. Schrijf een programma dat van en aantal getallen het grootste bepaalt, en ook telt hoeveel van de getallen gelijk zijn aan dit grootste getal. Invoer van het programma is het aantal getallen. De uitvoer bestaat uit het grootste getal en het aantal dat daaraan gelijk is. Sla de getallen op in een matrix. Laat de getallen bepalen door Int(10 * Rnd) - 5 Je krijgt dan gehele getallen van −5 t/m 4. Gebruik de volgende variabelen: getal() de getallen (matrix) Integer tellertje Integer i het aantal getallen Integer aantal het grootste getal Integer max het aantal gelijk aan de grootste Integer nmax b. Herschrijf het bovenstaande programma zo dat je geen matrix nodig hebt. Opgave 6 Om de oppervlakte π van de eenheidscirkel te benaderen zou je (in gedachten) pijltjes kunnen gooien naar een vierkant van 2 bij 2 cm. Een pijltje komt dan terecht in een punt (x, y) met −1 ≤ x ≤ 1 en −1 ≤ y ≤ 1. Sommige pijltjes komen binnen de eenheidscirkel (x2 + y2 < 1) en andere niet. Door het aantal pijtjes binnen de eenheidscirkel te vergelijken met het totale aantal pijltjes krijg je een idee van de oppervlakte van de eenheidscirkel. 54
Programmeren VBA 1
Hoofdstuk 4
Schrijf een programma dat een aantal willekeurige x- en y-waarden tussen −1 en 1 maakt, en telt hoeveel daarvan voldoen aan x2 + y2 < 1. Zo’n waarde vind je met 2 * Rnd - 1 Invoer van het programma is het aantal punten, uitvoer is het aantal punten binnen de eenheidscirkel, en een benadering van π. Gebruik de volgende variabelen: x-waarde Double x y-waarde Double y het aantal punten (x, y) Long aantal tellertje Long i aantal punten binnen de cirkel Long teller Opgave 7 Het onderstaande programma schrijft 50 willekeurige getallen in 5 regels (alinea’s) van 10 getallen. Sub Opgave07a() ' in rijen van 10, met If Const aantal As Integer = 50 Dim i As Integer, getal As Integer For i = 1 To aantal getal = Int(100 * Rnd) pvUitvoer vbTab, getal If i Mod 10 = 0 Then pvNR Next i End Sub ' Opgave07a Herschrijf dit programma zo dat er geen If-instructie nodig is. Opgave 8* In de datum van Pasen lijkt weinig regelmaat te zitten. Toch is deze met een niet zo ingewikkelde formule te bepalen. De formule luidt voor de jaren j tussen 1583 en 2199 als volgt. Haal de getallen m en n uit de onderstaande tabel. m n 22 2 1583 ≤ j ≤ 1699 3 1700 ≤ j ≤ 1799 23 4 1800 ≤ j ≤ 1899 23 24 5 1900 ≤ j ≤ 2099 6 2100 ≤ j ≤ 2199 24 Bereken daarna de getallen: a = j Mod 19 b = j Mod 4 c = j Mod 7 d = (19a + m) Mod 30 e = (2b + 4c + 6d + n) Mod 7 f=d+e−9 Als f ≤ 0, dan valt Pasen op 31 + f maart, en anders op f april. Uitzonderingen hierop zijn: • • a. Schrijf een programma met als invoer het jaartal j, en als uitvoer de datum van Pasen. Laat het programma alleen jaren accepteren tussen 1583 en 2199 (met MsgBox).
55
Programmeren VBA 1
Hoofdstuk 4
b. Schrijf een programma met als invoer twee jaartallen j1 en j2, en als uitvoer de data van Pasen vanaf jaar j1 tot en met jaar j2. Opgave 9* Niet alle rekeningnummers bij de bank (met 9 cijfers) zijn ‘geldige’ nummers. Om te zien of het een geldig nummer is, moet je het eerste (meest linkse) cijfer met 9 vermenigvuldigen, hert tweede cijfer met 8 enzovoort, en de som daarvan berekenen. Deze som moet deelbaar zijn door 11. Zo krijg je bij het nummer 357412982: 9×3 + 8×5 + 7×7 + 6×4 + 5×1 + 4×2 + 3×9 + 2×8 + 1×2 = 198, en dat is door 11 deelbaar. Schrijf een programma met als invoer een getal van 9 cijfers, en als uitvoer de mededeling of het een geldig nummer is of niet. Gebruik zo nodig een matrix voor de aparte cijfers. Je kunt die vinden zoals hieronder: (357412982 \ 1) Mod 10 = 357412982 Mod 10 = 2, (357412982 \ 10) Mod 10 = 35741298 Mod 10 = 8, (357412982 \ 100) Mod 10 = 3574129 Mod 10 = 9. enzovoort. Opgave 10* Om een vergelijking y = mx + n van een lijn door de twee punten (a, b) en (c, d) te vinden reken je eerst de richtingscoëfficiënt uit d−b m= ⋅ c−a Een vergelijking is dan y − b = m(x − a) ⇔ y = mx − ma + b. Als a = c gaat dit natuurlijk niet goed. Een vergelijking van de lijn is dan x = a. a. Schrijf een programma met als invoer de twee (verschillende) punten (a, b) en (c, d), en als uitvoer een vergelijking van de lijn door die twee punten. Laat het programma alleen twee verschillende punten als invoer accepteren. b. Als je onderdeel a hebt uitgevoerd zul je zien dat het programma (waarschijnlijk) niet altijd de uitvoer geeft die je ‘mooi’ zou vinden. Herschrijf het programma zo dat bij y = mx + n rekening wordt gehouden met de gevallen • m = 1, m = −1 en m = 0; • n > 0, n < 0 en n = 0. Opgave 11 In opgave 4 van hoofdstuk 2 heb je arcsin x laten berekenen met de formule x arcsin x = arctan ⋅ 1 − x2 Dit geldt natuurlijk niet voor x = ± 1, en ook voor waarden van x in de buurt van x = ± 1 is 1 − x2 bijna 0, dus daar is de formule ‘onnauwkeurig’. Je kunt dit ondervangen door voor 1 1 hoeken groter dan 4 π of kleiner dan − 4 π, waarvoor | tan α | > 1, de formules 1 1 arctan p = 2 π − arctan p als p > 0 1 1 arctan p = − 2 π − arctan p als p < 0 te gebruiken (p = tan α).
56
Programmeren VBA 1
Hoofdstuk 4
Gebruik dit om een programma te schrijven met als invoer een waarde van x, en als uitvoer de waarde van arcsin x. Zorg ervoor dat het programma geen waarden groter dan 1 of kleiner dan –1 als invoer accepteert. Met de formules 1 arccos x = 2 π − arcsin x kun je dan meteen arccos x laten berekenen. Opgave 12 Een punt P(x, y) heeft als poolcoördinaten de lengte r van het lijnstuk OP, en de hoek ϕ in radialen (−π < ϕ ≤ π) die de lijn OP maakt met de positieve x-as. Hiervoor geldt: y r = x2 + y2 en tan ϕ = x ⋅ Uit dat laatste zou volgen dat y ϕ = arctan x 1 1 maar daarmee krijg je altijd een hoek ϕ met − 2 π < x < 2 π, en dat klopt niet als x < 0. Bovendien levert het problemen op als x = 0. Schrijf een programma met als invoer de waarden van x en y, en als uitvoer de poolcoördinaten r en ϕ van het punt P(x, y). Opgave 13 In opgave 2 heb je twee en drie getallen van klein naar groot gesorteerd door elk getal te vergelijken met alle volgende getallen, en te verwisselen als een volgend getal kleiner was. Dat werkt natuurlijk ook met meer dan drie getallen. Schrijf een programma dat een (1-dimensionale) matrix vult met willekeurige getallen, bijvoorbeeld Int(100 * Rnd), en vervolgens de getallen sorteert van klein naar groot. De invoer van het programma is het aantal getallen, en de uitvoer de oorspronkelijke en de gesorteerde matrix.
57
Programmeren VBA 1
58
Programmeren VBA 1
Hoofdstuk 5
5. Procedures, functies Vooral bij grotere programma’s loop je het risico dat je het overzicht kwijtraakt. Je kunt dit voorkomen door het gebruik van procedures (of subroutines). Voorbeeld 1 Hieronder staat een programma om de schuine zijde van een rechthoekige driehoek te berekenen, zoals in voorbeeld 4 van hoofdstuk 1. Option Explicit ' alle variabelen moeten gedeclareerd worden ' de stelling van Pythagoras (hoofdstuk 1, voorbeeld 4) ' de drie zijden, nu op ‘module-niveau’ gedeclareerd; Dim a As Double, b As Double, c As Double Sub Voorbeeld01() Invoer Berekening Uitvoer End Sub ' Voorbeeld01 Private Sub Invoer() ' invoer van de rechthoekszijden pvInvoer "De ene rechthoekszijde:", a pvInvoer "De andere rechthoekszijde:", b End Sub ' Invoer Private Sub Berekening() ' berekening van de schuine zijde c = Sqr(a ^ 2 + b ^ 2) End Sub ' Berekening Private Sub Uitvoer() ' uitvoer pvUitvoer "De ene rechthoekszijde is ", a, ".": pvNR pvUitvoer "De andere rechthoekszijde is ", b, ".": pvNR pvUitvoer "De schuine zijde is ", c, ".": pvNR End Sub ' Uitvoer Toelichting De drie variabelen a, b en c zijn nu op moduleniveau gedeclareerd, dus vóór de ‘hoofdprocedure’ Voorbeeld01. Hierdoor zijn ze in de hele module zichtbaar. De hoofdprocedure, of het ‘hoofdprogramma’, Voorbeeld01 zelf bestaat nu uit maar drie instructies: Invoer, Berekening en Uitvoer. Dit zijn de ‘logische’ delen waaruit het programma bestaat. In elk van deze instructies wordt een procedure opgeroepen. Daarmee krijg je dus een nauwkeurige weergave van de volgende structuur. Sub Voorbeeld01() invoer berekening uitvoer End Sub ' Voorbeeld01
59
Programmeren VBA 1
Hoofdstuk 5
Daarna zijn de procedures gedeclareerd. In deze declaratie wordt precies omschreven wat elke procedure moeten doen: de ‘uitvoerbare instructies’. De hier gebruikte syntaxis van de declaratie is: Private Sub naam [(argumenten)] [instructies] End Sub Dit lijkt dus sterk op de declaratie van de ‘hoofdprocedure’, maar nu staat aan het begin van elke ‘hulpprocedure’ het woord Private. Hierdoor zijn deze procedures alleen binnen deze module toegankelijk. In tegenstelling tot de situatie in de eerdere hoofdstukken is nu voor elk voorbeeld (en elke opgave) een aparte module gebruikt. Dit is gedaan om eventuele problemen van ‘zichtbaarheid’ van variabelen en procedures te voorkomen. Ook in volgende voorbeelden zullen procedures met namen zoals Invoer en Uitvoer voorkomen, en er moet natuurlijk geen verwarring mogelijk zijn over welke procedure bedoeld wordt. Je zou kunnen vinden dat dit programma een stuk ‘langer’ is dan dat in het voorbeeld uit hoofdstuk 1. Dat is hier inderdaad het geval, maar in het algemeen worden vooral grotere programma’s nauwelijks langer door het gebruik van procedures. Bovendien weegt het voordeel van de overzichtelijkheid daar ruimschoots tegen op. In voorbeeld 5 zul je zien hoe procedures ook kunnen worden gebruikt om programma’s juist korter te maken.
Voorbeeld 2 Hieronder staat een programma om te berekenen wanneer een bepaald bedrag bij een bepaald rentepercentage (minstens) verdubbeld is, zoals in voorbeeld 2 van hoofdstuk 3. Option Explicit ' alle variabelen moeten gedeclareerd worden ' renteberekening (hoofdstuk 3, voorbeeld 2) ' variabelen op module-niveau; ' beginbedrag en bedrag: Dim b0 As Double, b As Double ' rentepercentage, aantal jaren: Dim p As Double, n As Long Sub Voorbeeld02() ' renteberekening Invoer UitvoerGegevens Berekening Resultaten End Sub ' Voorbeeld02
60
Programmeren VBA 1
Hoofdstuk 5
Private Sub Invoer() ' invoer bedrag en percentage, en controle percentage ' voor de functie MsgBox: Dim msg As Integer pvInvoer "Beginbedrag:", b0 pvInvoer "Rentepercentage (per jaar):", p Do While p <= 0 msg = MsgBox("Het percentage moet positief zijn.", _ vbOKOnly + vbExclamation, "Fout") pvInvoer "Rentepercentage (per jaar):", p Loop End Sub ' Invoer Private Sub UitvoerGegevens() ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 pvUitvoer "Beginbedrag: ": pvUitvoerR b0, ac: pvNR pvUitvoer "Rentepercentage: ", p: pvNR End Sub ' UitvoerGegevens Private Sub Berekening() ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 ' rentefactor: Dim r As Double r = 1 + p / 100 n = 0: b = b0 Do While b < 2 * b0 n = n + 1: b = b * r pvUitvoer n, vbTab: pvUitvoerR b, ac: pvNR Loop End Sub ' Berekening Private Sub Resultaten() ' aantal cijfers achter de komma (bij pvUitvoerR): Const ac As Integer = 2 pvUitvoer "Aantal jaren: ", n: pvNR pvUitvoer "Eindbedrag: ": pvUitvoerR b, ac: pvNR End Sub ' Resultaten Toelichting Ook hier is het programma met behulp van procedures in logische onderdelen verdeeld. In sommige procedures komen nu ook declaraties van constanten en/of variabelen voor. Dit zijn de voor die procedure locale variabelen en constanten. Die zijn alleen binnen de betreffende procedure zichtbaar, in tegenstelling tot de globale variabelen (de variabelen op moduleniveau) b0, b, p en n. De gedachte hierachter is de volgende. Een kleiner aantal (globale) variabelen zorgt ervoor dat een programma beter te begrijpen is. Allerlei ‘hulpvariabelen’ (zoals tellertjes in een herhalingsstructuur) zijn voor de hoofdlijnen van een programma niet van belang. Hier is dat dus onder andere de variabele msg in de procedure Invoer, die alleen ter plaatse (lokaal) even nodig is om een berichtje te laten verschijnen.
61
Programmeren VBA 1
Hoofdstuk 5
Een ander voordeel is dat binnen verschillende procedures locale variabelen met dezelfde naam mogen voorkomen, die voor elkaar onzichtbaar zijn. Je hoeft je er dus niet druk om te maken of een bepaalde naam van een variabele in een andere procedure, misschien met een heel andere bedoeling, ook al voorkomt, want de variabelen kunnen elkaar toch niet ‘zien’. Kijk voor meer informatie in de Visual Basic-editor: Help Index; bereik op procedureniveau (bereik en zichtbaarheid).
Voorbeeld 3 In dit voorbeeld wordt van een aantal getallen berekend hoeveel er positief, negatief of nul zijn, zoals in voorbeeld 3 van hoofdstuk 4. Option Explicit ' alle variabelen moeten gedeclareerd worden ' positief, negatief of nul (If ... Then ... ElseIf ... Else) ' (hoofdstuk 4, voorbeeld 3) ' variabelen op module-niveau; ' het aantal getallen: Dim Aantal As Integer ' het aantal positieve/negatieve getallen: Dim AantalPos As Integer, AantalNeg As Integer ' het aantal nullen: Dim AantalNul As Integer Sub Voorbeeld03() Initialisatie AantalPos, AantalNeg, AantalNul Invoer Aantal MaakEnTel Aantal, AantalPos, AantalNeg, AantalNul Uitvoer Aantal, AantalPos, AantalNeg, AantalNul End Sub ' Voorbeeld03 Private Sub Initialisatie(ByRef ap As Integer, _ ByRef an As Integer, _ ByRef a0 As Integer) ' initialisatie van Random-getallen en aantallen Randomize ap = 0: an = 0: a0 = 0 End Sub ' Initialisatie Private Sub Invoer(ByRef a As Integer) ' invoer van het aantal getallen pvInvoer "Aantal getallen:", a End Sub ' Invoer
62
Programmeren VBA 1
Hoofdstuk 5
Private Sub MaakEnTel(ByVal a As Integer, _ ByRef ap As Integer, _ ByRef an As Integer, _ ByRef a0 As Integer) ' maak Random-getallen en tel aantallen pos/neg/nul ' de getallen: Dim getal As Integer ' tellertje: Dim i As Integer pvZetRTabs 1.6, 10 For i = 1 To a ' een getal van -5 t/m 5: getal = Int(11 * Rnd) - 5 pvUitvoer vbTab, getal If getal > 0 Then ap = ap + 1 ElseIf getal < 0 Then an = an + 1 Else a0 = a0 + 1 End If Next i pvNR End Sub ' MaakEnTel Private Sub Uitvoer(ByVal a As Integer, _ ByVal ap As Integer, _ ByVal an As Integer, _ ByVal a0 As Integer) ' uitvoer resultaten pvWisTabs pvUitvoer "Het aantal getallen is ", a, ".": pvNR pvUitvoer "Het aantal positieve getallen is ", ap, "." pvNR pvUitvoer "Het aantal negatieve getallen is ", an, "." pvNR pvUitvoer "Het aantal nullen is ", a0, "." pvNR End Sub ' Uitvoer Toelichting De ‘hoofdprocedure’ ziet er nu zo uit: Sub Voorbeeld03() Initialisatie AantalPos, AantalNeg, AantalNul Invoer Aantal MaakEnTel Aantal, AantalPos, AantalNeg, AantalNul Uitvoer Aantal, AantalPos, AantalNeg, AantalNul End Sub ' Voorbeeld03 Bij elke oproep van een procedure wordt nu aangegeven welke variabelen daarbij betrokken zijn. Deze heten de argumenten van de procedure. Dit heeft natuurlijk ook gevolgen voor de declaraties van de procedures. Zo is de declaratie van de procedure Initialisatie als volgt 63
Programmeren VBA 1
Hoofdstuk 5
Private Sub Initialisatie(ByRef ap As Integer, _ ByRef an As Integer, _ ByRef a0 As Integer) ' initialisatie van Random-getallen en aantallen Randomize ap = 0: an = 0: a0 = 0 End Sub ' Initialisatie Hierin heten de formele argumenten ap, an en a0. Bij de oproep van de procedure Initialisatie AantalPos, AantalNeg, AantalNul worden deze formele argumenten vervangen door de eigenlijke argumenten, in de volgorde waarin ze in oproep en declaratie voorkomen. Het effect is hier dus AantalPos = 0: AantalNeg = 0: AantalNul = 0 De argumenten worden hier via verwijzing (‘by reference’) doorgegeven. Dat blijkt uit de ‘kop’ van de declaratie, waarin voor elk formeel argument het woord ByRef staat. Dit betekent dat de procedure de waarden van de variabelen die als argument worden opgegeven, ook daadwerkelijk kan veranderen. Dit is hier natuurlijk ook de bedoeling: de variabelen AantalPos, AantalNeg en AantalNul moeten de juiste beginwaarde 0 krijgen. Iets dergelijks gebeurt bij de oproep Invoer Aantal en de declaratie Private Sub Invoer(ByRef a As Integer) ' invoer van het aantal getallen pvInvoer "Aantal getallen:", a End Sub ' Invoer Ook hier wordt het formele argument a vervangen door het eigenlijke argument Aantal pvInvoer "Aantal getallen:", Aantal en wordt de waarde ‘naar buiten bekend gemaakt’, dus in de ‘hoofdprocedure’. Bij de volgende procedure-oproep MaakEnTel Aantal, AantalPos, AantalNeg, AantalNul is de situatie anders. Deze procedure heeft wel de waarde van de variabele Aantal nodig, maar verandert daar niets meer aan; dat zou ook ongewenst zijn. Dit argument wordt nu via waarde (‘by value’) doorgegeven. Dit blijkt uit de declaratie van de procedure, waarvan de kop luidt: Private Sub MaakEnTel(ByVal a As Integer, _ ByRef ap As Integer, _ ByRef an As Integer, _ ByRef a0 As Integer) Vóór het argument a staat nu het woord ByVal, om dit aan te geven. Dit betekent dat de procedure de waarde van dit argument niet kan veranderen. Als je een argument via waarde doorgeeft, kopieer je de oorspronkelijke variabele. Eventuele wijzigingen binnen de procedure hebben geen invloed op de oorspronkelijke variabele. De kop van deze procedure is over vier regels gespreid. Dat is in principe niet nodig, maar het is hier gedaan om een niet te ‘lange’ regel te krijgen. In de laatste procedure Uitvoer worden alle argumenten via waarde doorgegeven. Dat is natuurlijk altijd het geval bij een ‘uitvoerprocedure’. In het algemeen zul je een argument via verwijzing laten doorgeven als de procedure iets aan de waarde van het betrokken variabele moet veranderen; dit gebeurt dus bij ‘initialisatieprocedures’ en ‘invoerprocedures’, zoals Initialisatie en Invoer hierboven. Bij ‘rekenprocedures’ zullen sommige argumenten via verwijzing worden doorgegeven en andere via waarde, zoals
64
Programmeren VBA 1
Hoofdstuk 5
bij MaakEnTel hierboven. De ‘gegevens’, zoals hier het aantal getallen, worden via waarde doorgegeven, en wat berekend moet worden via verwijzing. Bij ‘uitvoerprocedures’ wordt natuurlijk alles via waarde doorgegeven. Hieraan zit ook nog een praktische kant. Doorgeven via waarde betekent dat er een kopie van het argument wordt gemaakt. Bij grotere ‘structuren’, zoals Strings en matrices kan dat een bezwaar zijn: het duurt langer en het kost meer geheugen. Daarom worden dergelijke structuren eigenlijk altijd via verwijzing doorgegeven. Kijk voor meer informatie in de Visual Basic-editor: Help Index; argumenten efficiënt doorgeven.
Voorbeeld 4 Het nu volgende voorbeeld is nogal onzinnig. Het is alleen maar bedoeld om het verschil aan te geven tussen lokale en globale variabelen, en tussen het doorgeven via verwijzing en via waarde. Probeer zelf eerst eens vast te stellen wat de uitvoer van het programma zal zijn. Option Explicit ' alle variabelen moeten gedeclareerd worden ' demonstratie ByVal en ByRef ' variabelen op module-niveau; Dim a As Integer, b As Integer Sub Voorbeeld04() a = 3: b = 8 pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b Een a pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b Twee a pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b Drie a pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b pvNR pvUitvoer "Hoofdprogramma; " pvUitvoer "oorspronkelijke waarden herstellen:" pvNR a = 3: b = 8 pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b Vier a pvUitvoer "Hoofdprogramma:", vbTab: Uit a, b End Sub ' Voorbeeld04 Private Sub Een(ByVal a As Integer) Dim b As Integer a = 13: b = 18 pvUitvoer "Procedure ‘Een’: ", vbTab Uit a, b End Sub ' Een Private Sub Twee(ByRef a As Integer) Dim b As Integer a = 23: b = 28 pvUitvoer "Procedure ‘Twee’: ", vbTab Uit a, b End Sub ' Twee
65
Programmeren VBA 1
Hoofdstuk 5
Private Sub Drie(ByVal a As Integer) a = 33: b = 38 pvUitvoer "Procedure ‘Drie’: ", vbTab Uit a, b End Sub ' Drie Private Sub Vier(ByRef a As Integer) Dim a1 As Integer, b1 As Integer a1 = a ' ‘kopieer’ de waarde van a a1 = 43: b1 = 48 pvUitvoer "Procedure ‘Vier’:", vbTab Uit a1, b1 End Sub ' Vier Private Sub Uit(ByVal a As Integer, ByVal b As Integer) pvUitvoer "a = ", a, ",", vbTab, "b = ", b, ".": pvNR End Sub ' Uit Toelichting Er zijn nu twee globale variabelen, a en b. Verder bevatten de procedures Een, Twee en Vier locale variabelen. De locale variabele b in de procedure Een zorgt ervoor dat de globale variabele b binnen Een onzichtbaar is. Bovendien wordt het argument a via waarde (ByVal) doorgegeven. Dat betekent dat de instructies a = 13: b = 18 geen invloed hebben op de waarden van de globale variabelen a en b. De locale variabele b in de procedure Twee zorgt ervoor dat de globale variabele b ook binnen Twee onzichtbaar is. Maar het argument a wordt nu via verwijzing (ByRef) doorgegeven. Dat betekent dat de instructies a = 23 wel invloed heeft op de waarden van de globale variabele a, maar de instructie b = 28 niet op die van de globale variabele b. Procedure Drie heeft geen locale variabelen. Het argument a wordt via waarde doorgegeven. De instructie a = 33 verandert dus de waarde van de globale variabele a. De instructie b = 38 heeft ook betrekking op de globale variabele. Zoiets wordt wel een neveneffect (‘side effect’) van een procedure genoemd: een effect dat niet bewust door overdracht van argumenten wordt bereikt. Je kunt zoiets in het algemeen maar beter zien te voorkomen. Het argument a in procedure Vier wordt via verwijzing doorgegeven, dus in principe kan de waarde van de globale variabele a veranderen. Maar de waarde van a wordt toegewezen aan de locale variabele a1, waardoor de globale variabele a uiteindelijk niet verandert. Dit heeft dus hetzelfde effect als wanneer het argument a via waarde doorgegeven zou zijn: er wordt een ‘kopie’ van a gebruikt, en a zelf verandert niet.
66
Programmeren VBA 1
Hoofdstuk 5
Voorbeeld 5 In dit voorbeeld worden twee breuken met elkaar vermenigvuldigd. Elke breuk bestaat uit twee variabelen, een teller en een noemer. De breuken en hun produkt worden niet vereenvoudigd. De breuken worden geschreven als een geheel deel, een teller en een noemer. Option Explicit ' alle variabelen moeten gedeclareerd worden ' breuken vermenigvuldigen (zonder vereenvoudigen) ' variabelen op module-niveau; ' de eerste breuk: Dim teller1 As Long, noemer1 As Long ' de tweede breuk: Dim teller2 As Long, noemer2 As Long ' het produkt: Dim teller3 As Long, noemer3 As Long Sub Voorbeeld05() InvoerBreuk teller1, noemer1 InvoerBreuk teller2, noemer2 Produkt teller1, noemer1, teller2, noemer2, _ teller3, noemer3 UitvoerBreuk teller1, noemer1 pvUitvoer " × " UitvoerBreuk teller2, noemer2 pvUitvoer " = " UitvoerBreuk teller3, noemer3 pvNR End Sub ' Voorbeeld05 Private Sub InvoerBreuk(ByRef t As Long, _ ByRef n As Long) ' invoer van één breuk pvInvoer "Teller:", t pvInvoer "Noemer:", n Do While n = 0 Foutmelding "De noemer mag niet 0 zijn." pvInvoer "Noemer:", n Loop End Sub ' InvoerBreuk
67
Programmeren VBA 1
Hoofdstuk 5
Private Sub UitvoerBreuk(ByVal t As Long, _ ByVal n As Long) ' ‘nette’ uitvoer van één breuk ' het gehele deel van de breuk: Dim g As Long ' het gehele deel en de (nieuwe) teller: g = t \ n: t = t Mod n If g <> 0 Then pvUitvoer g, " " If t <> 0 Then pvUitvoer t, "/", n End If Else If t <> 0 Then pvUitvoer t, "/", n Else pvUitvoer 0 End If End If End Sub ' UitvoerBreuk Private Sub Produkt(ByVal t1 As Long, ByVal n1 As Long, _ ByVal t2 As Long, ByVal n2 As Long, _ ByRef t3 As Long, ByRef n3 As Long) ' produkt van twee breuken t3 = t1 * t2: n3 = n1 * n2 End Sub ' Produkt Private Sub Foutmelding(ByRef tekst As String) ' voor MsgBox: Dim msg As Integer msg = MsgBox(tekst, vbOKOnly + vbExclamation, "Fout") End Sub ' Foutmelding Toelichting In dit voorbeeld zie je hoe procedures ook kunnen worden gebruikt om programma’s juist korter te maken. Zowel voor de invoer als voor de uitvoer van een breuk wordt één procedure gebruikt die meer dan een keer, met steeds andere argumenten, wordt opgeroepen. Verder vindt binnen de invoerprocedure meteen controle plaats op geldige invoer: de noemer van de breuk mag niet nul zijn. Zolang dit wel het geval is volgt er een foutmelding, en wordt om nieuwe invoer gevraagd. Deze foutmelding wordt dan weer door een aparte procedure afgehandeld, waarin het argument tekst (een String) via verwijzing wordt doorgegeven; zie daarvoor de opmerking aan het einde van voorbeeld 3. De uitvoerprocedure is nu tamelijk uitgebreid, om de breuken een beetje ‘netjes’ te laten verschijnen. De rekenprocedure Produkt is eigenlijk nog het eenvoudigst: t3 t1 t2 n3 = n1 ⋅ n2 , dus t3 = t1 ⋅ t2 en n3 = n1 ⋅ n2. De structuur van de hoofdprocedure is hier anders dan die in de vorige voorbeelden. In de hoofdprocedure worden nu rechtstreeks de verschillende invoer-, uitvoer- en rekenprocedures opgeroepen. In de voorbeelden 1, 2 en 3 was er steeds sprake van één invoer- en één uitvoerprocedure. We hadden dit anders kunnen oplossen. De nu gebruikte constructie is
68
Programmeren VBA 1
Hoofdstuk 5
Sub Voorbeeld05() InvoerBreuk teller1, noemer1 InvoerBreuk teller2, noemer2 Produkt teller1, noemer1, teller2, noemer2, _ teller3, noemer3 UitvoerBreuk teller1, noemer1 pvUitvoer " × " UitvoerBreuk teller2, noemer2 pvUitvoer " = " UitvoerBreuk teller3, noemer3 pvNR End Sub ' Voorbeeld05 In plaats daarvan hadden we kunnen schrijven Sub Voorbeeld05() Invoer teller1, noemer1, teller2, noemer2 Produkt teller1, noemer1, teller2, noemer2, _ teller3, noemer3 Uitvoer teller1, noemer1, teller2, noemer2, _ teller3, noemer3 End Sub ' Voorbeeld05 Er hadden dan aparte procedures Invoer en Uitvoer moeten zijn, die op hun beurt de procedure InvoerBreuk resp. UitvoerBreuk zouden moeten aanroepen, ongeveer als volgt Sub Invoer(ByRef t1 As Long, ByRef n1 As Long, _ ByRef t2 As Long, ByRef n2 As Long) ' Invoer van de twee breuken InvoerBreuk t1, n1 InvoerBreuk t2, n2 End Sub ' Invoer Hiermee zou er een extra ‘niveau’ in het programma zijn ingebouwd, en je kunt je afvragen of het daarmee niet te ingewikkeld wordt. In theorie zou dit te ondervangen zijn door de procedure InvoerBreuk binnen de procedure Invoer te declareren, dus Sub Invoer(ByRef t1 As Long, ByRef n1 As Long, _ ByRef t2 As Long, ByRef n2 As Long) ... Sub InvoerBreuk() ... End Sub ' InvoerBreuk End Sub ' Invoer Maar in Visual Basic is dit niet toegestaan: procedures mogen niet ‘genest’ worden. Verder is het verstandig een procedure niet teveel te laten doen. De procedure voor het invoeren van een breuk doet wat dat betreft precies genoeg. Het maakt twee waarden ‘naar buiten’ bekend, de teller en noemer van een breuk, waarbij gegarandeerd is dat de noemer niet gelijk is aan nul. Het is dus een ‘robuuste’ procedure. De procedure UitvoerBreuk doet eigenlijk al teveel. Niet alleen wordt de waarde van de breuk geschreven, maar daarvoor wordt ook nog gerekend, namelijk de splitsing van de breuk in een geheel deel, een teller en een noemer. Deze procedure heeft dus eigenlijk al iets te veel ‘functionaliteit’.
69
Programmeren VBA 1
Hoofdstuk 5
Voorbeeld 6 Je kunt ook matrices als argument aan procedures meegeven. In het voorbeeld wordt dit gedemonstreerd door een matrix te a vullen met willekeurige getallen, en de elementen van de matrix te schrijven. Het aantal elementen van de matrix is variabel. Option Explicit ' alle variabelen moeten gedeclareerd worden ' een matrix als argument ' variabelen op module-niveau; ' het aantal elementen van de matrix: Dim Aantal As Integer ' de matrix: Dim a() As Integer Sub Voorbeeld06() Invoer Aantal Initialisatie a(), Aantal VulMatrix a(), Aantal SchrijfMatrix a(), Aantal End Sub ' Voorbeeld06 Sub Invoer(ByRef n As Integer) pvInvoer "Aantal elementen van de matrix:", n End Sub ' Invoer Sub Initialisatie(ByRef a() As Integer, _ ByVal n As Integer) Randomize ReDim a(1 To n) End Sub ' Initialisatie Private Sub VulMatrix(ByRef a() As Integer, _ ByVal n As Integer) ' tellertje: Dim i As Integer For i = 1 To n a(i) = Int(10 * Rnd) + 1 Next i End Sub ' VulMatrix Private Sub SchrijfMatrix(ByRef a() As Integer, _ ByVal n As Integer) ' tellertje: Dim i As Integer pvZetRTabs 1.6, 10 For i = 1 To n pvUitvoer vbTab, a(i) Next i pvNR pvWisTabs End Sub ' SchrijfMatrix Toelichting Blijkbaar moet je de matrix als argument opgeven zoals in de declaratie Sub Initialisatie(ByRef a() As Integer, ...)
70
Programmeren VBA 1
Hoofdstuk 5
en de oproep Initialisatie a(), Aantal Een matrixargument wordt eigenlijk altijd via verwijzing doorgegeven; zie daarvoor de opmerking aan het einde van voorbeeld 3. Voorbeeld 7 In het voorafgaande zijn al verschillende (wiskundige) functies genoemd die in Visual Basic zijn ‘ingebouwd’. Maar het komt regelmatig voor dat een bepaalde functie niet standaard beschikbaar is. In het nu volgende voorbeeld wordt van twee gehele getallen de grootste gemeenschappelijke deler (de ggd) berekend volgens het algoritme van Euclides. Option Explicit ' alle variabelen moeten gedeclareerd worden ' functie ggd: algoritme van Euclides ' variabelen op module-niveau; ' de twee getallen en de ggd: Dim m As Long, n As Long, g As Long Sub Voorbeeld07() Invoer m, n ' de functie-oproep: g = ggd(m, n) Uitvoer m, n, g End Sub ' Voorbeeld07 Private Sub Invoer(ByRef m As Long, ByRef n As Long) pvInvoer "ggd; eerste getal:", m pvInvoer "ggd; tweede getal:", n End Sub ' Invoer Private Sub Uitvoer(ByVal m As Long, ByVal n As Long, _ ByVal g As Long) pvUitvoer "De ggd van ", m, " en ", n pvUitvoer " is ", g, ".": pvNR End Sub ' Uitvoer Private Function ggd(ByVal m As Long, ByVal n As Long) _ As Long ' ggd: algoritme van Euclides ' de rest bij deling: Dim r As Long r = m Mod n ' laat de tussenresultaten zien (niet noodzakelijk): pvUitvoer m, vbTab, n, vbTab, r: pvNR Do While r > 0 m = n: n = r r = m Mod n ' laat de tussenresultaten zien (niet noodzakelijk): pvUitvoer m, vbTab, n, vbTab, r: pvNR Loop ggd = n End Function ' ggd
71
Programmeren VBA 1
Hoofdstuk 5
Toelichting De hoofdprocedure bevat weer het oproepen van een invoer- en een uitvoerprocedure. Daar tussenin wordt nu de functie ggd opgeroepen: g = ggd(m, n) Deze functie is aan einde gedeclareerd. De kop van de declaratie is nu Private Function ggd(ByVal m As Long, ByVal n As Long) _ As Long Dit lijkt dus sterk op de declaratie van een procedure. De verschillen zijn dat nu het woord Function gebruikt wordt, en dat na de argumenten vermeld staat van welk type de functiewaarde is: As Long. De syntaxis van de door ons gebruikte functiedeclaratie is Private Function naam [(argumenten)] [As type] [instructies] naam = expressie End Function De functiewaarde wordt uiteindelijk toegewezen door de instructie ggd = n Hier staat dus de naam van de functie aan de linkerkant van de toewijzingsinstructie. Voor de argumenten geldt hetzelfde als voor die van een procedure. Ze kunnen via waarde of via verwijzing worden doorgegeven. In het algemeen zal dat bij functies via waarde zijn. Dit heeft hier nog extra betekenis omdat de argumenten binnen de functie van waarde veranderen, terwijl de betrokken variabelen dat natuurlijk niet moeten doen. In deze functie worden bovendien de tussenresultaten uitgevoerd. In het algemeen zal een functie natuurlijk niet meer doen dan rekenen, maar hier is het interessant om ook die tussenresultaten te kunnen zien. Het algoritme van Euclides voor het berekenen van de ggd van twee positieve gehele getallen houdt het volgende in. Neem eens de getallen 222 en 162. Bereken daarbij quotiënt en rest: 222 = 1 × 162 + 60 Ga nu verder met de getallen 162 en 60, en doe dit nog een keer: 162 = 2 × 60 + 42 Ga hiermee door totdat de rest gelijk is aan 0: 60 = 1 × 42 + 18 42 = 2 × 18 + 6 18 = 3 × 6 + 0 De ggd is dan de laatste rest die niet gelijk is aan nul, dus 6. In de functie gebeurt dit zo: instructie r>0 m n r r = m Mod n 222 162 60 True m = n: n = r 162 60 r = m Mod n 162 60 42 True m = n: n = r 60 42 r = m Mod n 60 42 18 True m = n: n = r 42 18 r = m Mod n 42 18 6 True m = n: n = r 18 6 r = m Mod n 18 6 0 False De laatste rest die niet 0 is, is hier 6. Dit is tevens de laatste waarde van het argument n (maar niet de laatste waarde van r). Daarom sluit de functie af met ggd = n
72
Programmeren VBA 1
Hoofdstuk 5
Als je begonnen zou zijn met m = 162 en m = 222, dan zou je gekregen hebben: 162 = 0 × 222 + 162 222 = 1 × 162 + 60 162 = 2 × 60 + 42 enzovoort. Dat levert dus één extra stap in de herhaling op, maar het resultaat is hetzelfde. Je kunt de uitkomst 6 controleren door de getallen te ontbinden in factoren. Je krijgt dan de ontbindingen 222 = 2 × 3 × 37 en 162 = 2 × 34. Hieraan kun je zien dat de ggd gelijk is aan 2 × 3 = 6. Dit is geen praktische methode om de functie ggd te programmeren.
Voorbeeld 8 Hieronder staat een voorbeeld van het gebruik van een functie waarvan de functiewaarde Boolean is. Het programma doet hetzelfde als dat van voorbeeld 3 in hoofdstuk 3. Het gebruik van de functie maakt de hoofdprocedure wat overzichtelijker. De details van MsgBox zijn nu in de functie ‘verborgen’. Option Explicit ' alle variabelen moeten gedeclareerd worden ' een Boolean-functie Sub Voorbeeld08() ' een getal van 1 t/m 10: Dim i As Integer Randomize pvUitvoer "Getallen van 1 t/m 10.": pvNR Do i = Int(10 * Rnd) + 1 pvUitvoer vbTab, i ' de functie-oproep: Loop Until Klaar End Sub ' Voorbeeld08 Private Function Klaar() As Boolean ' tekst van de titelbalk: Const titel As String = "Getallen van 1 t/m 10" ' knoppen en pictogram (Ja- en Nee-knop, vraagteken): Const knop As Integer = vbYesNo + vbQuestion ' tekst van de vraag: Const vraag As String = "Nog een?" ' het resultaat van MsgBox: Dim antwoord As Integer antwoord = MsgBox(vraag, knop, titel) Klaar = (antwoord = vbNo) End Function ' Klaar Toelichting De herhaling wordt nu uitgevoerd totdat de waarde van de functie Klaar True is. Deze functie heeft geen argumenten. In de functie zijn drie constanten gedeclareerd voor de titel, de knoppen en de tekst van de vraag in MsgBox. De functiewaarde is antwoord = vbNo
73
Programmeren VBA 1
Hoofdstuk 5
dus True als het op de Nee-knop is gedrukt, en anders False. Dit is een handige manier om een programma te laten vragen of er nog een bewerking of berekening nodig is.
In de voorafgaande voorbeelden heb je kunnen zien hoe je procedures en functies op een nuttige manier kunt gebruiken. De voordelen van het gebruik van procedures en functies zijn: • Je kunt programma’s beter structureren. In plaats van één groot programma maak je kleinere logische eenheden van ‘uitvoerbare’ instructies, die je oproept wanneer je ze nodig hebt. • Je kunt het aantal variabelen (en constanten) in zekere zin beperken door alleen de variabelen die in de hoofdprocedure voorkomen, op moduleniveau te declareren. Binnen ‘kleinere’ procedures of functies kun je dan locale variabelen declareren. • Door het gebruik van argumenten kun je duidelijk laten zien welke variabelen bij een procedure betrokken zijn. Door argumenten via waarde door te geven kun je voorkomen dat variabelen onbedoeld van waarde veranderen (neveneffecten). • Door het gebruik van argumenten kun je ook meer algemene procedures, zoals in- en uitvoerprocedures, maken. Je kunt die vervolgens naar behoefte oproepen. Je moet er dan wel voor zorgen dat je niet te veel ‘functionaliteit’ is zo’n procedure stopt. • Je kunt allerlei details binnen een procedure of functie ‘verbergen’. Daarmee voorkom je dat een onoverzichtelijk geheel ontstaat. In feite gebruik je dit principe al de hele tijd bij het aanroepen van de in- en uitvoerprocedures die met pv beginnen. Hier is sprake van ‘abstractie’. Kijk voor meer informatie in de Visual Basic-editor: Help Index (onder andere): • procedures, Sub: een procedure Sub schrijven • procedures, Function: een procedure Function schrijven • procedures, declareren • procedures, argumenten • procedures, instructies • procedures, variabelen en constanten declareren: de levensduur van variabelen • bereik op procedureniveau: bereik en zichtbaarheid
Bij de zichtbaarheid van namen (van constanten, variabelen, procedures en functies) moet je om het volgende denken. Elk Word-document wordt beschouwd als een project. Elk project waarin macro’s voorkomen bestaat uit een of meer modules, waarbij een module bestaat uit declaraties en procedures/functies. Bovendien is elk document, dus elk project, gebaseerd op een documentsjabloon, of kortweg sjabloon. Onze projecten zijn gebaseerd op het standaardsjabloon normal.dot. Daarin zijn alle namen gedeclareerd die beginnen met pv, dus onder andere de in- en uitvoerprocedures. Deze zijn dus in alle projecten zichtbaar die gebaseerd zijn op dit sjabloon. Eventuele hulpprocedures in dit sjabloon zijn gedeclareerd als Private, zodat ze niet buiten het sjabloon zichtbaar zijn. Het project waarin de voorbeelden van dit hoofdstuk zijn opgenomen, bestaat uit een aantal modules. Elke module bestaat daarin uit een declaratie van constanten en variabelen op moduleniveau, een ‘hoofdprocedure’ zonder argumenten (niet Private), en hulpprocedures (wel Private). Door deze constructie zijn de namen van de hoofdprocedures wel zichtbaar bij het oproepen van de macro’s vanuit het document (Extra, Macro, Macro’s of Alt+F8), en die van de hulpprocedures niet. Bovendien zijn de hulpprocedures in de verschillende modules onzichtbaar voor elkaar. De locale variabelen en constanten van elke procedure zijn tenslotte alleen zichtbaar binnen de procedure/functie waarin ze gedeclareerd zijn. Als er een globale variabele (een variabele op moduleniveau) is met dezelfde naam als de locale variabele, dan is de globale variabele binnen de procedure onzichtbaar. Voor de zichtbaarheid heb je dus de volgende hiërarchie: 74
Programmeren VBA 1
Hoofdstuk 5
1 sjabloon (normal.dot) 2 project (verzameling modules) 3 module (verzameling declaraties en procedures) 4 procedure/functie Bij de voorbeelden van dit hoofdstuk is voor deze structuur gekozen omdat ze op zichzelf staan. Elk voorbeeld demonstreert – los van de andere – een bepaald aspect van Visual Basic, dus zo’n opbouw ligt voor de hand. In andere situaties zou je kunnen kiezen voor een andere structuur. Als je met een groter project bezig bent, kun je overwegen om alle procedures die een bepaald aspect van het project vertegenwoordigen, bijvoorbeeld alles wat met invoer te maken heeft, in één module onder te brengen. Daarmee houd je het overzicht, en je weet precies waar je iets moet zoeken. Je zult er dan wel voor moeten zorgen dat de betreffende procedures op de juiste manier buiten de module zichtbaar zijn.
75
Programmeren VBA 1
Hoofdstuk 5
Opgaven In deze opgaven (vanaf opgave 2) moet je gebruik maken van procedures en functies, met argumenten en de juiste manier van doorgeven. Het is verstandig om regelmatig het commando Foutopsporing compileren te geven, om taalfouten vroegtijdig te onderscheppen. Opgave 1 Wat is de uitvoer van het onderstaande programma? Option Explicit ' alle variabelen moeten gedeclareerd worden ' oefening argumenten, lokale variabelen ' variabelen op module-niveau; Dim m As Integer, n As Integer Sub Opgave01() pvZetLTabs 4: pvZetLTabs 6 m = 2: n = 9 pvUitvoer "Hoofdprogramma:", vbTab: Uit m, n Een m pvUitvoer "Hoofdprogramma:", vbTab: Uit m, n Twee n pvUitvoer "Hoofdprogramma:", vbTab: Uit m, n Drie m pvUitvoer "Hoofdprogramma:", vbTab: Uit m, n Vier m, n pvUitvoer "Hoofdprogramma:", vbTab: Uit m, n pvWisTabs End Sub ' Opgave01 Private Sub Een(ByVal m As Integer) m = 12: n = 19 pvUitvoer "Procedure A:", vbTab: Uit m, n End Sub ' Een Private Sub Twee(ByRef n As Integer) Dim m As Integer m = 22: n = 29 pvUitvoer "Procedure B:", vbTab: Uit m, n End Sub ' Twee Private Sub Drie(ByRef m As Integer) Dim m1 As Integer, n1 As Integer m = 32: n1 = 39 pvUitvoer "Procedure C:", vbTab: Uit m, n1 End Sub ' Drie Private Sub Vier(ByVal m As Integer, ByVal n As Integer) m = 42: n = 49 pvUitvoer "Procedure D:", vbTab: Uit m, n End Sub ' Vier Private Sub Uit(ByVal m As Integer, ByVal n As Integer) pvUitvoer "m = ", m, vbTab, "n = ", n, "." pvNR End Sub ' Uit
76
Programmeren VBA 1
Hoofdstuk 5
Opgave 2 a. Schrijf een programma om de tafel van vermenigvuldiging van een getal n te laten schrijven, zoals in opgave 2 van hoofdstuk 3. Schrijf daarvoor een procedure Invoer voor de invoer van n, en een procedure Tafel voor het schrijven van de tafel van n. Voorzie de procedures van de juiste argumenten met zorg voor het juiste doorgeven via waarde of verwijzing. b. Gebruik de procedure Tafel uit onderdeel a om de tafels van 1 t/m 20 te laten schrijven, met steeds een lege regel ertussen. Opgave 3 Schrijf een programma voor de berekening van de oppervlakte van een driehoek als de zijden gegeven zijn, zoals in opgave 1 van hoofdstuk 4. Maak een ‘hoofdprocedure’ die er zo uitziet (dus met een herhaling): Sub Opgave03() Do Invoer a, b, c UitvoerGegevens a, b, c OppKwadraat a, b, c, O2 Uitvoer O2, O Loop Until Klaar End Sub ' Opgave03 Schrijf de procedure Invoer en de overige procedures met de juiste argumenten. Zorg voor het op een passende manier doorgeven van de argumenten (via waarde of via verwijzing). Opgave 4 Schrijf een programma voor het delen van twee breuken. Gebruik daarbij voorbeeld 5 als model. Maak een herhalingsstructuur zoals in opgave 3, en zorg voor een passende tekst in het venstertje en in de titel ervan. Opgave 5 Complexe getallen zijn getallen van de vorm a + bi waarin a en b reële getallen zijn, en waarbij i2 = −1. Twee complexe getallen vermenigvuldig je als volgt: (a + bi)(c + d i) = ac + bci + ad i + bd i2 = ac + bci + ad i + bd ⋅ (−1) = ac + bci + ad i − bd = ac − bd + ad i + bci = (ac − bd) + (ad + bc)i. Dus als (a + bi)(c + d i) = p + q i dan is p = ac − bd en q = ad + bc. Schrijf daarvoor onder andere procedures InvoerComplexGetal en UitvoerComplexGetal voor het in- en uitvoeren van één complex getal. Gebruik voorbeeld 5 als model. Maak een herhalingsstructuur zoals in opgave 3, en zorg voor een passende tekst in het venstertje en in de titel ervan.
77
Programmeren VBA 1
Hoofdstuk 5
Opgave 6 In opgave 11 van hoofdstuk 4 heb je een programma geschreven om de arcsinus en de arccosinus van een getal x te laten berekenen. Gebruik dit om functies Asn (voor arcsinus) en Acs (voor arccosinus) te schrijven. Neem voor x en voor arcsin x Double-waarden. Test de functie door een tabel te laten maken waarin x loopt van –1 t/m 1 met stappen van 0,1. Controleer de uitkomsten met je rekenmachine. Opgave 7 Schrijf een programma met als invoer de zijden a, b en c van een driehoek, en als uitvoer de zijden en de hoeken alfa, beta en gamma van de driehoek in graden. Gebruik daarbij de cosinusregel(s): b2 + c2 − a2 a2 = b2 + c2 − 2bc cos α ⇒ cos α = 2bc 2 + a2 − b2 c b2 = c2 + a2 − 2ca cos β ⇒ cos β = 2ca 2 a + b2 − c2 c2 = a2 + b2 − 2ab cos γ ⇒ cos γ = 2ab Maak daarvoor procedures Invoer, Berekening en Uitvoer, met de juiste argumenten en manier van doorgeven. Laat na elke berekening vragen of er nog een driehoek berekend moet worden. De procedure Invoer (van de drie zijden) moet een procedure InvoerZijde oproepen waarin één zijde wordt ingevoerd, en waarin wordt gecontroleerd of dit een positief getal is. Verder moet de procedure Invoer controleren of met de waarden van a, b en c wel een driehoek mogelijk is. Dit is niet het geval als a + b ≤ c, b + c ≤ a of c + a ≤ b. Gebruik de functies Acs, en dus ook Asn, uit opgave 6. Houd er rekening mee dat die functies een hoek in radialen geven. Opgave 8* Schrijf een programma voor het vermenigvuldigen van twee breuken. De breuken moeten nu ook vereenvoudigd worden. Gebruik voorbeeld 5 als model, en gebruik de functie ggd uit voorbeeld 7. Laat na elke berekening vragen of er nog een vermenigvuldiging uitgevoerd moet worden. Opgave 9* Schrijf een programma om een matrix a te sorteren, zoals in opgave 13 van hoofdstuk 4. Schrijf daarvoor een procedure InvoerAantal om het aantal elementen n van de matrix in te voeren, een procedure Initialisatie om te declareren dat a bestaat uit n elementen, een procedure VulMatrix om de matrix a te vullen met n getallen (willekeurig getallen van 0 t/m 99), een procedure SchrijfMatrix om de getallen a(1) t/m a(n) te schrijven, en een procedure SorteerMatrix om de matrix te sorteren van klein naar groot. Geef de matrices als argumenten via verwijzing door, dus ook in de procedure SchrijfMatrix.
78
Programmeren VBA 1
Hoofdstuk 5
Opgave 10* Schrijf een programma voor het vermenigvuldigen van een 2-dimensionale k×m-matrix a met een m×n-matrix b, zoals in opgave 11 van hoofdstuk 3. Het produkt is dan de m×n-matrix c. Schrijf daarvoor de volgende procedures. naam argumenten doel de afmetingen invoeren InvoerAfmetingen k, m, n x(), p, q de afmetingen p×q van de matrix declareren Initialisatie x(), p, q een p×q-matrix x() vullen VulMatrix a(), b(), c(), k, m, n Produkt het produkt c = a ⋅ b berekenen x(), p, q een p×q-matrix x() schrijven SchrijfMatrix Laat de matrices vullen met willekeurige getallen van −2 t/m 2. Geef alle matrices als argumenten door via verwijzing. Laat na elke berekening vragen of er nog een moet worden uitgevoerd. Opgave 11* Schrijf een programma dat van een complex getal x + iy het argument ϕ berekent. Dit argument is hetzelfde als de ‘poolhoek’ ϕ, in radialen met −π < ϕ ≤ π, van het punt P(x, y) zoals in opgave 12 van hoofdstuk 4. Het is voor x ≥ 0 (ook) te berekenen uit y y sin ϕ = 2 dus ϕ = arcsin 2 . 2 2 x +y x +y Voor x < 0 moet je dat nog aanpassen; als y ≥ 0 moet je π optellen bij de gevonden waarde, en voor y < 0 moet je π ervan aftrekken. Schrijf daarvoor een functie Arg met argumenten x en y die deze hoek oplevert. Gebruik de functie Asn uit opgave 6. Gebruik de procedures InvoerComplexGetal en UitvoerComplexGetal uit opgave 5 om een complex getal in- en uit te voeren. Gebruik zo nodig een herhaling zoals in opgave 3. Test het programma met complexe getallen uit alle kwadranten, dus ook het tweede en derde, en controleer de uitkomsten met je rekenmachine. Test ook getallen met x = 0 of y = 0. Opgave 12* Elk complex getal z = a + bi is te schrijven als z = r(cos ϕ + i sin ϕ), waarin ϕ het argument van z is, en r de absolute waarde of modulus van z: r = a2 + b2 . Met deze voorstelling kun je machten van z uitrekenen, want z n = r n (cos nϕ + i sin nϕ). Je moet dus de absolute waarde tot de n-de macht nemen, en het argument met n vermenigvuldigen. Schrijf een programma met als invoer het complexe getal z = a + bi en de exponent n, en als uitvoer de macht z n = p + qi. Schrijf daarvoor een procedure Macht met als argumenten a, b, n, p en q. Gebruik ook de procedures InvoerComplexGetal en UitvoerComplexGetal uit de opgaven 5 en 12. Als controle zou je het complexe getal 1 = 1+0 ⋅ i n keer met z = a + bi kunnen laten vermenigvuldigen, zoals in opgave 5.
79
Programmeren VBA 1
80
Programmeren VBA 1
Hoofdstuk 6
6. Opdrachten In de voorafgaande hoofdstukken heb je kennis gemaakt met de elementaire bouwstenen van het procedureel programmeren om (voornamelijk) wiskundige problemen op te laten lossen door een reeks instructies: een programma. De werking van zo’n programma berust op het toewijzen van waarden aan variabelen, om bij een bepaalde begintoestand de gevraagde eindtoestand te bereiken. Het programma is daarbij gestructureerd in logische eenheden, zoals initialisatie, invoer, berekening of verwerking, en uitvoer: de procedures. Deze worden opgeroepen in een ‘hoofdprocedure’. Daarin is bijna altijd een herhalingsstructuur opgenomen om te vragen of er nog een berekening of bewerking gewenst wordt. Hierna volgen enkele ‘grotere’ programmeeropgaven die je nu moet kunnen oplossen. Bij het oplossen ervan zul je je onder andere de volgende vragen moeten stellen. • Uit welke logische onderdelen (procedures) bestaat het programma? Welke argumenten heeft elke procedure nodig, en hoe moeten die worden doorgegeven (via waarde of via verwijzing)? • Welke constanten en variabelen zijn er nodig, en van welk type? Welke constanten en variabelen moeten gedeclareerd worden op moduleniveau (liefst zo weinig mogelijk)? • Wat is de invoer en de uitvoer van het programma, en in welke vorm? Meestal zijn daarvoor al wat aanwijzingen gegeven. • Welk algoritme (welk ‘recept’) is nodig om van de begintoestand of de invoer te komen tot de eindtoestand of de uitvoer? Ook daarvoor zijn aanwijzingen gegeven. • Zijn er herhalingsstructuren nodig, en zo ja, welk soort? Zijn er keuzestructuren nodig? Om een duidelijk en goed werkend programma te maken zul je dus eerst de grote lijnen moeten ‘ontwerpen’, en pas daarna de details moeten invullen. Dat betekent dat je een korte en overzichtelijke ‘hoofdprocedure’ maakt, waarin je de verdere procedures oproept. Verder is het verstandig om constanten, variabelen en procedures duidelijke namen te geven. Als richtlijn voor het geven van namen kun je nemen: • Gebruik voor procedures, die in het algemeen een bepaalde actie uitvoeren, een werkwoord. • Gebruik voor constanten, variabelen en functies een zelfstandig naamwoord. • Gebruik voor Boolean variabelen, die een bepaalde eigenschap aangeven, een bijvoeglijk naamwoord. Gebruik zo nodig hoofdletters om een naam die uit meer dan één woord bestaat, duidelijker te maken; een naam als TelOp is begrijpelijker dan Telop. Zorg ook voor een duidelijke opmaak van de programmatekst, door niet te lange regels te gebruiken (zo nodig met _ splitsen in meer regels), en door bij keuze- en herhalingsstructuren steeds in te springen (ik gebruik altijd 2 spaties), zeker als binnen zo’n structuur er weer een voorkomt. Probeer daarbij ook ‘triviale’ fouten, zoals For zonder Next, te voorkomen door bij elk begin van zo’n constructie meteen ook het einde te schrijven, en pas daarna de rest ertussen te zetten. Schrijf dus in één keer For i = 1 To 10 Next i of If a > 1 Then Else End If en voeg daarna de ontbrekende instructies (inspringend) ertussen.
81
Programmeren VBA 1
Hoofdstuk 6
Wees kritisch op het gebruik van keuzestructuren. Vraag je eerst af of de keuzestructuur eigenlijk wel nodig is. Vaak worden keuzestructuren ten onrechte gebruikt om ‘ontwerpfouten’ weg te poetsen. Als je ervan overtuigd bent dat de keuzestructuur nodig is, gebruik die dan op de juiste manier. Als bijvoorbeeld de procedure een splitsing in drieën kent, afhankelijk van a > 1, a < 1 of a = 1, schrijf dan niet If a > 1 Then ... End If If a < 1 Then ... End If If a = 1 Then ... End If maar If a > 1 Then ... ElseIf a < 1 Then ... Else ... End If of Select Case a Case Is > 1 ... Case Is < 1 ... Case Else ... End Select Terwijl je het programma aan het schrijven bent (dus na de ‘ontwerpfase’), moet je regelmatig controleren of er geen ‘taalfouten’ in zitten. Je doet dat in de editor met het commando Foutopsporing ... compileren. Herstel de taalfouten zo snel mogelijk. Verder zul je moeten testen of je programma inderdaad doet wat het moet doen. Als je erg grondig bent, test je elke procedure apart. Verder kijk je of het programma bij een bepaalde invoer ook de uitvoer geeft die je bij een berekening met de hand zou krijgen. Bij ‘vreemde’ resultaten kun je het beste het programma stap voor stap laten uitvoeren, met de commando’s Foutopsporing Stap (F8) en Foutopsporing Stap over (Shift+F8), en kijken of de variabelen de waarden hebben die je op dat moment zou mogen verwachten. Tenslotte nog een welgemeend advies. Probeer het niet in een keer te mooi te maken. Zorg er eerst voor dat je een programma hebt dat aan de specificaties voldoet. Ga eventueel pas daarna verder verfraaien. Schrijf zo nodig de briljante invallen die je in de ontwerpfase krijgt, op voor later gebruik.
82
Programmeren VBA 1
Hoofdstuk 6
Opgave 1 Schrijf een programma voor het oplossen van stelsels van twee lineaire vergelijkingen met twee onbekenden x en y a1 x + b1 y = c1 a2 x + b2 y = c2 Laat daarvoor eerst de ‘determinant’ ∆ = a1b2 − a2b1 berekenen. Voor ∆ ≠ 0 is de oplossing ∆1 ∆2 x= , y= ∆ ∆ met ∆1 = c1b2 − c2b1, ∆2 = a1c2 − a2c1. Als ∆ = 0 en ∆1 = 0, dan is het stelsel afhankelijk; als ∆ = 0 en ∆1 ≠ 0, dan is het stelsel strijdig. Invoer van het programma zijn de coëfficiënten a1, b1, c1, a2, b2 en c2. Uitvoer zijn ook deze coëfficiënten, en de oplossing of de mededeling dat het stelsel afhankelijk dan wel strijdig is. Laat het programma na afloop vragen of er nog een stelsel vergelijkingen moet worden opgelost. Schrijf in ieder geval aparte procedures voor de in- en uitvoer van de coëfficiënten, en voor het oplossen van het stelsel. Opgave 2 Schrijf een programma voor het oplossen van de tweedegraads vergelijking ax2 + bx + c = 0. Voor a ≠ 0 wordt de oplossing gegeven door de abc-formule −b ± D x= met D = b2 − 4ac. 2a Als D > 0, dan zijn er twee reële oplossingen; als D > 0, dan is er één reële oplossing; als D < 0, dan zijn er geen reële oplossingen, maar wel twee complexe −b −D x = 2a ± 2a i met i2 = −1. Voor a = 0 is de vergelijking bx + c = 0. Deze is op de gewone manier op te lossen; kijk wel uit met b = 0. Invoer van het programma zijn de coëfficiënten a, b en c. Uitvoer zijn ook deze coëfficiënten, en de oplossing, of de mededeling dat er geen oplossing is of dat elke x voldoet. Laat het programma na afloop vragen of er nog een vergelijking moet worden opgelost. Schrijf in ieder geval aparte procedures voor de in- en uitvoer van de coëfficiënten, en voor het oplossen van de tweedegraads (a ≠ 0) en de eerstegraads (a = 0) vergelijking. Opgave 3 Schrijf een programma om een positief geheel getal n (type Long, maximaal 2.147.483.647) te ontbinden in priemfactoren: n = p1e1 ⋅ p2e2 … pkek Laat het programma na elke ontbinding vragen of er nog een getal ontbonden moet worden. Bedenk bij het ontbinden het volgende. • Als n geen priemgetal is, dan is n = a ⋅ b waarbij a ≤ n of b ≤ n . Je hoeft dus alleen de (priem)factoren f ≤ n te onderzoeken.
83
Programmeren VBA 1
Hoofdstuk 6
• Om te zien of n deelbaar is door f kijk je natuurlijk of
n Mod f = 0 Zolang dit het geval is, kun je n door f delen. • Je moet in principe alle priemfactoren p ≤ n proberen. Dat zou betekenen dat je ook zou moeten kijken of een eventuele factor wel een priemgetal is. Een alternatief is om alle factoren f ≤ n te proberen, maar dat zijn er veel te veel. Je zou je bijvoorbeeld kunnen beperken tot de factor 2, en verder alle oneven factoren. Nog iets subtieler is het om de factoren 2, 3 en 5 te proberen, en dan de factor afwisselend met stappen van 2 en 4 te laten toenemen. Je slaat daarmee de veelvouden van 3 over: 2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37, 41, … De factoren 25, 35 enz. die geen priemgetal zijn, leveren natuurlijk geen succes meer op als je alle factoren 5 (of 7 enz.) al weggedeeld hebt. Het belangrijkste is dus een procedure Deel waarin je kijkt of je n door f kunt delen, en dit ook doet zolang dat kan. Deze procedure zal ook moeten schrijven, namelijk de factor en de bijbehorende exponent. Zo zou de uitvoer kunnen zijn: 8928 = 2^5 3^2 31 660395736 = 2^3 3^2 7^3 11^2 13 17 2017554 = 2 3 7 11^2 397 138418267 = 13^2 17 48179 98947 = 98947 Invoer van het programma is het getal n; uitvoer is de ontbinding van n. Laat het programma na afloop vragen of er nog een getal moet worden ontbonden. Opgave 4 Schrijf een programma voor het oplossen van de goniometrische vergelijking a cos x + b sin x = c. Deze oplossing verloopt als volgt. Bereken eerst A = a2 + b2 en deel de vergelijking door A: b c a A cos x + A sin x = A Dit is nu te schrijven als c b a cos (x + ϕ) = A met cos ϕ = A en sin ϕ = − A of c cos (x + ϕ) = cos α met cos α = A De oplossingen gaat nu zo verder: x + ϕ = α + k ⋅ 2π ∨ x + ϕ = −α + k ⋅ 2π; x = α − ϕ + k ⋅ 2π ∨ x = −α − ϕ + k ⋅ 2π. Dit gaat natuurlijk alleen goed als c −1 ≤ cos α ≤ 1 of −1 ≤ A ≤ 1 dus c2 c ≤ 1 ⇔ ≤ 1 ⇔ c2 ≤ a2 + b2 ⇔ a2 + b2 − c2 ≥ 0. −1 ≤ 2 a2 + b2 a + b2 Het getal D = a2 + b2 − c2 heet ook wel de discriminant van de vergelijking a cos x + b sin x = c.
84
Programmeren VBA 1
Hoofdstuk 6
Daarvoor geldt: Als D > 0, dan zijn er twee series oplossingen. Als D = 0, dan is er een serie oplossingen. Als D < 0, dan zijn er geen oplossingen. Invoer van het programma zijn de coëfficiënten a, b en c. Uitvoer zijn ook deze coëfficiënten, en de serie(s) oplossingen, of de mededeling dat er geen oplossing is. Laat het programma na afloop vragen of er nog een vergelijking moet worden opgelost. Schrijf in ieder geval aparte procedures voor de in- en uitvoer van de coëfficiënten, en voor de drie oplossingsmogelijkheden. Je mag ervan uitgaan dat de functies Asn (voor arcsinus) en Acs (voor arccosinus) beschikbaar zijn, en dat π gedefinieerd als Pi (in WiskFunc.bas). Opgave 5 Schrijf een programma voor het berekenen van de repeterende breuk die je krijgt als je twee positieve gehele getallen deelt. 167
Zo is bijvoorbeeld 108 = 1,54629629629… Je vindt dat (zie ook opgave 10 van hoofdstuk 3) met een ‘staartdeling’: 108 / 1 6 7 \ 1,546296 1 0 8 5 9 0 5 4 0 5 0 0 4 3 2 6 8 0 6 4 8 3 2 0 2 1 6 1 0 4 0 9 7 2 6 8 0 6 4 8 3 2 Als m de teller van de breuk is, en n de noemer, dan is het deel voor de komma gelijk aan m \ n In het voorbeeld: 167 \ 108 = 1 Je houdt dan nog een rest over, namelijk m Mod n In het voorbeeld: 167 Mod 108 = 59 Met die rest herhaal je steeds opnieuw: • vermenigvuldig de rest met 10: 590 • bereken het quotiënt: 590 \ 108 = 5 noteren: 5 • bereken de rest: 590 Mod 108 = 50 Je kunt dan ook zien wanneer de breuk gaat repeteren, namelijk als er een rest voorkomt die al eerder is voorgekomen. In een schema:
85
Programmeren VBA 1
Hoofdstuk 6
stap nr. teller noemer quotiënt rest 0 167 108 1 59 1 590 108 5 50 2 500 108 4 68 ← 3 680 108 6 32 4 320 108 2 104 5 1040 108 9 68 ← 6 680 108 6 32 Dus 167 108 = 1,54629629629629629629629629… of 167 108 = 1,54(629) waarbij het repeterende deel tussen haakjes staat. Omdat rest nummer 2 gelijk is aan rest nummer 5 bestaat het niet-repeterende deel uit 2 cijfers, en het repeterende deel uit 3 cijfers. Invoer van programma zijn de twee positieve gehele getallen m en n (Integer), en uitvoer deze getallen en de repeterende breuk in de vorm 1,54(629) zoals hierboven, dus met het repeterende deel tussen haakjes. Gebruik een matrix om de resten te ‘onthouden’ zodat je ze met de eerdere resten kunt vergelijken. Je mag ervan uitgaan dat de lengte van het repeterende stuk niet langer is dan n, en de lengte van het niet-repeterende stuk hoogstens 15. Opgave 6 Schrijf een programma voor het maken van een tabel (zonder lijntjes) met priemgetallen zoals hieronder. 1 2 3 4 5 6 7 8 9 10 0 2 3 5 7 11 13 17 19 23 29 1 31 37 41 43 47 53 59 61 67 71 2 73 79 83 89 97 101 103 107 109 113 3 127 131 137 139 149 151 157 163 167 173 4 179 181 191 193 197 199 211 223 227 229 5 233 239 241 251 257 263 269 271 277 281 6 283 293 307 311 313 317 331 337 347 349 7 353 359 367 373 379 383 389 397 401 409 8 419 421 431 433 439 443 449 457 461 463 9 467 479 487 491 499 503 509 521 523 541 De eerste rij bestaat daarbij uit de getallen 1 t/m 10, en de eerste kolom geeft de tientallen aan. Zo kun je hier bijvoorbeeld zien dat het 86e priemgetal het getal 443 is. Een getal n groter dan 1 is een priemgetal als het alleen door 1 en door zichzelf deelbaar is. Je moet dus onderzoeken of het getal deelbaar is door kleinere getallen k: n Mod k = 0 De grootst mogelijke deler van n, behalve n zelf, is natuurlijk n . Invoer van het programma is het aantal priemgetallen in de tabel, en uitvoer is de tabel met eerste rij en kolom zoals hierboven. Gebruik een Boolean functie Priem die aangeeft of een geheel getal n een priemgetal is.
86
Programmeren VBA 1
Hoofdstuk 6
Opgave 7 Schrijf een programma voor het berekenen van primitieve drietallen van Pythagoras zoals (3, 4, 5). Een drietal van Pythagoras is een drietal positieve gehele getallen (a, b, c) waarvoor geldt a2 + b2 = c2. Elk veelvoud van een drietal van Pythagoras is er weer een, zoals (6, 8, 10). Een primitief drietal is een drietal dat geen veelvoud van een ander is. De grootste gemene deler van de drie getallen is dan 1. Voor elke positieve gehele waarde van s en t met s > t levert de combinatie a = s2 − t 2, b = 2st, c = s2 + t 2 een drietal van Pythagoras op. Als s en t allebei even getallen zijn, dan zijn a, b en c dat ook, en is het drietal dus niet primitief. Dat geldt ook als s en t allebei oneven zijn. Je hoeft dus alleen de volgende combinaties van s en t te onderzoeken: s = 2 en t = 1: (3, 4, 5); s = 3 en t = 2: (5, 12, 13); s = 4 en t = 1: (15, 8, 17) of t = 3: (7, 24, 25); s = 5 en t = 2: (21, 20, 29) of t = 4: (9, 40, 41); s = 6 en t = 1, t = 3 of t = 5; s = 7 en t = 2, t = 4 of t = 6; s = 8 en t = 1, t = 3, t = 5 of t = 7; s = 9 en t = 2, t = 4, t = 6 of t = 8; enz. Bijvoorbeeld bij s = 9 en t = 6 zou je dan het niet-primitieve drietal (45, 72, 117) vinden. Om te zien of een drietal primitief is moet je de ggd van gehele getallen kunnen berekenen. Voor twee gehele getallen m en n gebruik je daarvoor het algoritme van Euclides, zoals in voorbeeld 7 van hoofdstuk 5 (nu zonder alle uitvoerinstructies): Private Function ggd(ByVal m As Long, ByVal n As Long) _ As Long ' ggd: algoritme van Euclides ' de rest bij deling: Dim r As Long r = m Mod n Do While r > 0 m = n: n = r r = m Mod n Loop ggd = n End Function ' ggd De ggd van drie getallen a, b en c kun je dan bijvoorbeeld berekenen door eerst te berekenen m = ggd(a, b), en daarna ggd(m, c). De drietallen moeten ‘gesorteerd’ verschijnen. Voor s = 4 en t = 1 vind je (15, 8, 17). Dat moet dus veranderen in (8, 15, 17). Invoer van het programma is het aantal primitieve drietallen (je weet dus niet van te voren wat de eindwaarden van s en t zullen zijn). Uitvoer zijn de gesorteerde drietallen. Schrijf in ieder geval aparte procedures voor in- en uitvoer, en een procedure om een drietal te laten verschijnen. Een procedure om een drietal te sorteren is natuurlijk ook handig.
87
Programmeren VBA 1
88
Programmeren VBA 1
Appendix
Appendix Om zelf (thuis) aan de slag te gaan met deze cursus heb je een aantal dingen nodig. Allereerst natuurlijk een PC waarop je Microsoft Office 97 of 2000 – of alleen Word 97 of 2000 – moet installeren. Deze cursus werkt niet met eerdere versies van Word, zoals Word 95 of 6.0. Verder zul je de macro’s voor in- en uitvoer moeten installeren die in deze cursus worden gebruikt. Bovendien is het handig om ook ‘Help voor Visual Basic’ te installeren, zodat je het naslagwerk bij de hand hebt. ‘Help voor Visual Basic’ wordt niet standaard geïnstalleerd als je Office installeert. Tenslotte is het natuurlijk plezierig om de voorbeelden bij de hand te hebben. Dit zijn gewone Word-bestanden die je op een geschikte plaats op je harde schijf moet zetten. Ik gebruik daarvoor in de map ‘Mijn Documenten’ een map ‘Word’ en daarin een map ‘Pv1’ voor al mijn Pv1-bestanden. De bestanden die bij deze cursus nodig zijn: Pv1-1v.doc voorbeelden hoofdstuk 1 Pv1-1o.doc opgaven hoofdstuk 1 Pv1-2v.doc voorbeelden hoofdstuk 2 Pv1-2o.doc opgaven hoofdstuk 2 Pv1-3v.doc voorbeelden hoofdstuk 3 Pv1-3o.doc opgaven hoofdstuk 3 Pv1-4v.doc voorbeelden hoofdstuk 4 Pv1-4o.doc opgaven hoofdstuk 4 Pv1-5v.doc voorbeelden hoofdstuk 5 Pv1-5o.doc opgaven hoofdstuk 5 pvInUit.bas macro’s in- en uitvoer WiskFunc.bas macro’s wiskundige functies Als je Office nog niet geïnstalleerd hebt, moet je dat eerst doen door het programma Setup op de Office-CD uit te voeren. Let er daarbij op dat je ook ‘Help voor Visual Basic’ installeert. Als je Office al geïnstalleerd hebt, maar ‘Help voor Visual Basic’ nog niet, dan moet je het installatieprogramma opnieuw uitvoeren. Als je Word 2000 gebruikt moet je misschien ook nog de instelling van de beveiliging bij macro’s veranderen. Je doet dat met het commando Extra, Macro, Beveiliging. Stel daarbij het Beveiligingsniveau in op Gemiddeld. Om de macro’s voor in- en uitvoer te installeren heb je het bestand pvInUit.bas nodig. Je kunt dit van mij krijgen, rechtstreeks of door een mailtje te sturen (
[email protected]). Niet echt nodig, maar wel handig, is het bestand WiskFunc.bas, waarin wat wiskundige functies gedefinieerd zijn die niet standaard in VBA zitten. De installatie gaat als volgt: 1 Start Word en sluit Document 1. Als Word al gestart is: sluit alle documenten. 2 Geef het commando: Extra, Macro, Visual Basic-editor (Alt+F11). 3 Geef zo nodig het commando: Beeld, Projectverkenner (Ctrl+R). 4 Selecteer in de projectverkenner het project Normal (als het goed is zijn er geen andere projecten). 5 Geef het commando: Bestand, Bestand importeren (Ctrl+M). Selecteer pvInUit.bas. 6 Geef het commando: Bestand, Normal opslaan (Ctrl+S). Herhaal de stappen 2 t/m 6 zo nodig voor het bestand WiskFunc.bas. Zelf heb ik ook nog het een en ander gewijzigd in de instellingen van de VBA-editor. Om deze veranderingen uit te voeren moet je dit doen: 1 Start zo nodig Word. 2 Geef het commando: Extra, Macro, Visual Basic-editor (Alt+F11). i
Programmeren VBA 1
Appendix
3 Geef in de VBA-editor het commando: Extra, Opties. 4 Bij Editor staat overal een ‘vinkje’ (dus ook bij Variabelen declareren vereist); Tabstopbreedte: 2 5 Bij Opmaak in editor: Programmacodekleuren, Sleutelwoord: ik heb gekozen voor een iets feller blauw. 6 Bij Dokken: ‘vinkje’ bij Venster Direct, Venster Lokale variabelen, Projectverkenner, Venster Eigenschappen 7 Sluit af met OK. De standaardinstellingen van Word bevallen mij ook niet helemaal. Omdat ik niet goed word van die irritante rode en groene kronkellijntjes onder woorden heb ik de ‘spellings- en grammaticacontrole tijdens het typen’ uitgeschakeld (in Word: Extra, Opties, Spelling en grammatica). Bovendien heb ik de ‘WordPerfect Help’ uitgeschakeld (in Word: Extra, Opties, Algemeen: ‘vinkje’ weg bij Help voor WordPerfect-gebruikers). Verder vind ik het prettig als ik, in normale weergave, in de linkermarge van het Word-venster de namen van de opmaakprofielen kan zien (in Word: Extra, Opties, Weergave: Breedte van opmaakgebeid: 2 cm). Tenslotte kun je overwegen om de resolutie van je monitor hoger in te stellen om meer informatie tegelijkertijd in beeld te hebben. Doe dit via Start, Instellingen, Configuratiescherm, en dubbelklik op Beeldscherm. Kies bij Instellingen een bruikbare resolutie, eventueel ten koste van het aantal kleuren. Mijn persoonlijke ervaring is dat op een 15" monitor de instelling ‘1024 bij 768 pixels’ nog net werkt, en op een 17" monitor ‘1280 bij 1024 pixels’. Hierna volgen de macro’s uit de modules pvInUit en WiskFunc (najaar 2000). Verwacht hier geen staaltjes van hoogstaand programmeerwerk – soms doe ik dingen die ik mijn studenten verbied. Het zijn vrij simpele macro’s, die redelijk werken; ze zijn niet uitgebreid getest. Ik heb ze gemaakt door in Word 97 macro’s op te nemen, en daarin de wijzigingen aan te brengen die mij nodig leken.
ii
Programmeren VBA 1
Appendix
De module pvInUit. ' Programmeren VBA: invoer, uitvoer, Tabs Option Explicit ' alle variabelen moeten gedeclareerd worden '-------------------------------------' invoer en uitvoer '-------------------------------------Sub pvInvoer(tekst As String, x) ' lees de ingevoerde tekst of (numerieke) waarde Dim hulp As String hulp = InputBox(tekst, "Invoer") If IsNumeric(x) Then If hulp = "" Then x = 0 Else x = CDbl(hulp) Else x = hulp End If End Sub ' pvInvoer Sub pvUitvoer(arg01, Optional arg02, _ Optional arg03, Optional arg04, _ Optional arg05, Optional arg06, _ Optional arg07, Optional arg08, _ Optional arg09, Optional arg10, _ Optional arg11, Optional arg12, _ Optional arg13, Optional arg14, _ Optional arg15) ' maximaal 15 argumenten (maar dat is makkelijk te veranderen) Uitvoer1 (arg01) If Not IsMissing(arg02) Then Uitvoer1 (arg02) If Not IsMissing(arg03) Then Uitvoer1 (arg03) If Not IsMissing(arg04) Then Uitvoer1 (arg04) If Not IsMissing(arg05) Then Uitvoer1 (arg05) If Not IsMissing(arg06) Then Uitvoer1 (arg06) If Not IsMissing(arg07) Then Uitvoer1 (arg07) If Not IsMissing(arg08) Then Uitvoer1 (arg08) If Not IsMissing(arg09) Then Uitvoer1 (arg09) If Not IsMissing(arg10) Then Uitvoer1 (arg10) If Not IsMissing(arg11) Then Uitvoer1 (arg11) If Not IsMissing(arg12) Then Uitvoer1 (arg12) If Not IsMissing(arg13) Then Uitvoer1 (arg13) If Not IsMissing(arg14) Then Uitvoer1 (arg14) If Not IsMissing(arg15) Then Uitvoer1 (arg15) End Sub ' pvUitvoer Private Sub Uitvoer1(x) ' Zet de tekst of de (numerieke) waarde in het document Dim hulp As String If TypeName(x) = "Boolean" Then If x Then hulp = "waar" Else hulp = "onwaar" ElseIf IsNumeric(x) Then hulp = CStr(x) Else hulp = x End If Selection.TypeText Text:=hulp End Sub ' Uitvoer1
iii
Programmeren VBA 1
Appendix
Sub pvUitvoerR(ByVal x As Double, ac As Integer) ' zet de (reële) waarde x in het document ' met ac cijfers achter de komma Dim hulp As String, notatie As String ' notatie: ‘door de gebruiker gedefinieerde notatie-expressie’ Dim i As Integer notatie = "0" If ac > 0 Then notatie = notatie & "." For i = 1 To ac notatie = notatie & "0" Next i hulp = Format(x, notatie) Selection.TypeText Text:=hulp End Sub ' pvUitvoerR Sub pvFout(tekst As String) ' Foutmelding Dim msg As Integer msg = MsgBox(tekst, vbOKOnly + vbExclamation, "Fout") End Sub ' pvFout '-------------------------------------' nieuwe regel, pagina, backspace '-------------------------------------Sub pvNR(Optional n As Integer = 1) ' overgang op nieuwe regel (alinea) Dim i As Integer For i = 1 To n Selection.TypeParagraph Next i End Sub ' pvNR Sub pvNP(Optional n As Integer = 1) ' overgang op nieuwe pagina Dim i As Integer For i = 1 To n Selection.InsertBreak Type:=wdPageBreak Next i End Sub ' pvNP Sub pvBS(Optional n As Integer = 1) ' backspace Dim i As Integer For i = 1 To n Selection.TypeBackspace Next i End Sub ' pvBS '-------------------------------------' Tabs '-------------------------------------Sub pvZetLTabs(cm As Single, Optional aantal As Integer = 1) ' zet een aantal linkertabs, om de zoveel cm Const MaxAantal As Byte = 63 Dim i As Integer If aantal > MaxAantal Then aantal = MaxAantal For i = 1 To aantal Selection.ParagraphFormat.TabStops.Add _ Position:=CentimetersToPoints(i * cm), _ Alignment:=wdAlignTabLeft, _ Leader:=wdTabLeaderSpaces Next i End Sub ' pvZetLTabs
iv
Programmeren VBA 1
Appendix
Sub pvZetRTabs(cm As Single, Optional aantal As Integer = 1) ' zet een aantal rechtertabs, om de zoveel cm Const MaxAantal As Byte = 63 Dim i As Integer ActiveDocument.DefaultTabStop = CentimetersToPoints(1.25) If aantal > MaxAantal Then aantal = MaxAantal For i = 1 To aantal Selection.ParagraphFormat.TabStops.Add _ Position:=CentimetersToPoints(i * cm), _ Alignment:=wdAlignTabRight, _ Leader:=wdTabLeaderSpaces Next i End Sub ' pvZetRTabs Sub pvZetCTabs(cm As Single, Optional aantal As Integer = 1) ' zet een aantal gecentreerde tabs, om de zoveel cm Const MaxAantal As Byte = 63 Dim i As Byte If aantal > MaxAantal Then aantal = MaxAantal For i = 1 To aantal Selection.ParagraphFormat.TabStops.Add _ Position:=CentimetersToPoints(i * cm), _ Alignment:=wdAlignTabCenter, _ Leader:=wdTabLeaderSpaces Next i End Sub ' pvZetCTabs Sub pvZetDTabs(cm As Single, Optional aantal As Integer = 1) ' zet een aantal decimale tabs, om de zoveel cm Const MaxAantal As Byte = 63 Dim i As Byte If aantal > MaxAantal Then aantal = MaxAantal For i = 1 To aantal Selection.ParagraphFormat.TabStops.Add _ Position:=CentimetersToPoints(i * cm), _ Alignment:=wdAlignTabDecimal, _ Leader:=wdTabLeaderSpaces Next i End Sub ' pvZetDTabs Sub pvWisTabs(Optional aantal As Integer) ' wis alle tabstops Selection.ParagraphFormat.TabStops.ClearAll End Sub ' pvWisTabs
v
Programmeren VBA 1
Appendix
De module WiskFunc. ' Wiskundige functies Option Explicit Public Const Pi As Double = 3.14159265358979 '-------------------------------------' cyclometrische functies ' (inverse goniometrische functies) '-------------------------------------Function Asn(ByVal x As Double) As Double ' arcsinus; géén controle op -1 <= x <= 1 Const HalfWortelTwee As Double = 0.707106781186548 If Abs(x) <= HalfWortelTwee Then Asn = Atn(x / Sqr(1 - x * x)) Else If x > 0 Then Asn = Pi / 2 - Atn(Sqr(1 - x * x) / x) Else Asn = -Pi / 2 - Atn(Sqr(1 - x * x) / x) End If End If End Function ' Asn Function Acs(ByVal x As Double) As Double ' arccosinus Acs = Pi / 2 - Asn(x) End Function ' Acs '-------------------------------------' hyperbolische functies '-------------------------------------Function Sinh(ByVal x As Double) As Double ' sinus hyperbolicus Sinh = (Exp(x) - Exp(-x)) / 2 End Function ' Sinh Function Cosh(ByVal x As Double) As Double ' cosinus hyperbolicus Cosh = (Exp(x) + Exp(-x)) / 2 End Function ' Cosh Function Tanh(ByVal x As Double) As Double ' tangens hyperbolicus Tanh = (Exp(x) - Exp(-x)) / (Exp(x) + Exp(-x)) End Function ' Tanh
vi