Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Voor de graduaatsopleiding hanteren we een vaste structuur voor de oefeningen.
Elke oefening begint met een unieke titel. Deze gebruik je voor de organisatie van je bestanden.
Eerst krijg je een omschrijving in tekst van wat de algemene bedoeling is van je programma. Deze bevat niet noodzakelijk elk detail, maar geeft je een big picture. Bijvoorbeeld:
In deze oefening maak je een echo. Dit is een programma dat tekst inleest vanaf het toetsenbord en deze tekst dan op het scherm toont.
Je hoeft niet altijd alle code zelf te schrijven. Code die je nog niet verondersteld wordt zelf te kunnen schrijven, of code die gewoon veel tijd in beslag neemt, krijg je. Bij de stukken code staat telkens de naam van het bestand waarin ze thuishoort. Commentaar bij de te wijzigen stukken code geeft aan welke aanpassingen je moet doen. Bijvoorbeeld:
Ten slotte worden één of meerdere voorbeelden getoond van hoe je programma zich gedraagt. In deze voorbeelden stellen regels die niet beginnen met >
iets voor dat op het scherm verschijnt. Regels die wel beginnen met >
stellen iets voor dat door de gebruiker zelf wordt ingetypt. Bijvoorbeeld, voor het programma dat we hier als leidraad gebruiken:
We gebruiken de speciale syntax <EOF>
om aan te geven dat er een end-of-file signaal wordt gestuurd.
Als je aanpassingen wil voorstellen specifiek voor de graduaatsopleiding, dan wordt aangeraden een fork van deze repository te maken op Github. De verschillen tussen de cursus voor de graduaatsopleiding en de bachelor hebben bijna uitsluitend betrekking tot de oefeningen. Als je aanpassingen wil doen met betrekking tot de inhoud, zie dan de overeenkomstige pagina in de cursus van de bachelor.
In alle lessen (hoorcollege en practica) hebben we 2 zaken nodig:
Deze cursus
Een laptop met daarop Visual Studio Code editie geïnstalleerd.
Deze cursus is een fork van die van de richting toegepaste informatica. We zijn Tim Dams en al zijn co-auteurs dank verschuldigd voor de stevige basis. Je mag veronderstellen dat alles Tims werk is, wij hebben vooral de vorm van de oefeningen aangepast voor een meer beroepsgerichte opleiding.
Herhalingen (loops) creëer je wanneer bepaalde code een aantal keer moet herhaald worden. Hoe vaak de herhaling moet duren is afhankelijk van de conditie die je hebt bepaald. Deze conditie is een booleaanse expressie, net zoals de conditie van if
.
In het vorige hoofdstuk leerden we hoe we met behulp van beslissingen onze code konden branchen, aftakken zodat andere code werd uitgevoerd afhankelijk van de staat van bepaalde variabelen of invoer van de gebruiker. Wat we nog niet konden was terug naar boven vertakken. Soms willen we dat een heel stuk code 2 of meerdere keren moet uitgevoerd worden zo lang als of totdat aan een bepaalde conditie wordt voldaan. "Voer volgende code uit tot dat de gebruiker een bepaalde waarde invoert." Dit zal bijna net hetzelfde werken als bij if
, maar nadat je code is uitgevoerd, wordt de conditie (opnieuw) gecontroleerd om na te gaan of de code (opnieuw) moet worden uitgevoerd.
Door herhalende code met loops te schrijven maken we onze code korter en bijgevolg ook minder foutgevoelig en beter onderhoudbaar.
Van zodra je dezelfde lijn(en) code onder elkaar in je code ziet staan (door bijvoorbeeld te copy pasten) is de kans zéér groot dat je dit korter kunt schrijven met loops.
Er zijn verschillende soorten loops:
Definite of counted loop: een loop waar het aantal iteraties vooraf van gekend is. (Bijvoorbeeld 100 keer of een aantal keer gelijk aan de waarde van de variabele x
).
Indefinite of sentinel loop: een loop waarvan op voorhand niet kan gezegd worden hoe vaak deze zal uitgevoerd worden. Input van de gebruiker of een interne test zal bepalen wanneer de loop stopt (bv. "Voer getallen in, voer -1 in om te stoppen")
Oneindige loop: een loop die nooit stopt. Soms gewenst, vaak een bug.
Er zijn 3 manieren om zogenaamde loops te maken in C#:
while
: zal 0 of meerdere keren uitgevoerd worden
do while
: zal minimaal 1 keer uitgevoerd worden
for
: een alternatieve iets compactere manier om loops te beschrijven
Al deze oefeningen maak je in een klasse VariabelenEnDatatypes
. In de oefeningen van hoofdstuk 1 heb je gezien hoe je een nieuwe klasse maakt.
gebruik van variabelen om input via Console.ReadLine
op te slaan
berekeningen met de opgeslagen data uitvoeren
het resultaat dat werd opgeslagen in een variabele via Console.WriteLine
te tonen
Een applicatie vraagt je twee getallen in te voeren. Na de invoer van het tweede getal worden beide getallen bij elkaar opgeteld. Het resultaat wordt uiteindelijk weergeven.
Noem de methode voor deze oefening Optellen
.
De vraag wordt gesteld om een getal in te typen en daarna op enter/return te drukken.
Er wordt gevraagd een tweede getal in te typen en dan op enter/return te drukken.
De twee getallen worden opgeteld.
Het resultaat wordt weergegeven.
Lees de gebruikersinvoer van de console en sla dit op in een variabele voor wat het eerste getal betreft. Herhaal dit voor het tweede getal. Tel de twee getallen samen en bewaar deze in een derde variabele. Uiteindelijk geef je dan de inhoud van deze derde variabele weer in de console.
Let op: met Console.ReadLine()
lees je tekst in, dus waarden die je kan toekennen aan variabelen van type string
. Om een getal in te lezen, vervang je Console.ReadLine()
door Convert.ToInt32(Console.ReadLine())
. De werking hiervan zie je later.
Voer tekst in.
Voer een getal met 100 cijfers in.
Voer geen getal in.
gebruik van variabelen om input via Console.ReadLine
op te slaan
berekeningen met de opgeslagen data uitvoeren
het resultaat dat werd opgeslagen in een variabele via onsole.WriteLine
te tonen
Een applicatie zal voor jou het gemiddelde verbruik van een wagen berekenen.
Hiervoor worden volgende vragen gesteld:
Hoeveel liter is er nog aanwezig in de benzinetank.
Hoeveel liter zit er nog in de benzinetank na de rit.
Ook de kilometerstand van bij de aanvang van de rit wordt gevraagd en ook deze nadat de rit werd uitgevoerd.
Op basis van deze parameters wordt het gemiddelde verbruik berekend en weergegeven.
Noem de methode voor deze oefening VerbruikWagen
.
De vraag wordt gesteld om het aantal liter, aanwezig in de benzinetank, op te geven.
Daarna wordt gevraagd om ook het aantal liter op te geven na de rit.
De kilometerstand van de aanvang van de rit wordt gevraagd.
Uiteindelijk ook de kilometerstand na het beëindigen van de rit wordt opgevraagd.
Lees de gebruikersinvoer van de console en slaag dit op in variabelen.
Zorg ervoor dat je het juiste gegevenstype kiest voor de verschillende variabelen.
Nadien voer je de berekening uit om op basis van de ingevoerde gegevens het gemiddeld verbruik te berekenen (100 * (aantalLiterinTankVoorRit - aantalLiterinTankNaRit) / (kilometerstandNaRit - kilometerstandVoorRit))
Uiteindelijk geef je dan het resultaat weer in de console.
Voer tekst in.
Voer een getal met 100 cijfers in.
Voer geen getal in.
expressies schrijven
voorrang van operatoren
effect van operaties naargelang datatype begrijpen
Je schrijft een programma dat de rol vervult van een rekenmachine. Het voert volgende berekeningen uit:
-1 + 4 * 6
( 35+ 5 ) * 7
14 + -4 * 6 / 11
2 + 15 / 6 * 1 - 7 * 2
Noem de methode voor deze oefening BeetjeWiskunde
.
Eerst wordt een resultaat berekend, daarna wordt het geprint.
Test uit met getallen van het type int
.
Test uit met getallen van het type float
.
expressies schrijven
voorrang van operatoren
effect van operaties naargelang datatype begrijpen
Je schrijft een programma dat het gemiddelde van 18, 11 en 8 berekent, d.w.z. deze drie getallen optelt en de som deelt door drie.
Noem de methode voor deze oefening Gemiddelde
.
Eerst wordt het resultaat berekend, daarna wordt het geprint.
Test uit met getallen van het type int
.
Test uit met getallen van het type float
.
de console leegmaken
werken met wiskundige operatoren
interactie met de gebruiker
Je schrijft een programma dat de tafel van vermenigvuldiging voor 411 geeft. Dit programma wacht steeds tot de gebruiker op ENTER duwt voor het het volgend resultaat toont. Verder maakt het steeds het scherm leeg voor het een nieuw resultaat toont. Zie "programmaverloop".
Noem de methode voor deze oefening Maaltafels
. Je kent nog geen lusstructuren, dus probeer deze niet te gebruiken, zelfs als je ze al ergens anders bent tegengekomen. Schrijf gewoon tien instructies.
(enzovoort)
Voor elk resultaat wordt het scherm eerst leeggemaakt. Daarna pas wordt het resultaat getoond. Wanneer de gebruiker op ENTER duwt, wordt deze handeling herhaald voor het volgende resultaat (of eindigt het programma, na het tiende resultaat). Het scherm leegmaken doe je met Console.Clear()
. Plaats 411 ook in een variabele.
Test uit zoals gegeven.
Test uit voor 511. Je zou maar één teken in je code moeten aanpassen als je de instructies hebt gevolgd.
werken met kommagetallen
Je massa is overal dezelfde en wordt uitgedrukt in kilogram. Je gewicht daarentegen is afhankelijk van de zwaartekracht van de plek waar je bent en wordt uitgedrukt in Newton. Je hebt dus een ander gewicht op andere planeten. Zo is je gewicht veel groter op Jupiter dan op Mars, omdat Jupiter meer zwaartekracht uitoefent dan Mars. Schrijf een programma dat je gewicht op aarde omzet naar je gewicht op een ander hemellichaam. Je krijgt volgende omzettingstabel:
Mercurius: 0.38 (een persoon van 100kg voelt zich alsof hij 38kg weegt)
Venus: 0.91
Aarde: 1.00 (een persoon van 100kg voelt zich alsof hij 100kg weegt)
Mars: 0.38
Jupiter: 2.34
Saturnus: 1.06
Uranus: 0.92
Neptunus: 1.19
Pluto: 0.06
Noem de methode voor deze oefening Ruimte
.
Plaats je gewicht in een variabele. Kies zelf een geschikt type.
Test uit voor je eigen gewicht.
Test uit voor het gewicht van een persoon met een massa van 100kg.
Er zijn quasi oneindig veel boeken over C# geschreven, althans zo lijkt het. Hier een selectie van boeken met een korte bespreking waarom ik denk dat ze voor jou een meerwaarde kunnen zijn bij het leren programmeren in C#:
van Mike McGrath: een uiterst compact, maar zeer helder en kleurrijk boekje dat ik ten stelligste aanbeveel als je wat last hebt met de materie van de eerste weken.
van Joyce Farrell: Niet het meest sexy boek, maar wel het meest volledige qua overlap met de leerstof van deze cursus. Aanrader voor zij die wat meer in detail willen gaan en op zoek zijn naar oneindig veel potentiele examenvragen ;)
van Andrew Stellman & Jennifer Greene: laat de ietwat bizarre, bijna kleuterachtige look and feel van de head first boeken je niet afschrikken. Ieder boek in deze serie is goud waar. De head first boeken zijn d� ideale manier als je zoekt naar een alternatieve manier om complexe materie te begrijpen. Bekijk zeker ook de Head First Design Patterns en Head First Sql boeken in de reeks!
van Bart De Smet: in mijn opinie dé referentie om C# tot op het bot te begrijpen. Geschreven door een Belg die bij Microsoft in Redmond aan C# werkt.
van Steve McConnell: een referentiewerk over 'programmeren in het algemeen'. Het boek is al jaar en dag het te lezen boek als je je als programmeur wilt verdiepen in wat nu 'correct programmeren' behelst. Als je op je CV kunt zetten dat je dit boek door en door kent dan zal elk IT-bedrijf je stante pede aannemen ;)
Leren programmeren door enkele de opdrachten in deze cursus te maken zal je niet ver (genoeg) brengen. Onze dikke vriend het Internet heeft echter tal van schitterende bronnen. Hier een overzicht.
Ideale manier om programmeren meer in de vingers te krijgen op een speelse manier:
Pittige vragen van de jaarlijkse Vlaamse Programmeerwedstrijd:
Ja hoor, ze bestaan. Meer en meer professionele én beginnende programmeurs streamen terwijl te programmeren. Dit is een ideale manier om te zien hoe andere mensen problemen aanpakken. De meeste programming streamers kan je terugvinden op youtube, maar ook op Twitch zijn er steeds meer. Enkele aanraders (bekijk zeker de filmpjes uit de archieven eens):
We hebben twee zaken nodig om C# programma's te schrijven.
Met een editor, zoals Visual Studio, krijgen we ondersteuning bij het schrijven van de code die we later zullen uitvoeren. Een editor vergemakkelijkt dus het schrijven van een specifiek soort tekst.
De "SDK" (software development toolkit) bevat alles wat je nodig hebt om de code daadwerkelijk uitvoerbaar te maken.
Voor deze opdrachten gebruiken we .NET Core 6.0, maar we gebruiken de (explicietere) template van .NET Core 5.0. Je moet dus beide SDK's installeren.
Sommige afbeeldingen hieronder dateren van de tijd van .NET Core 2.0 en Visual Studio 2017. Soms kunnen zaken er op jouw scherm dus wat anders uitzien dan in deze cursus.
Kies voor de "community" versie
Het installatiescherm ziet er als volgt uit, in de volgende punten wordt beschreven welke elementen je specifiek moet aanduiden.
Kies bij de installatie voor .NET desktop development
Bij de installation details kies je voor volgende elementen
In het tabblad individual components klik je .NET 5.0 Runtime (out of support) nog extra aan.
Vervolgens kies je rechts onderaan voor de knop Install
Log in met jouw account van de hogeschool, nl. s-nummer@ap.be
Kies voor Create New Project
Vervolgens selecteer je Console App
Je geeft je project een goede naam, hieronder dien je de Project name dan aan te passen en je zal zien dat de Solution name automatisch mee wordt gewijzigd. Denk er ook aan om je project op de juiste plaats op te slaan door de Location te wijzigen.
In de volgende stap dien je als Framework .NET 6.0 te kiezen en de optie "Do not use top-level statements" aan te vinken.
Nadat je je project hebt aangemaakt, rechterklik je op je project in de Solution Explorer -> Properties -> Build -> General en schakel je "Implicit global usings" uit.
Hierbij ga je ervoor zorgen dat je met afkortingen kan coderen die dan automatisch in het goede formaat worden omgezet.
Bv. cr (gevolgd door twee TABs) wordt dan Console.ReadLine()
Je kan volgende code kopiëren in een tekstbestand met als extensie .snippet of je kan het hieronder downloaden.
Vervolgens dien je binnen Visual Studio in het menu Tools naar Code Snippets Manager gaan.
Je kiest voor Language CSharp en klikt op de knop Import.
Je kiest het snippet bestand en voegt dit toe.
Via Console.WriteLine kan je iets op het scherm laten zien:
Met de console kan je met een handvol methoden reeds een aantal interessante dingen doen.
Zo kan je bijvoorbeeld input van de gebruiker inlezen en bewaren in een variabele als volgt:
Bespreking van deze code:
string result;
Concreet zeggen we hiermee aan de compiler: maak in het geheugen een plekje vrij waar enkel data van het type string in mag bewaard worden;
Noem deze geheugenplek result
zodat we deze later makkelijk kunnen in en uitlezen.
result = Console.ReadLine();
Vervolgens roepen we de ReadLine
methode aan. Deze methode zal de invoer van de gebruiker uitlezen tot de gebruiker op enter drukt.
Het resultaat van de ingevoerde tekst wordt bewaard in de variabele result
(denk eraan dat de toekenning van rechts naar links gebeurt).
Een stukje code van de vorm naam = waarde
heet een toekenning. Dit is niet hetzelfde als een wiskundige gelijkheid. In een toekenning plaats je een waarde aan de rechterkant. Aan de linkerkant schrijf je een naam die je aan deze waarde geeft.
Als we een naam voor de eerste keer in het programma koppelen aan een waarde, spreken we soms over een initialisatie. Dit is ook een soort toekenning.
Je programma zou nu moeten zijn:
Start nogmaals je programma. Je zal merken dat je programma nu een cursor toont en wacht op invoer. Je kan nu eender wat intypen en van zodra je op enter duwt gaat het programma verder (in dit geval stopt het programma hierna dus).
We kunnen nu invoer van de gebruiker, die we hebben bewaard in de variabele result
gebruiken en tonen op het scherm.
Op de tweede lijn hier gebruiken we de variabele result
(waar de invoer van de gebruiker in bewaard wordt) als parameter in de WriteLine
-methode.
Met andere woorden: de WriteLine
methode zal op het scherm tonen wat de gebruiker even daarvoor heeft ingevoerd.
Je volledige programma ziet er dus nu zo uit:
Test het programma en voer je naam in wanneer de cursor knippert.
Voorbeelduitvoer (lijn 3 is wat de gebruiker heeft ingetypt)
Wanneer je de inhoud van een variabele wil gebruiken in een methode zoals WriteLine()
dan plaats je deze zonder aanhalingsteken! Bekijk zelf eens wat het verschil wordt wanneer je volgende lijn code Console.Write(result);
vervangt door Console.Write("result");
.
De uitvoer wordt dan:
De WriteLine
-methode zal steeds een line break (een 'enter' ) aan het einde van de lijn zetten zodat de cursor naar de volgende lijn springt.
De Write
-methode zal geen enter aan het einde van de lijn toevoegen. Als je dus vervolgens iets toevoegt (met een volgende Write
of WriteLine
) dan zal dit aan dezelfde lijn toegevoegd worden.
Vervang daarom eens de laatste 3 lijnen code in je project door:
Voer je programma uit en test het resultaat. Je krijgt nu:
Wat is er verkeerd gelopen? Al je tekst van de laatste lijn plakt zo dicht bij elkaar? Inderdaad, we zijn spaties vergeten toe te voegen! Spaties zijn ook tekens die op scherm moeten komen (ook al zien we ze niet) en je dient dus binnen de aanhalingstekens spaties toe te voegen. Namelijk:
Je uitvoer wordt nu:
Spaties zijn ook tekens die op scherm moeten komen (ook al zien we ze niet) en je dient dus binnen de aanhalingstekens spaties toe te voegen. Indien je deze erbuiten plaats dan heeft dit geen effect (je wist al uit het eerste hoofdstuk dat C# alle witregels negeert die niet tussen aanhalingstekens staan). In volgend voorbeeld zijn de spaties aangegeven als liggende streepjes ( _ ).
Fout (de code zal werken maar je spaties worden genegeerd):
Correct:
We kunnen dit hele verhaal een pak korter tonen. De plus-operator (+
) in C# kan je namelijk gebruiken om variabelen van het type string aan elkaar te plakken. De laatste 3 lijnen code kunnen korter geschreven worden als volgt:
Merk op dat result dus NIET tussen aanhalingstekens staat, in tegenstelling de andere stukken zin. Waarom is dit? Aanhalingstekens in C# duiden aan dat een stuk tekst moet beschouwd worden als tekst van het type string. Als je geen aanhalingsteken gebruikt dan zal C# de tekst beschouwen als een variabele met die naam.
Bekijk zelf eens wat het verschil wanneer je volgende lijn code vervangt door de lijn er onder:
Als je meerdere inputs van de gebruiker tegelijkertijd wenst te bewaren dan zal je meerdere geheugenplekken nodig hebben om de invoer te bewaren. Bijvoorbeeld:
Je mag echter ook de geheugenplekken al vroeger maken. In C# zet men de geheugenplek creatie zo dicht mogelijk bij de code waar je die plek gebruikt (zoals vorig voorbeeld), maar dat is geen verplichting. Dit mag dus ook:
Voor de graduaatsopleiding volgen we een reeks afspraken (ook "conventies" genoemd) die bepalen welke code wel en niet aanvaardbaar is. Wanneer een groep programmeurs dezelfde afspraken volgt, is het voor iedereen makkelijker code uit te wisselen.
Als je deze pagina voor de eerste keer ziet, zullen de meeste van deze afspraken je nog niet veel zeggen. Dat is logisch. We zullen je tijdens de les wijzen op de afspraken die horen bij een nieuw concept, maar hier heb je een handig overzicht:
Onderstaande richtlijnen zijn gebaseerd op . We beperken ons tot de zaken die we tijdens de cursus zien.
We kiezen namen in het Engels. Enkel tekst die aan de gebruiker getoond wordt, schrijven we in het Nederlands.
Merk op dat het verschil tussen PascalCase en camelCase samenhangt met de default zichtbaarheid. Wanneer afgeweken wordt van de default zichtbaarheid, kan de conventie voor de notatie ook veranderen. Wij zullen ons in deze cursussen voor de eenvoud strikt aan de default zichtbaarheid houden.
"meervoud toegestaan" betekent dat we iets noteren als een meervoud als de data ook meerdere objecten voorstelt. Bijvoorbeeld een lokale variabele met naam studenten
en datatype Student[]
, maar een lokale variabele student
met datatype Student
.
We beginnen niet elke identifier voor een private
member met een underscore. Sommige programmeurs doen dit wel, maar wij volgen één richtlijn.
Namen van klassen voor custom exceptions eindigen op Exception
.
Vermijd afkortingen in namen.
Uitzondering: algemeen aanvaarde afkortingen zoals ID
, HTML
,...
Afkortingen van één of twee letters
Andere talen hebben soms andere conventies!
using
directievenDeze komen alleen vooraan in het bestand voor en worden steeds gevolgd door de declaratie van de namespace.
In het eerste semester groeperen we al onze code in de namespace Programmeren
, in het tweede semester in de namespace ObjectgerichtProgrammeren
.
Elk zelf gedefinieerd datatype (klasse, struct, interface, enum type, later ook delegate) plaatsen we in een afzonderlijk bestand met dezelfde naam als dat datatype.
In de eerste twee lessen mag dit met behulp van +
.
Gebruik stringinterpolatie om kleine aantallen strings aan elkaar te hangen of data weer te geven in stringformaat.
Gebruik een StringBuilder
om strings aan elkaar te hangen in een lus.
Gebruik steeds een expliciet, statisch type.
Dat wil zeggen: geen variabelen declareren als var
. Dat doe je pas later.
Dat wil zeggen: geen variabelen declareren als dynamic
. Dat doe je pas later.
Gebruik de kortste syntax voor arrays van literals, dus string[] vowels1 = { "a", "e", "i", "o", "u" };
en niet string[] vowels2 = new string[] { "a", "e", "i", "o", "u" };
.
static
membersPlaats voor een static
member altijd uitdrukkelijk de naam van de klasse waarin dat static
member gedefinieerd is.
Klassen die uitsluitend static
members bevatten ("library classes") maken we ook static
met de syntax static class
.
Klassen die niet bedoeld zijn om van over te erven maken we final
.
Als iets een constante is voor een bepaald programme, gebruiken we ook de access modifier const
.
We gebruiken alleen zaken die in de les aan bod zijn gekomen. We zien geen lambda's, delegates, LINQ,... dus je gebruikt deze ook niet, zelfs als je ze al ergens anders gezien hebt.
In dit onderdeel analyseren we het standaard programma dat Visual Studio voor ons in het bestand Program.cs gemaakt heeft. Verder zetten we de eerste stappen in het leren programmeren.
weet je hoe de basisstructuur van een C# programma in elkaar steekt
ken je de basiselementen van het programmeren:
declaratie: gegevens en variabelen
statement: één instructie
functie of methode: een reeks instructies om één specifiek doel te realiseren, bijvoorbeeld voor het bereken van de oppervlakte van een cirkel In Object Oriented jargon spreekt men van een methode en vermits C# een OO taal is, zullen we in deze cursus verder het woord methode gebruiken. Als jullie JavaScript leren ga je het niet over een methode hebben maar over een functie omdat JavaScript geen strikte OO taal is.
methode In C# staat code altijd in een methode. Je kan geen statement schrijven zonder eerst een methode te hebben gemaakt. Hier moeten enkele zaken worden opgemerkt:
static Het sleutelwoord static is een modifier. Met een modifier geven we aan hoe we de methode willen gebruiken. Vermits we in het eerste semester klassen enkel en alleen gaan gebruiken om code te ordenen en niet om objecten te maken en te gebruiken, gaan we meestal de modifier static vóór de methode naam plaatsen, zoals in het voorbeeld hierboven. Met static bedoelen we dan deze methode tot de klasse behoort en niet tot de objecten die op basis van die klasse worden gemaakt.
void In C# spreken we van methoden. Maar een methode is eigenlijk een functie. Een functie retourneert meestal een waarde. Is dat niet zo moet je aangegeven dat de methode geen waarde retourneert door er void vóór de plaatsen zoals de Main methode in het voorbeeld hierboven.
Main Dit is de naam van de methode. De moeder aller methoden in een C# programma draagt de naam Main methode. Elk programma dat je ooit gaat schrijven in C# moet een Main methode bevatten. Het is bovendien de enige methode die zal worden uitgevoerd als je het programma runt. Alle andere methode worden niet automatisch uitgevoerd. Wil je een bepaalde methode laten uitvoeren moet je die in de Main methode oproepen.
(string[] args) Na de naam staan twee ronde haakjes. Tussen de ronde haken kan je parameters plaatsen. In deze parameters worden de argumenten die bij het oproepen van de methode worden meegegeven opgevangen. In het voorbeeld hierboven is dat string[] args. Dat wil zeggen dat je de Main methode kan oproepen en een reeks van argumenten kan meegeven. We komen hier later op terug. Je mag deze parameter gerust verwijderen vermits we pas later leren werken array's. De Main methode hierboven bevat slechts 1 statement.
statement
Er is maar 1 statement. In het statement roepen we de WriteLine
methode op die in de klasse Console
staat. De klasse Console
staat in de namespace met de naam System
. Bovenaan, op regel 1, geven we aan dat we die bibliotheek in ons programma zullen gebruiken met het statement using System;
Het statement ziet er zo uit:
Het eerste wat we opmerken is dat een statement eindigt met een ; (een puntkomma).
In dit statement roepen we de methode WriteLine
op en geven als argument de tekst "Hello World" mee. De methode WriteLine
beschikt over 1 parameter die deze tekst aanneemt en voor ons in de console zal tonen.
Let erop dat je iedere 'zin' eindigt met een puntkomma.
Om een uitvoerbaar C#-programma te maken moeten we bepaalde regels respecteren. We noemen deze regels de syntaxis of syntax van de taal.
Een C#-programma bestaat uit een opeenvolging van instructies ook wel statements genoemd. Met de uitzondering van blokken (ook blocks, afgebakend door {
en }
) eindigen deze steeds met een puntkomma (;
)
De volgorde van de woorden is niet vrijblijvend en moet aan grammaticale regels voldoen. Enkel indien alle statements correct zijn zal het programma gecompileerd worden naar een werkend en uitvoerbaar programma (zoals in een vorige sectie besproken).
Enkele belangrijke regels van C#:
Hoofdletter-gevoelig: C# is hoofdlettergevoelig. Dat wil zeggen dat hoofdletter T
en kleine letter t
totaal verschillende zaken zijn voor C#. Reinhardt
en reinhardt
zijn dus ook niet hetzelfde.
Statements afsluiten met puntkomma: Ieder C# statement dat geen block is, wordt afgesloten moet een puntkomma ( ;
). Doe je dat niet dan zal C# denken dat de regel gewoon op de volgende lijn doorloopt en deze als één (fout) geheel proberen te compileren.
Blocks: Een block begint met {
, eindigt met }
en bevat daartussen verdere statements (ook geneste blocks zijn toegelaten).
Witruimtes: Spaties, tabs en newlines worden door de C# compiler genegeerd. Je kan ze dus gebruiken om de layout van je code te verbeteren. De enige plek waar witruimtes wél een verschil geven is tussen aanhalingstekens " "
die we later (bij string) zullen leren gebruiken.
Commentaar toevoegen kan: met behulp van //
voor een enkele lijn en /* */
voor meerdere lijnen commentaar. Commentaar zal door de compiler genegeerd worden.
Er zijn binnen C# dan ook 80 woorden, zogenaamde reserved keywords die deel van de taal zelf uitmaken. In deze cursus zullen we stelselmatig deze keywords leren kennen en gebruiken op een correcte manier om zo werkende code te maken.
Deze keywords zijn:
De keywords in vet zijn keywords die we dit semester zullen kennen. Die in cursief in het tweede semester. De overige zal je zelf moeten leren.
Indien je deze tabel in pdf bekijkt zal deze om zeep zijn. Onze gitbook gnomes proberen dit op te lossen maar voorlopig vinden ze helaas geen oplossing, waarvoor onze excuses.
We hebben variabelen nodig om data een naam te geven, zodat we er later naar kunnen verwijzen. Wanneer we een statement schrijven dat bijvoorbeeld input van de gebruiker moet vragen, dan willen we die input ook bewaren. Zo kunnen we verderop in het programma iets met deze data doen. We doen hetzelfde in ons hoofd wanneer we bijvoorbeeld zeggen: "tel 3 en 4 op en vermenigvuldig dat resultaat met 5". In die zin is "dat resultaat" een manier om te verwijzen naar wat we eerder gedaan hebben. Met andere woorden, een variabele. Vervolgens zullen we de inhoud van die variabele vermenigvuldigen met 5.
Wanneer we een variabele aanmaken, zal deze moeten voldoen aan enkele afspraken. Zo moeten we minstens 2 zaken aangeven:
Het type van de variabele: het datatype dat aangeeft wat voor data we wensen op te slaan (tekst, getal, afbeelding, etc.).
De naam van de variabele: de identifier waarmee we snel aan de variabele-waarde kunnen.
De code die we gaan schrijven moet voldoen aan een hoop regels. Wanneer we in onze code zelf namen (identifiers) moeten geven aan variabelen (en later ook methoden, objecten, etc.) dan moeten we een aantal regels volgen:
Hoofdlettergevoelig: de identifiers tim
en Tim
zijn verschillend, zoals reeds vermeld.
Geen keywords: identifiers mogen geen gereserveerde C# keywords zijn. De keywords van hierboven mogen dus niet. Varianten waarbij de hoofdletters anders zijn mogen wel, bijvoorbeeld: gOTO
en stRINg
mogen dus wel, maar niet goto
of string
daar beide een gereserveerd keyword zijn maar dankzij de hoofdlettergevoelig-regel is dit dus toegelaten. INT
mag ook ook, maar niet int
.
Eerste karakter-regel: het eerste karakter van de identifier mag enkel zijn:
kleine of grote letter
liggend streepje (_
)
Alle andere karakters-regels: de overige karakters mogen enkel zijn:
kleine of grote letter
liggend streepje
een cijfer (0
tot en met 9
)
Lengte: Een legale identifier mag zo lang zijn als je wenst, maar je houdt het best leesbaar.
Enkele voorbeelden van toegelaten en niet toegelaten identifiers:
Duidelijke naam: de identifier moet duidelijk maken waarvoor de identifier dient. Schrijf dus liever gewicht
of leeftijd
in plaats van a
of meuh
.
Camel casing: gebruik camel casing indien je meerdere woorden in je identifier wenst te gebruiken. Camel casing wil zeggen dat ieder nieuw woord terug met een hoofdletter begint. Een goed voorbeeld kan dus zijn leeftijdTimDams
of aantalLeerlingenKlas1EA
. Merk op dat we liefst het eerste woord met kleine letter starten. Uiteraard zijn er geen spaties toegelaten.
Pascal casing: Zoals camel casing, maar ook de eerste letter is een hoofdletter. Gebruik dit voor namespaces, klassen en voor methodes waarbij je public
zet.
Soms wil je misschien extra opmerkingen bij je code zetten. Als je dat gewoon zou doen (bv Dit deel zal alles verwijderen
), dan zal je compiler niet begrijpen wat die zin doet. Hij verwacht namelijk C# en geen Nederlandstalige zin. Om dit op te lossen kan je in je code op twee manieren aangeven dat een stuk tekst gewoon commentaar is en mag genegeerd worden door de compiler:
Eén lijn commentaar geef je aan door de lijn te starten met twee voorwaartse slashes //
. Uiteraard mag je ook meerdere lijnen op deze manier in commentaar zetten. Zo wordt dit ook vaak gebruikt om tijdelijk een stuk code "uit te schakelen". Ook mogen we commentaar achter een stuk C# code plaatsen (zie voorbeeld hieronder).
We kunnen een stuk tekst als commentaar aangeven door voor de tekst /*
te plaatsen en */
achteraan. Een voorbeeld:
Een essentieel onderdeel van C# is kennis van datatypes. Een datatype is, zoals de naam het zegt, een soort waartoe bepaalde gegevens kunnen behoren. Wanneer je data wenst te bewaren in je applicatie dan zal je je moeten afvragen wat voor soort data het is. Gaat het om een getal, een geheel getal, een kommagetal, een stuk tekst of misschien een binaire reeks? Een variabele met een bepaald datatype kan een bepaald soort data bewaren en dit zal afhankelijk hiervan een bepaalde hoeveelheid computergeheugen vereisen.
Er zijn tal basistypes in C# gedeclareerd (zogenaamde built-in datatypes). Dit semester leren we werken met datatypes voor:
Gehele getallen: sbyte, byte, short, ushort, int, uint, long
Kommagetallen: double, float, decimal
Tekst: char, string
Booleans: bool
Het datatype string
heb je al in actie gezien in het vorig hoofdstuk. Je hebt toen al een variabele aangemaakt van het type string door de zin string result;
.
Verderop koppelden we de naam result
dan aan het resultaat van een actie, namelijk inlezen van tekst via Console.ReadLine
(eerst wordt dat resultaat uitgerekend, dan pas wordt het aan de naam gekoppeld):
C# heeft een hoop datatypes gedefinieerd om te werken met getallen zoals wij ze kennen, gehele en kommagetallen. Intern zullen deze getallen steeds binair bewaard worden, maar dat merken we zelden tijdens het programmeren.
De basistypen van C# om getallen in op te slaan zijn:
Voor gehele getallen: sbyte, byte, short, ushort, int, uint, long
Voor kommagetallen: double, float, decimal
Deze datatypes hebben allemaal een bepaald bereik, wat een rechtstreeks gevolg is van de hoeveelheid geheugen die ze innemen.
Voor de gehele getallen:
Enkele opmerkingen bij deze tabel:
De s
vooraan sbyte
types staat voor signed
: m.a.w. 1 bit wordt gebruikt om het + of - teken te bewaren.
De u
vooraan ushort
, uint
en ulong
staat voor unsigned
. Het omgekeerde van signed dus. Kwestie van het ingewikkeld te maken. Deze twee datatypes hebben dus geen teken en zijn altijd positief.
char
heeft dezelfde kenmerken als ushort
, maar dient voor iets anders. ushort
gebruik je echt om getallen binnen dat bereik voor te stellen. char
bewaart karakters, d.w.z. symbolen zoals letters. Een beetje vereenvoudigd kan je stellen dat er een tabel is waarin elk symbool gekoppeld is aan een getal, zodat een reeks nulletjes en eentjes ook een letter kan voorstellen.
Voor de kommagetallen zijn er maar 3 mogelijkeden. Ieder datatype heeft een voordeel tegenover de 2 andere, dit voordeel staat vet in de tabel:
Zoals je ziet moet je bij kommagetallen een afweging maken tussen 3 even belangrijke criteria. Heb je zeer grote precisie (veel cijfers na de komma) nodig, dan ga je voor een decimal
. Wil je vooral erg grote of erg kleine getallen (met meer kans op afrondingen), dan kies je voor double
.
Bij twijfel opteren we meestal voor kommagetallen om het double
datatype te gebruiken. Bij gehele getallen kiezen we meestal voor int
.
Het bool
(boolean) is het eenvoudigste datatype van C#. Het kan maar 2 mogelijke waarden bevatten: true
of false
. 1 of 0 met andere woorden. In het Nederlands meestal uitgedrukt als "waar" en "niet waar".
We besteden verderop een heel apart hoofdstuk aan tonen hoe je tekst en enkele karakters kan bewaren in variabelen. De basis:
Tekst kan bewaard worden in het string
datatype
Letterlijke tekst schrijf je tussen dubbele quotes, bijvoorbeeld "Hallo Wereld!"
Een enkel karakter wordt bewaard in het char
datatype, dat we ook hierboven al even hebben zien passeren.
Letterlijke karakter schrijf je tussen enkele quotes, bijvoorbeeld 'à'
De essentie van een computerprogramma is het zogenaamde algoritme (het "recept" zeg maar). Dit is de reeks instructies die het programma moet uitvoeren telkens het wordt opgestart. Het algoritme van een programma moet je zelf verzinnen. De volgorde waarin de instructies worden uitgevoerd zijn uiteraard zeer belangrijk. Dit is exact hetzelfde als in het echte leven: een algoritme om je fiets op te pompen kan zijn:
Eender welke andere volgorde van bovenstaande algoritme zal vreemde (en soms fatale) fouten geven.
Om een algoritme te schrijven dat onze computer begrijpt dienen we een programmeertaal te gebruiken. Net zoals er ontelbare spreektalen in de wereld zijn, zijn er ook vele programmeertalen. C# (spreek uit 'siesjarp') is er een van de vele. In tegenstelling tot onze spreektalen moet een computertaal 'exact' zijn en moet het op ondubbelzinnige manier door de computer verstaan worden. C# is een taal die deel uitmaakt van de .NET (spreek uit 'dotnet') omgeving die meer dan 15 jaar geleden door Microsoft werd ontwikkeld (juli 2000).
Deze cursus werd geschreven in . Helaas ondersteunt deze geen # in titels. Daardoor heet dit hoofdstuk "C-Sharp" en niet "C#". Niets aan te doen.
De geschiedenis en de hele .NET-wereld vertellen zou een cursus op zich betekenen en gaan we hier niet doen. Het is nuttig om weten dat er een gigantische bron aan informatie over .NET en C# online te vinden is, beginnende met .
Met .NET Core kun je zowel webapplicaties, desktopapplicaties, microservices en Internet of Things toepassingen ontwikkelen.
Je kan in console-applicaties zelf bepalen in welke kleur nieuwe tekst op het scherm verschijnt. Je kan zowel de kleur van het lettertype instellen (via ForegroundColor
) als de achtergrondkleur (BackgroundColor
).
Je kan met de volgende expressies de console-kleur veranderen, bijvoorbeeld de achtergrond in blauw en de letters in groen:
Vanaf dan zal alle tekst die je na deze 2 expressies via WriteLine
naar het scherm stuurt met deze kleuren werken.
Een voorbeeld:
Als je deze code uitvoert krijg je als resultaat:
Kleur in console gebruiken is nuttig om je gebruikers een minder eentonig en meer informatieve applicatie aan te bieden. Je zou bijvoorbeeld alle foutmeldingen in het rood kunnen laten verschijnen.
Soms wil je terug de originele applicatie-kleuren hebben. Je zou manueel dit kunnen instellen, maar wat als de gebruiker slecht ziend is en in z'n besturingssysteem andere kleuren als standaard heeft ingesteld?!
De veiligste manier is daarom de kleuren te resetten door de Console.ResetColor()
methode aan te roepen zoals volgend voorbeeld toont:
Alle kleuren die beschikbaar zijn zijn beschreven in ConsoleColor
deze zijn:
ConsoleColor.Black
ConsoleColor.DarkBlue
ConsoleColor.DarkGreen
ConsoleColor.DarkCyan
ConsoleColor.DarkRed
ConsoleColor.DarkMagenta
ConsoleColor.DarkYellow
ConsoleColor.Gray
ConsoleColor.DarkGray
ConsoleColor.Blue
ConsoleColor.Green
ConsoleColor.Cyan
ConsoleColor.Red
ConsoleColor.Magenta
ConsoleColor.Yellow
Om je oefeningen ordelijk bij te houden, gaan we al een aantal zaken gebruiken die je pas verder in de cursus in detail leert. Hieronder krijg je een korte inleiding.
Volg hiervoor de instructies in het document op DigitAP.
EenProgrammaSchrijvenInCSharp
Dit is nieuw. Klassen zijn een extra organisatie-eenheid van code. Ze spelen een grote rol in objectgeoriënteerd programmeren, maar kunnen ook gebruikt worden om stukken code verder te groeperen. Dit is voorlopig het enige dat wij er mee zullen doen. Via "File" maak je een nieuw bestand aan. Je kiest ervoor een nieuwe C#-klasse te maken met de naam EenProgrammaSchrijvenInCSharp
.
Een definitie van een methode bevat één taak die kan worden uitgevoerd. Onze eerste oefenprogramma's zullen telkens één methode zijn. De werking van methodes wordt verder in de cursus in detail omschreven. Soms moet je eerst iets overnemen voor je de functie van elk onderdeeltje kan begrijpen!
Neem aandachtig volgende code over, zodat jouw bestand exact dit bevat:
Belangrijke onderdelen:
using System;
maakt onderdelen uit de System
namespace toegankelijk. Zonder dit kan je geen gebruik maken van Console
.
namespace LaboOefeningen
geeft aan dat alles wat binnen de buitenste accolades (de symbolen op regels 4 en 11) staat behoort tot die namespace. Het is de "achternaam" van jouw code. Als er bijvoorbeeld twee stukken code EenProgrammaSchrijvenInCSharp
zijn, kan je ze uit elkaar houden door te zeggen over welke namespace het gaat. Dit is hetzelfde als twee personen die dezelfde voornaam hebben, maar een andere achternaam.
public
: hier komen we pas een stuk later op terug. Voorlopig zetten we het standaard bij klassen en methoden.
class
: zie dit voorlopig als een verdere onderverdeling van je code. Eigenlijk zijn klassen veel meer dan dat, maar dat is voor later.
static
: dit is sowieso nodig wanneer we klassen enkel zien als een verdere onderverdeling van je code. We zetten het bij onze methoden.
void
: voorlopig zetten we dit altijd bij onze methoden. Het betekent ongeveer: "deze methode voert een commando uit, eerder dan een antwoord te geven op een vraag".
MijnEersteMethode
: dit is de naam van onze methode. We kiezen deze zelf.
()
: dit betekent dat de methode geen extra informatie nodig heeft om uit te voeren. Soms zetten we ook zaken tussen deze haakjes, de zogenaamde "parameters". Dit komt later aan bod.
Alles tussen de accolades op regel 7 en 9: de "body" van de methode. Dit is eigenlijk wat er gebeurt als we de methode gebruiken.
Standaard voert de code in de klasse Program
, in de methode Main
uit. Dat is het beginpunt van ons programma. We kunnen de body van Main
aanpassen. We zullen hierin aangeven dat onze eigen methode moet worden opgeroepen als volgt:
We noemen de code in de body een "oproep" van de methode MijnEersteMethode
. Hierin staat niet hoe die methode werkt. Er staat alleen dat we ze willen gebruiken.
Je mag nu zowel de definitie als de oproep van MijnEersteMethode
wissen. In de oefeningen die volgen, maak je telkens nieuwe methodes. Test ze telkens uit door de oproep te vervangen. Laat de definitie staan wanneer je overgaat naar de volgende oefening. Later zullen we een keuzemenu maken dat ons toestaat makkelijk een oefening naar keuze te demonstreren.
een eigen programma kunnen uitvoeren
input en output via Console.ReadLine
en Console.WriteLine
Binnen een zgn. dos-box wordt een titel weergegeven, nl. dit is mijn eerste c# programma. Vervolgens wordt gevraagd je naam te noteren. Wanneer je je naam hebt genoteerd en op enter hebt gedrukt, verschijnt de tekst “hallo [en je ingegeven naam]”.
maak een methode met de naam MijnEersteProgramma
Wat het lezen en schrijven van tekst betreft moet gebruik gemaakt worden Console.WriteLine
en Console.ReadLine
.
Probeer meer dan 200 tekens in te voeren
Probeer geen tekst in te voeren
een eigen programma kunnen uitvoeren
input en output via Console.ReadLine
en Console.WriteLine
de computer leren zien als "domme verwerker"
Dit programma verwerkt tekst die door de gebruiker wordt ingetypt. Het print nieuwe berichten die deze tekst bevatten uit. Het print niet de berichten die je verwacht: het zal de antwoorden door elkaar halen en je favoriete kleur tonen wanneer het beweert je favoriete eten te tonen, enzovoort. De verbanden worden duidelijk uit de voorbeeldinteractie.
Schrijf dit programma als een methode met de naam Rommelzin
binnen de klasse EenProgrammaSchrijvenInCSharp
. Test uit door deze methode op te roepen binnen de Main
methode.
Per regel die getoond wordt op het scherm, maak je gebruik van Console.WriteLine
. Per regel die je zelf intypt, maak je gebruik van Console.ReadLine
. Zorg zelf voor de juiste ondersteunende code.
Test uit met een héél lang stuk tekst (meer dan 200 tekens) voor je favoriete kleur.
Test uit met tekst met internationale karakters, bijvoorbeeld de ç.
Ga na wat er gebeurt als je een lege regel invoert, dus als je meteen op ENTER duwt wanneer gevraagd wordt om invoer.
de kleur van tekst in de console aanpassen
herhaling van de leerdoelen uit H1-rommelzin
Dit programma werkt net als H1-rommelzin, maar elke regel die aan de gebruiker wordt getoond, krijgt een andere kleur. De namen van de kleuren die je gebruikt (in deze volgorde) zijn:
DarkGreen
DarkRed
DarkYellow
Blue
Cyan
Red
Schrijf deze oefening als een nieuwe methode met de naam GekleurdeRommelzin
in de klasse EenProgrammaSchrijvenInCSharp
. Test uit door deze methode op te roepen binnen de Main
methode.
Voor elke regel die in kleur getoond wordt, wissel je de voorgrondkleur. Op de juiste plaatsen in de code herstel je de oorspronkelijke kleuren van de terminal.
Test opnieuw uit met een kleur, maaltijd, auto, film en boek naar keuze.
Je kan strings en variabelen samenvoegen tot een nieuwe string op verschillende manieren. We bekijken volgende twee mogelijkheden:
+
-operator
$
string interpolation
Gebruik zelf stringinterpolatie tenzij het anders gevraagd wordt. Dit is bijna altijd de handigste manier. Online kom je nog (vooral oudere) code tegen die het anders doet, maar we geven deze bewust niet omdat stringinterpolatie bijna altijd het beste werkt.
We gaan van volgende informatie uit:
Stel dat je 2 variabelen hebt int age=13
en string name="Finkelstein"
.
We willen de inhoud van deze variabelen samenvoegen in een nieuwe string result
die zal bestaan uit de tekst:
Ik ben Finkelstein en ik ben 13 jaar oud.
Volgende 2 manieren tonen hoe je steeds tot voorgaande string zal komen.
+
-operatorAls je de +
tussen strings plaatst, krijgt deze operator een andere betekenis dan tussen getallen. De strings worden dan achter elkaar geplakt. Als iets geen string is en op deze manier wordt gebruikt, wordt eerst een tekstvoorstelling bepaald, zoals hieronder bij age
(want dat is een int
).
Op het eerste zicht is dit een eenvoudige manier om strings op te bouwen, maar ze heeft een paar belangrijke nadelen:
je moet vaak afwisselen tussen aanhalingstekens en plustekens
het is lastig spaties en leestekens juist te noteren op deze manier (merk op dat de stukken tekst in het voorbeeld spaties op de zijkanten bevatten)
er komt vrij veel extra werk bij kijken als je data in een specifiek formaat wil weergeven, bijvoorbeeld met een specifiek aantal cijfers na de komma
als je grote, complexe strings op deze manier opbouwt, kost het erg veel rekentijd
We geven deze manier van werken vooral mee omdat ze in héél veel programmeertalen bestaat en omdat ze simpel is. Ze is niet bijzonder goed.
$
Via stringinterpolatie schrijf je je string min of meer zoals hij er uiteindelijk moet uitzien, maar vervang je de niet-letterlijke delen door geformatteerde waarden. Dit levert een goed leesbaar resultaat.
Door het $
-teken VOOR de string te plaatsen geef je aan dat alle delen in de string die tussen accolades staan als code mogen beschouwd worden. Een voorbeeld maakt dit duidelijk:
In dit geval zal dus de inhoud van de variabele name
tussen de string op de plek waar nu {name}
staat geplaatst worden. Idem voor age
. Dit mag, zelfs al is age
geen string: hetgeen tussen de accolades staat, wordt altijd intern omgezet naar een string voor het in het resultaat wordt geplaatst.
Zoals eerder aangegeven, kan je geen strings toekennen aan variabelen van type int of omgekeerd. Toch wil je soms een getal beschouwen als tekst, of tekst omzetten naar een getal .NET heeft hiervoor methoden die je kunnen helpen om data van het ene type naar het andere te brengen. Deze methoden zitten binnen de Convert
-klasse.
Het gebruik hiervan is zeer eenvoudig. Enkele voorbeelden:
Je plaatst bij gebruik van Convert
tussen de ronde haakjes de variabele of literal die je wenst te converteren naar een ander type. Merk op dat naar een int
converteren met .ToInt32()
moet gebeuren. Voor andere types zijn er overeenkomstige methoden. Je kan . Volgende conversies zullen je al ver vooruit helpen:
Voorgaande code veronderstelt dat de gebruiker géén fouten invoert. De conversie zal namelijk mislukken indien de gebruiker bijvoorbeeld IKWEEG10KG
invoert in plaats van 10,3
.
In het begin van de leercurve moet je er altijd van uitgaan dat de gebruiker foutloze input geeft. Later leer je wel hoe je dit kan afhandelen.
Opgelet: de invoer van kommagetallen door de gebruiker is afhankelijk van de landinstellingen van je besturingssysteem. Staat deze in Belgisch/Nederlands dan moet je kommagetallen met een KOMMA(,
) invoeren (dus 9,81
), staat deze in het Engels dan moet je een PUNT(.
) gebruiken (9.81
).
Opgelet 2: In je C# code moet je doubles ALTIJD met een punt schrijven. Dit is onafhankelijk van je taalinstellingen.
Strings bevatten veel ingebouwde functionaliteit. Als je deze leert gebruiken, kan je al snel nuttige programmaatjes voor tekstverwerking schrijven.
Length
De lengte van een string is het aantal symbolen in de weergegeven versie. Je plaatst .Length
achter de string om de lengte te weten te komen. Enkele voorbeelden:
Substring
Een string is een reeks van 0 of meer symbolen in een bepaalde volgorde. Dat wil zeggen dat we elk symbool een nummer kunnen toekennen. Misschien wat vreemd, maar het eerste symbool krijgt nummer 0, het tweede krijgt nummer 1, het derde nummer 2,... en het laatste krijgt een nummer gelijk aan de Length
van de string min één. Dit nummer heet de index van het symbool.
We kunnen een substring (= deel van een string) opvragen door de index van het eerste symbool en de lengte mee te geven als volgt:
Je mag de lengte achterwege laten om vanaf de gegeven index tot het einde van de string te gaan:
Deze methode verandert een string niet. Er is geen enkele methode die dat doet, want je kan een string niet veranderen in C#. Je kan er alleen een nieuwe mee bouwen. De methode berekent wel een nieuwe string met de gewenste eigenschappen. Dit is een belangrijk onderscheid. Volgende drie voorbeelden tonen het verschil. Voer uit en verklaar.
Onthoud het goed: je kan een string niet aanpassen in C#. We kunnen alle gevolgen hiervan nog niet uitleggen, maar het is wel zo.
IndexOf
Een index is, zoals hierboven aangegeven, de positie van een teken in de string. Als we willen weten waar een bepaalde substring in een string voorkomt, gebruiken we IndexOf
:
ToUpper
/ ToLower
Met deze twee methodes bereken je een versie van de string waarop je ze toepast, maar dan in hoofdletters of in kleine letters. Let op: je past de oorspronkelijke string niet aan!
Replace
Met deze methode kan je een substring vervangen door een andere substring. Ook deze methode past de oorspronkelijke tekst niet aan.
TrimStart
/ TrimEnd / Trim
Met deze methodes verwijder je witruimte (spaties, tabs, newlines,...) aan het begin of aan het einde van een string:
Een string is een reeks van 0, 1 of meerdere lettertekens, zoals je ook kan zien als je even met je muis boven een string keyword hovert in je code:
Merk op dat we bij een string literal gebruik maken van aanhalingstekens ("
) terwijl bij chars we een apostrof gebruiken ('
). Dit is de manier om een string van een char te onderscheiden.
Volgende code geeft twee keer het cijfer 1 onder elkaar op het scherm, maar de eerste keer gaat het om de weergave van een string
(reeks van tekens) en de tweede keer van een int
(effectief getal):
De output van dit programma zal dan zijn:
Fout gebruik van strings zal code geven die niet zal gecompileerd worden:
In de tweede toekenning proberen we een literal van het type int toe te kennen aan een variabele van het type string.
In de laatste toekenning proberen we een literal van het type string toe te kennen aan een variabele van het type int.
Al deze oefeningen maak je in een klasse StringsEnHunMethoden
gebruik van variabelen om input en output op te slaan en te tonen
functionaliteit van strings
Een applicatie vraagt je tekst in te voeren die dan daarna zal worden getoond met allemaal hoofdletters.
Noem de methode voor deze oefening VariabelenEnHoofdletters
.
Programmaverloop
Lees de gebruikersinvoer van de console en sla deze op in een variabele.
Zet de inhoud van deze variabele om in hoofdletters. Je kan dit doen door ToUpper()
te gebruiken. Uiteindelijk geef je dan het resultaat weer in de console.
Testscenario's
Voer tekst in met spaties
Voer tekst in van meer dan 100 karakters
Voer tekst in van 1 karakter
Voer geen tekst in
gebruik van string interpolation
Zelfde als oefeningen maaltafels en ruimte vorig hoofdstuk.
Je moet twee methoden schrijven. Noem de eerste MaaltafelsStringInterpolatie
en de tweede RuimteStringInterpolatie
. Deze doen net hetzelfde als hun tegenhangers uit het vorige hoofdstuk, maar je bouwt de getoonde tekst op met stringinterpolatie in plaats van via +
en/of Console.Write
.
Zie oefening H2-maaltafels en H2-ruimte.
Pas string interpolatie m.b.v. $
toe om de veranderlijke onderdelen van de output in te vullen.
Zie oefening H2-maaltafels en H2-ruimte.
gebruik van string interpolation
Een programma vraagt een bedrag en vervolgens btw percentage in te geven waarna het bedrag incl. btw-percentage wordt weergegeven.
Noem de methode voor deze oefening BerekenBtw
.
Het bedrag dat wordt ingevoerd moet geconverteerd worden naar een int
met Convert.ToInt32
.
Pas string interpolatie toe om de output te tonen.
Typ tekst in
Geef een veel te groot bedrag in
functionaliteit van strings leren kennen
We willen tekst omvormen naar een ander formaat. Laat de gebruiker een lijn tekst ingeven en haal er alle tussenliggende spaties uit en vervang de a's door @
Gebruik Console.ReadLine
om tekst in te lezen en hou bij in een variabele. Pas de nodige string methodes toe om het resultaat te verkrijgen. Noem je methode voor dit programma LeetSpeak
.
test met een zin zonder a's
test met een zin met vijf a's of meer
test met een lege string
leren werken met stringinterpolatie
leren werken met methodes van strings
We willen met behulp van een programma instructies genereren voor de gebruiker. Meerbepaald wordt automatisch aangegeven in welke map de gebruiker bepaalde bestanden op een UNIX-achtig systeem moet bijhouden.
Voor deze oefening is het verplicht gebruik te maken van een (geïnterpoleerde) string.
Op basis van de voornaam van de student en de naam van de cursus wordt de map gegeven die de student moet aanmaken (/home/
, eerste 3 letters voornaam, in hoofdletters met submap de naam van de cursus. Noem je methode Instructies.
functionaliteit van strings
stringinterpolatie
De gebruiker voert zijn lottocijfers in. We willen deze op een overzichtelijke manier weergeven.
Laat de lottocijfers allemaal achter elkaar ingeven, gescheiden door komma's, zonder spaties. De gebruiker wordt verondersteld cijfers onder de 10 in te geven voorafgegaan door een nul. Gebruik de juiste methode om de cijfers uit te string te "knippen" en gebruik het karakter |
om de uitvoer te scheiden. Noem je methode Lotto
.
functionaliteit van strings
stringinterpolatie
De gebruiker voert een getal in. Het programma berekent de som van de cijfers in de decimale voorstelling van dit getal.
We veronderstellen dat de gebruiker een getal van exact vijf cijfers ingeeft, desnoods vooraan opgevuld met nullen. Noem je methode SomVanCijfers
.
Onderstaand voorbeeld komt uit op 27, want 6 + 3 + 9 + 2 + 7 is 27.
functionaliteit van strings
stringinterpolatie
De gebruiker voert een e-mailadres in. Jouw programma toont hieruit het gedeelte dat de naam voorstelt, in hoofdletters.
We veronderstellen dat de gebruiker een juist mailadres invult. Noem je methode NaamUitEmail
.
functionaliteit van strings
stringinterpolatie
De gebruiker voert zijn naam in. Je programma toont dan de eerste letter van de voornaam en de familienaam.
We veronderstellen dat de gebruiker een voornaam zonder spaties invult. Noem je methode EersteLetterEnAchternaam
.
functionaliteit van strings
stringinterpolatie
omzetting tussen tekst en getal
De gebruiker voert enkele persoonlijke gegevens in en op basis hiervan wordt een persoonlijke toegangscode gegenereerd.
Noem je methode Toegangscode
. De code van vier tot vijf cijfers wordt als volgt bepaald:
het eerste symbool is de voorlaatste letter van de naam, in kleine letters
het tweede symbool is de laatste letter van de naam, in hoofdletters
het derde symbool is het laatste cijfer van het geboortejaar
het vierde (en eventueel vijfde) symbool is het eerste cijfer van de postcode, in het kwadraat (dus vermenigvuldigd met zichzelf)
Om beslissingen te kunnen nemen in C# moeten we kunnen nagaan of een bepaalde uitspraak waar of niet waar is. Anders gezegd: we moeten stukjes code kunnen schrijven waarvan we achteraf kunnen zeggen of ze "waar" of "niet waar" zijn. Zo'n stukjes code noemen we booleaanse expressies. De meest typische booleaanse expressies schrijf je met de relationele operatoren. Deze stellen vergelijkingen tussen stukjes data voor. Gelukkig ken je deze al uit het lager onderwijs en moet je alleen de notatie hieronder leren:
"Gelijk aan" noteren we met twee symbolen, omdat één symbool gebruikt wordt voor een toekenning.
Deze operatoren leveren je altijd één van twee mogelijkheden als uitkomst: true
of false
. Je kan dit ook uittesten: Console.WriteLine(4 < 7);
of Console.WriteLine(4 < 2);
toont je het verwachte resultaat.
Er bestaan nog simpelere booleaanse expressies dan die met de relationele operatoren: true
en false
zelf zijn hun eigen resultaat en zijn dus technisch gesproken ook booleaanse expressies. Je mag dus bv. ook true
schrijven in plaats van 4 > 2
.
In dit deel zullen we bekijken hoe we ons programma dynamischer kunnen maken met behulp van het if
-statement, al dan niet uitgebreid met else
en else if
.
De if
is een van de elementairste constructies in een programmeertaal. Vrijwel elke programmeertaal bezit deze constructie. We zullen de werking ervan eerst wat algemener bekijken, zodat je het concept goed begrijpt. Daarna zullen we inzoomen op de syntax in C#.
Het basisidee is als volgt: een if
-constructie bevat code die enkel uitvoert als een booleaanse expressie true
oplevert. Met andere woorden: als een voorwaarde naar keuze waar is. We noemen dergelijke code conditionele code, want een conditie is een voorwaarde.
We zullen dit tonen met een Flowgorithm programma. Dit is een beperkte, maar heel visuele programmeertaal. In een Flowgorithm programma start je bij Main
en volg je steeds de pijlen.
De gele box stelt een declaratie voor. Ook in Flowgorithm moet je variabelen declareren. Het blauw parallellogram: de gebruiker geeft iets in (de waarde van wachtwoord
). De rode ruit bevat een booleaanse expressie. Dan is er een vertakking, met daarop de waarden True
en False
. We volgen de pijl met daarop de uitkomst van de booleaanse expressie. Als we dus het juiste wachtwoord intypen, krijgen we de geheime info, anders gebeurt er niets.
Het if
-statement stemt overeen met alles tussen de rode ruit en het rode bolletje. Dus als aan een bepaalde voorwaarde voldaan is, voeren we afgebakende code uit.
Uit dit programma kan je dan ook volgende C#-code afleiden:
In de gegenereerde code stemt de rode ruit dus overeen met de haakjes meteen na if
en stemt de tak True
overeen met de accolades.
Flowgorithm is vrij te downloaden, dus als je moeite hebt met deze concepten, wordt aangeraden hier wat mee te experimenteren.
Er zijn enkele veelgemaakte fouten waar je op moet letten:
De types in je booleanse expressie moeten steeds vergelijkbaar zijn. Volgende code is dus fout: if( "4" > 3)
daar we hier een string
met een int
vergelijken.
Als je geen accolades schrijft, verandert de werking van if
. Het gevolg zal zijn dat enkel het eerste statement na de if
zal uitgevoerd worden indien true
. Gebruiken we de if
van daarnet maar zonder accolades dan zal het tweede statement altijd uitgevoerd worden ongeacht de if
:
Voor ons is het simpel: we schrijven if
altijd met accolades.
Het eerdere voorbeeld toont dat we soms actie willen ondernemen als aan een voorwaarde voldaan is, maar heel vaak willen we een andere actie ondernemen als aan diezelfde voorwaarde niet voldaan is. In dat geval maken we gebruik van de if ... else ...
.
We gaan terug naar onze login. Een fout wachtwoord ingeven heeft bepaalde gevolgen. Dit kan bijvoorbeeld een alarm doen afgaan, omdat indringers soms foute wachtwoorden ingeven tot ze toevallig het juiste vinden. We stellen dit hier voor door een actie toe te voegen als het wachtwoord niet klopt.
De overeenkomstige C# code:
In bovenstaande code stemt de rode ruit dus nog steeds overeen met de haakjes meteen na if
, stemt de tak True
overeen met de accolades vlak na de ronde haakjes en stemt else { ... }
overeen met de tak False
. Anders gezegd: als aan een bepaalde voorwaarde voldaan is, voeren we het eerste blok afgebakende code uit, anders voeren we het tweede blok afgebakende code uit.
Als we iets altijd willen doen, hoort dat niet in een vertakking. Dan zetten we het in de flowchart na het rode bolletje. In ons programma zetten we het dan na de volledige if ... else ...
:
De gegenereerde code is dan:
Het is perfect mogelijk om bepaalde controles enkel te doen als eerdere controles wel of niet gelukt zijn. Dit kan er zo uitzien:
In de gegenereerde code leidt dit tot geneste conditionele code: een if
binnenin een grotere if
of else
. In digt geval gaat het om een if
in een else
. Je mag zo diep nesten als je maar wil. Er is geen technische limiet, maar je code zal wel onleesbaar worden als je overdrijft.
Nesten van conditionele code levert soms code op die moeilijk te ontrafelen is. Het gebeurt vaak dat we een else
nodig hebben met meteen daarin terug een if
, om zo verschillende gevallen af te handelen.
We kunnen een programma maken dat dit demonstreert: Er is niet aan de hoofdvoorwaarde voldaan, maar er is wel aan een andere voorwaarde voldaan:
De gegenereerde code voor dit frament is technisch juist:
Maar Flowgorithm is geen menselijke programmeur. Een menselijke programmeur zou volgende voorstelling gebruiken, die hetzelfde doet, maar makkelijker leesbaar is:
De logische EN, OF en NIET-operatoren die je misschien kent uit de lessen technologie of elektronica kan je ook gebruiken in C#:
Je kan de &&
-operator tussen twee booleaanse expressies zetten om een nieuwe, grote expressie te schrijven die afhangt van de kleinere expressies aan beide kanten. Idem voor de ||
-operator. Je kan de niet-operator voor een booleaanse expressie zetten om het resultaat hiervan om te draaien. Bijvoorbeeld:
Hieronder krijg je nog eens het overzicht van de werking van de AND en OR operatoren:
Wat zal de uitkomst zijn van volgende expressies? (tip: het zal steeds true
of false
zijn, niets anders)
3>2
4!=4
4<5 && 4<3
"a"=="a" || 4>=3
(3==3 && 2<1) || 5!=4
!(4<=3)
true || false
!true && false
Je bent niet beperkt tot het gebruik van expressies met één relationele operator in de voorwaarde van een if. Je mag om het even welk type booleaanse expressie gebruiken. Anders gezegd: als het gedeelte tussen haakjes uiteindelijk maar true of false oplevert, is het goed. We kunnen dus ook gebruik maken van de logische operatoren &&, ||, ! in onze voorwaarden.
Goed gebruik van booleaanse expressies kan onze programma's drastisch vereenvoudigen. We tonen dit met een voorbeeld.
Volgend programma bepaalt of je korting krijgt op het openbaar vervoer. Volg zelf de flowchart om de logica te snappen:
Maar we kunnen dit eenvoudiger zo schrijven:
Deze code zegt dat je korting krijgt als je jonger bent dan 18 of als je ouder bent dan 65. Ze doet hetzelfde als de eerste versie, maar het diagram is eenvoudiger en de gegenereerde code is dat ook. Ook via de andere operatoren &&
en !
kan je gelijkaardige vereenvoudigingen verkrijgen.
Nu we de elementaire zaken van C# en Visual Studio kennen is het tijd om onze programma's wat interessanter te maken. De ontwikkelde programma's tot nog toe waren steevast lineair van opbouw, ze werden lijn per lijn uitgevoerd zonder de mogelijkheid om de flow van het programma aan te passen. Het programma doorliep de lijnen braaf na elkaar en wanneer deze aan het einde kwam sloot het programma zich af.
Onze programma's waren met andere woorden niet meer dan een eenvoudige oplijstingen van opdrachten. Je kan het vergelijken met een lijst die je vertelt over hoe je een brood moet kopen:
Neem geld uit spaarpot
Wandel naar de bakker om de hoek
Vraag om een brood
Krijg het brood
Betaal het geld aan de bakker
Keer huiswaarts
Smullen maar
Alhoewel dit algoritme redelijk duidelijk en goed zal werken, zal de realiteit echter zelden zo rechtlijnig zijn. Een beter algoritme (dat foutgevoeliger is én interactiever voor de eindgebruiker) zal afhankelijk van de omstandigheden (bakker gesloten, geen geld meer, etc.) mogelijke andere stappen ondernemen. Het programma zal beslissingen nemen op basis van aanwezige informatie.
In een typisch project, bijvoorbeeld een bestelsysteem, kunnen we beslissingen gebruiken voor tal van zaken:
als we de voorraad van producten gaan bijhouden en we willen reageren wanneer de voorraad op is
als we op bepaalde dagen van de week kortingen op bepaalde producten willen geven in plaats van de gebruiker deze zelf te laten kiezen
als we spaarpunten willen introduceren en de gebruiker deze kan inwisselen
De locatie waar je een variabele aanmaakt bepaalt de scope, oftewel de gegarandeerde levensduur, van de naam van de variabele. Ruwweg: als je binnen hetzelfde stel accolades werkt waarin een variabele is gedeclareerd, dan is die naam in scope en kan je hem gebruiken. Indien je de variabele dus buiten die accolades nodig hebt, dan heb je een probleem: de variabele is enkel bereikbaar binnen de accolades vanaf het punt in de code waarin het werd aangemaakt.
Zeker wanneer je begint met if
, loops, methoden, etc. zal de scope belangrijk zijn: deze code-constructies gebruiken steeds accolades om codeblocks aan te tonen. Een variabele die je dus binnen een if-blok aanmaakt zal enkel binnen dit blok bestaan, niet erbuiten.
Wil je dus getal ook nog buiten de if
gebruiken zal je je code moeten herschrijven zodat getal
voor de if
wordt aangemaakt:
Zolang je in de scope van een variabele bent kan je geen nieuwe variabele met dezelfde naam aanmaken:
Volgende code is dus niet toegestaan:
Je krijgt de error: A local variable named 'getal' cannot be declared in this scope because it would give a different meaning to 'getal', which is already used in a 'parent or current' scope to denote something else
Enkel de tweede variabele een andere naam geven is toegestaan in het voorgaande geval.
Dit is wel geldig, daar de scope van de eerste variabele afgesloten wordt door de accolades:
Het gebeurt heel vaak dat we een teller bijhouden die aangeeft hoe vaak een lus al doorlopen is. Wanneer de teller een bepaalde waarde bereikt moet de loop afgesloten worden.
Bijvoorbeeld volgende code om alle even getallen van 0 tot 10 te tonen:
Met een for-loop kunnen we deze veel voorkomende code-constructie verkort schrijven.
De syntax van een for
-loop is de volgende:
setup: In het setup gedeelte voeren we code uit die klaar moet zijn voor de lus voor het eerst begint. Dat betekent in de praktijk bijna altijd dat we een loop variabele aanmaken waarmee we bijhouden hoe vaak we al door de lus zijn gegaan. Variabelen die je hier aanmaakt hebben dezelfde scope als de body van de lus. Wanneer de for
eindigt, bestaan ze met andere woorden niet meer.
test: Hier plaatsen we een booleaanse expressie die nagaat of de lus mag worden uitgevoerd. Deze heeft dus niet dezelfde functie als de booleaanse expressie van een while
. Als ze false
oplevert, wordt de body van de lus niet meer uitgevoerd. Dit betekent in de praktijk heel vaak dat we nagaan of de loop variabele al een zekere waarde heeft bereikt.
update: Hier plaatsen we wat er moet gebeuren telkens de loop body is afgewerkt. Meestal zullen we hier de loop variabele verhogen of verlagen.
In Flowgorithm (dit stemt helaas niet 100% overeen met C# omwille van een klein verschil in scope):
Gebruiken we deze kennis nu, dan kunnen we de eerder vermelde code om de even getallen van 0 tot en met 10 tonen als volgt:
Voor de indexvariabele kiest men meestal niet teller
, maar i
, maar dat is niet noodzakelijk. In de setup wordt dus een variabele op een start-waarde gezet. De test zal aan de start van iedere loop kijken of de voorwaarde nog waar is, indien dat het geval is dan wordt een nieuwe loop gestart en wordt i
met een bepaalde waarde, zoals in update aangegeven, verhoogd.
Je kan niets met een for
dat je met een while
niet kan. Je kan niets met een while
dat je met een for
niet kan. Als je dit niet inziet, heb je lussen nog niet begrepen en moet je dit hoofdstuk vanaf het begin opnieuw lezen.
P.S.: wat voor lus is hier net gebruikt?!
Waarom stemt de Flowgorithm hierboven niet perfect overeen met de C#-code? Omdat in de C#-code teller
gedeclareerd wordt als onderdeel van de for. In de code die we uit de Flowgorithm hierboven kunnen genereren, wordt teller
niet gedeclareerd als onderdeel van de lus en blijft deze dus bestaan nadat de lus is afgewerkt. Dit is een klein detail, maar we vinden het belangrijk uit te leggen waarom deze code niet gelijkwaardig is.
Volgend Flowgorithm programma demonstreert hoe een while
lus wordt toegepast wanneer je systematisch boodschappen afwerkt.
Omdat de True
-tak terug leidt naar de controle van de voorwaarde, blijven we dezelfde stappen herhalen tot niet meer aan de voorwaarde voldaan is. Dit is het basisconcept achter een while
lus. Het woord "while" betekent dan ook "zolang als".
Dit is anders dan bij if
: het woordje "if" betekent gewoonweg "als" en houdt dus niet in dat we dezelfde stappen zullen herhalen. Bij while
blijven we dezelfde stappen uitvoeren zo lang dat nodig is.
De syntax van een while loop is eenvoudig:
Waarbij, net als bij een if
statement, de conditie uitgedrukt wordt als een booleaanse expressie.
Zolang de conditie true
is zal de code binnen de accolades uitgevoerd worden. Indien dus de conditie reeds vanaf het begin false
is dan zal de code binnen de while
-loop niet worden uitgevoerd.
Telkens wanneer het programma aan het einde van het while
codeblock komt springt het terug naar de conditie bovenaan en zal de test wederom uitvoeren. Is deze weer true
dan wordt de code weer uitgevoerd. Van zodra de test false
is zal de code voorbij het codeblock springen en na het while
codeblok doorgaan.
De code voor ons boodschappenlijstje ziet er dan ook zo uit:
Een tweede voorbeeld van een eenvoudige while loop:
Zolang myCount
kleiner is dan 100 (myCount < 100
) zal myCount met 1 verhoogd worden en zal de huidige waarde van myCount getoond worden. We krijgen met dit programma dus alle getallen van 1 tot en met 100 op het scherm onder elkaar te zien.
Daar de test gebeurt aan het begin van de loop wil dit zeggen dat het getal 100 nog wel getoond zal worden. Begrijp je waarom? Test dit zelf!
In tegenstelling tot een while loop, zal een do-while loop sowieso minstens 1 keer uitgevoerd worden.
Volgende flowchart helpt je bepalen of je klaar bent om een commit operatie uit te voeren in Git. Zoals je in andere vakken leert, moet je in Git eerst de gewenste bestanden in de staging area zetten. Dat kan soms in één commando gebeuren en soms in meerdere commando's, maar je moet altijd wel minstens één keer stagen.
Een tweede voorbeeld: een opstel schrijven. Dat moet je altijd nog eens nakijken. Als het niet goed is, moet je je tekst aanpassen en opnieuw controleren. Pas wanneer je helemaal tevreden bent, mag je inzenden.
De syntax van een do-while is eveneens verraderlijk eenvoudig:
Merk op dat achteraan de conditie een puntkomma na het ronde haakje staat. Dit is een véél voorkomende fout. Bij een while is dit niet zo! Daar de test van een do-while achteraan de code van de loop gebeurt is het logisch dat een do-while dus minstens 1 keer wordt uitgevoerd. Het volgende eenvoudige aftelprogramma toont de werking van de do-while loop.
Voor het opstel ziet dit er bijvoorbeeld zo uit:
Je kan dit ook doen met een gewone while (je kan elke do while herschrijven met een gewone while), maar dan kost dat meer code. Dat verhoogt dan weer de kans op fouten.
Uiteraard mag de conditie waaraan een loop moet voldoen complexer zijn door middel van de relationele operatoren.
Volgende while
bijvoorbeeld zal de gebruiker aanraden in bed te blijven zolang deze ziek of moe is:
Indien de loop-conditie nooit false
wordt, dan heb je een oneindige loop gemaakt. Soms is dit gewenst gedrag (bijvoorbeeld in een programma dat constant je scherm moet updaten, zoals in een game), soms is dit een bug.
Volgende twee voorbeelden tonen dit:
Een bewust oneindige loop:
Een bug die een oneindige loop veroorzaakt:
Volgende code toont bijvoorbeeld foutief hoe je de som van de eerste 10 getallen (1+2+3+...+10) zou maken:
De correcte manier om dit op te lossen is te beseffen dat de variabele som enkel binnen de accolades van de while-loop gekend is. Op de koop toe wordt deze steeds terug op 0 gezet en er kan dus geen som van alle teller-waarden bijgehouden worden:
Stel je voor dat je, met de kennis die je nu hebt, een boodschappenlijstje moet programmeren. Met andere woorden, een programma dat vraagt om een aantal regels tekst in te typen, waarbij je dan items kan aanduiden als gekocht.
Je zou ruimte kunnen voorzien voor 3 items, voorgesteld als string
, als volgt:
Een groot nadeel van deze aanpak: als je vier items wil ondersteunen, heb je meer code nodig. Als je vijf items wil ondersteunen, heb je nog meer code nodig. Deze code kan geen basis zijn voor een degelijk boodschappenlijstje.
Een tweede mogelijkheid bestaat erin heel het lijstje voor te stellen als een string, als volgt:
Hiermee kan je elk gewenst aantal items toevoegen, maar de voorstelling is erg onhandig. Als je bijvoorbeeld item zeven op je lijstje wil zoeken, moet je plustekens eerst zes plustekens tellen. Als je het lijstje op een andere manier wil tonen aan de gebruiker, bijvoorbeeld met één item per regel, is dat lastig. Als je wil bijhouden of iets gekocht is, kan je daar geen boolean voor gebruiken, maar moet je bijvoorbeeld een vinkje vlak voor het item plaatsen. Dat is allemaal vrij ingewikkelde code voor een vrij eenvoudige taak.
Arrays bieden een oplossing voor deze problemen. Arrays zijn reeksen van waarden, maar ze zijn zelf ook waarden. Met andere woorden, ze maken het mogelijk één variabele te maken die verschillende waarden voorstelt. Hierdoor wordt je code leesbaarder en eenvoudiger in onderhoud. Arrays zijn een zeer krachtig hulpmiddel, maar er zitten wel enkele addertjes onder het gras.
Laat ons beginnen bij het begin: een array is een reeks waarden van één type en heeft een bepaalde lengte. Bijvoorbeeld een reeks van 10 int
s of 5 string
s. Je kan dus geen reeks hebben met een mengeling van int
en string
, of van boolean
en double, of wat dan ook. Je kan een array toekennen aan een variabele, maar je kan ook individuele waarden uit de reeks halen door middel van een index. Dit is een getal dat een positie in de reeks aanduidt, zoals je dat ook kent van de substring
methode.
We zullen het eerst even voordoen in Flowgorithm.
Net als andere datatypes moet je aangeven dat een variabele van een bepaald array type is. Je moet dus de compiler informeren dat er een array bestaat met een bepaalde naam en een bepaald type. Dit is bijna hetzelfde als het declareren van een variabele van een van de types die je al kent, maar je geeft aan dat het om een reeks gaat door middel van rechte haken.
We geven hier enkele voorbeelden:
De rechte haken betekenen dus "een reeks van" en een declaratie van een reeks kan een heleboel declaraties van individuele variabelen vervangen. Met deze voorstelling heb je bijvoorbeeld item1
en item2
en item3
niet meer nodig.
Als we naar de Flowgorithm code van het boodschappenlijstje kijken, zien we dit ook:
Merk wel op: hier vindt ook meteen een initialisatie plaats.
Een initialisatie vertelt de compiler niet alleen dat deze variabele bestaat, maar ook wat zijn eerste waarde is. Een waarde van een array-type stelt eigenlijk een hoeveelheid ruimte in het geheugen voor. Ruimte voor een bepaald aantal strings, voor een bepaald aantal getallen, maakt niet uit. Om technische redenen die we later in meer detail bekijken, moet je deze ruimte reserveren met het keyword new
. Je moet ook zeggen hoe veel ruimte je nodig hebt. Bijvoorbeeld, met de declaraties van hierboven:
Zoals je merkt uit het laatste voorbeeld, mag je deze waarde ook berekenen. Ook het Flowgorithm voorbeeld toont dit, want daar wordt aantalItems
ingelezen.
Nadat de nodige ruimte voorzien is, kan je deze gebruiken. Dit doe je door te zeggen dat een bepaalde waarde op een bepaalde positie in de reeks terechtkomt. Deze positie noemen we de index van het element. Het eerste element krijgt index 0, niet 1. Het laatste krijgt een index gelijk aan de lengte van de array verminderd met 1.
We geven even een voorbeeld rechtstreeks in C#:
Dit hoeven geen vaste waarden te zijn! Je kan ook dit doen:
Dit zorgt dat je code kan uitsparen. Met 10 individuele variabelen, dus zonder array, zou je bovenstaande code ongeveer zo moeten schrijven:
Maar met een array heb je ook deze optie:
En als je je lijstje wil uitbreiden tot 1000 items, moet je alleen de 10 door 1000 vervangen. We zullen nog vaak gebruik maken van arrays en lussen om herhaalde code te vermijden!
Je gebruikt de notatie met rechte haken ook om een array uit te lezen:
Ook hier bespaar je vervelend werk met een lus:
Als je een element probeert uit te lezen dat nog geen waarde gekregen heeft, krijg je een defaultwaarde. Wat voor waarde dat is, hangt af van de array. Er zit een systeem achter, maar dat behandelen we pas later in de cursus. Voorlopig mag je dit onthouden: voor getallen krijg je 0. Voor booleans krijg je false
. Voor strings krijg je een speciale waarde null
, die verschillend is van ""
. intWat je vooral moet weten over null
is dat je er geen eigenschappen van kan opvragen of methodes op kan toepassen. Ik laat even zien wat ik bedoel.
Dus als een variabele null
bevat of kan bevatten, moet je syntax vermijden die hier een punt achter plaatst. Je kan dit op een eenvoudige manier doen met if (mijnVariabele is null) { ... } else { ... }
.
Soms kan het nodig zijn dat je in een later stadium van je programma de lengte van je array nodig hebt. De Length
eigenschap geeft je deze lengte. Volgend voorbeeld toen dit:
Het voordeel hiervan is je maar één keer moet vastleggen hoe groot de array is en daarna in het algemeen over de grootte kan spreken. Zo kan je bij het aanpassen van je code niet vergeten overal de juiste aanpassing te doen. Let op: de lengte geeft niet aan hoe veel items je al hebt ingevuld! Ze geeft aan hoe veel plaats er maximaal is in de array.
Al deze oefeningen maak je in een klasse Beslissingen
flowchart omzetten naar conditionele code
Eenvoudige conditie
Uitbreiding: samengestelde booleaanse expressie
Functionele analyse
Basis: Maak een programma dat aan de gebruiker vraagt hoeveel paar schoenen hij/zij wenst te kopen. Ieder paar schoenen kost normaal 50 euro. Indien de gebruiker 2 paar of meer koopt, is er 10% korting. Toon aan de gebruiker de totale prijs.
Uitbreiding: Breid in een tweede stap je programma uit zodat de korting enkel geldt als de gebruiker een klantenkaart heeft.
Technische analyse
Maak een methode met de naam SchoenenVerkoper
. Maak gebruik van een if.
Zet volgende flowchart om in code. Een Real
in Flowgorithm stemt overeen met een double
in C#.
Basis:
Uitbreiding:
Basis:
Uitbreiding:
flowchart omzetten naar conditionele code
tweevoudige conditie
gebruik modulo
Maak een programma dat aan de gebruiker een geheel getal vraagt. Het programma geeft terug of het ingegeven getal even of oneven is.
Maak een methode met de naam EvenOneven
.
Een getal is even als de rest van de deling door 2 nul is. Hiervoor kan je de modulo operator gebruiken. Het teken voor de modulo is het %
-teken.
Voorbeelden:
7%2 geeft 1 => 7 is oneven
8%2 geeft 0 => 8 is even
Zet volgende flowchart om in code:
flowchart omzetten naar conditionele code
meervoudige conditie
Maak een programma dat aan de gebruiker een geheel getal vraagt. Het programma geeft terug of het ingegeven getal positief, negatief of 0 is.
Maak een methode met de naam PositiefNegatiefNul
.
Maak gebruik van een if
– else if
- else
.
Zet volgende flowchart om in code:
Voorbeeldinteractie(s)
meervoudige conditie
gebruik van else if
De formule is: BMI = gewicht / lengte².
Je toont niet enkel de BMI maar ook een beoordeling over de BMI:
- BMI lager dan 18,5 => ondergewicht
- BMI vanaf 18,5 maar lager dan 25 => normaal gewicht
- BMI vanaf 25 maar lager dan 30 => overgewicht
- BMI vanaf 30 maar lager dan 40 => zwaarlijvig
- BMI hoger dan 40 => ernstige obesitas
Maak een methode met de naam BMIBerekenaar
.
Maak gebruik van een if
– else if
– else if
…
meervoudige conditie
samengestelde booleaanse expressie
gebruik van else if
Maak een programma om van 3 ingegeven getallen, het grootste te bepalen.
Maak een methode met de naam GrootsteVanDrie
.
Maak gebruik van een if
– else if
- else
Voorbeeldinteractie
conditie
samengestelde booleaanse expressie
Maak een programma waarmee je aan de gebruiker het resultaat van 3 examens opvraagt. De opgevraagde resultaten zijn de behaalde punten op 100. Om te slagen moet de student een gemiddelde van meer dan 50% hebben én maximaal 1 onvoldoende.
Maak een methode met de naam Examens
.
Maak gebruik van een if
– else
.
Voorbeeldinteractie
- conditionele berekeningen
De Wet van Ohm houdt in dat een elektrische stroom (voorgesteld als I) gelijk is aan een spanningsverschil (U) gedeeld door een weerstand (R), dus I = U / R.
Vraag aan de gebruiker wat hij wenst te berekenen: Spanning, Weerstand of Stroomsterkte. Vraag vervolgens de twee andere waarden. Als dus de gebruiker "Spanning" kiest, vraag je aan de gebruiker de waarden van de stroomsterkte en de weerstand. Bereken m.b.v. de Wet van Ohm de gewenste waarde en toon aan de gebruiker.
Maak een nieuwe methode genaamd WetVanOhm
.
Denk eraan dat de gegeven formule wiskundig gedefinieerd is. Houd rekening met het feit dat deze drie maten uitgedrukt kunnen worden in kommagetallen.
- Conditionele functionaliteit
We willen dat de gebruiker een menu ter beschikking heeft om eerst te kiezen uit welk hoofdstuk er een oefening moet gekozen worden en vervolgens welke oefening er moet uitgevoerd worden.
Maak in je klasse Program een hoofdmenu. Deze methode laat de gebruiker kiezen uit één van de hoofdstukken. Na de keuze wordt het scherm leeggemaakt en dan wordt de methode Keuzemenu
van de juiste klasse opgeroepen.
Elke klasse die je tot hiertoe in dit project gemaakt hebt, voorzie je dus van een methode Keuzemenu, zodat de gebruiker kan kiezen uit de oefeningen (=methodes) binnen het gekozen hoofdstuk.
Een bewerking die een waarde kan opleveren is een expressie. Anders gezegd: een expressie in C# is iets dat je kan toekennen aan een variabele. Bijvoorbeeld 3+2
, Console.ReadLine()
, of 7
. Als je kan zeggen dat een bewerking een duidelijk resultaat heeft, is het waarschijnlijk een expressie.
Meestal zal je expressies schrijven waarin je bewerkingen op en met variabelen uitvoert. Vervolgens zal je het resultaat van die expressie willen bewaren voor verder gebruik in je code.
Voorbeeld van expressie-resultaat toekennen:
Hierbij zal de temperatuur uit de rechtse 2 variabelen worden uitgelezen, van elkaar wordt afgetrokken en vervolgens bewaard worden in temperatuursVerschil.
De voorgaande code kan ook langer geschreven worden als:
Een ander voorbeeld van een expressie-resultaat toewijzen maar nu met literals (stel dat we temperatuursVerschil reeds hebben gedeclareerd eerder):
Uiteraard mag je ook combinaties van literals en variabelen gebruiken in je expressies:
Operators in C# zijn de welgekende 'wiskundige bewerkingen' zoals optellen (+
), aftrekken (-
), vermenigvuldigen (*
) en delen (/
). Deze volgen de wiskundige regels van volgorde van berekeningen:
Haakjes
Vermenigvuldigen, delen: *
(vermenigvuldigen), /
(delen)
Optellen en aftrekken: +
en -
(etc.)
Net zoals in de wiskunde kan je in C# met behulp van de haakjes verplichten het deel tussen de haakjes eerst te doen, ongeacht de andere operators en hun volgorde van berekeningen:
Je kan nu complexe berekeningen doen door literals, operators en variabelen samen te voegen. Bijvoorbeeld om te weten hoe zwaar je je op Mars zou voelen:
De types die je in je berekeningen gebruikt bepalen ook het type van het resultaat. Als je bijvoorbeeld twee int
variabelen of literals optelt zal het resultaat terug een int
geven.
Je kan echter geen kommagetallen aan int
toewijzen. Als je dus twee double
variabelen deelt is het resultaat terug een double
en zal deze lijn een fout geven daar je probeert een double
aan een int
toe te wijzen:
Wat als je een int
door een int
deelt? Het resultaat is terug een int
. Je bent gewoon alle informatie na de komma kwijt. Kijk maar:
Er zal 4
op het scherm verschijnen! (niet 4.5
daar dat geen int
is).
Wat als je datatypes mengt? Als je een berekening doet met een int
en een double
dan zal C# het meest algemene datatype kiezen. In dit geval een double. Volgende code zal dus werken:
Volgende niet:
Wil je dus het probleem oplossen om 9 te delen door 2 dan zal je minstens 1 van de 2 literals of variabelen door een double moeten omzetten. Het voorbeeld van hierboven herschrijven we dan naar:
En nu krijgen we wel 4.5
.
Het kan subtiel worden in grotere berekeningen.
Stel dat ik afspreek dat je van mij de helft van m'n salaris krijgt. Ik verdien (fictief) 10000 euro per maand. Ik gebruik volgende formule:
Hoeveel krijg je van me? 0.0 euro!
De volgorde van berekeningen zal eerst het gedeelte tussen de haakjes doen: 1 delen door 2 geeft 0, daar we een int
door een int
delen en dus terug een int
als resultaat krijgen. Vervolgens zullen we deze 0
vermenigvuldigen met 10000.0
. We vermenigvuldigen weliswaar een double
(het salaris) met een int
, maar die int
is reeds 0
en we krijgen dus 0.0
als resultaat.
Wil je het dus eerlijk spelen dan zal je de formule moeten aanpassen naar:
Nu krijgt het gedeelte tussen de haakjes een double
als resultaat, namelijk 0.5
dat we dan kunnen vermenigvuldigen met het salaris om 5000.0
te krijgen.
Debugging is een essentiële skill. Bekijk onderstaande clip en zorg dat je de werking van elk gedemonstreerd onderdeel begrijpt. Gebruik voortaan altijd de debugger als je programma wel uitvoert, maar verkeerde resultaten produceert vooraleer je de lector aanspreekt.
Intussen kan je hopelijk stap voor stap een Flowgorithm programma in je hoofd uitvoeren. Als je de software gedownload hebt, kan je dit ook op je machine doen door telkens F6 te gebruiken. Je kan ook op elk punt in het programma kijken wat de waarden van je variabelen zijn via de knop met het symbooltje x=
.
Voor C#-programma's is er een gelijkaardige tool in Visual Studio Code: de debugger. De naam is wat misleidend: deze zal geen bugs verwijderen uit je programma, maar zal je wel helpen stap voor stap door je programma te gaan. Als je programma bugs vertoont, is de kans groot dat je op deze manier de oorzaak kan vinden.
Dit wordt geïllustreerd door volgende code:
Eerst zet je ergens in deze code een breakpoint. Dit ziet er uit als een rood bolletje naast de code waar je programma moet "stilstaan". Je doet dit door op de plaats van het bolletje te klikken.
Dan start je je programma op via "Run -> Start debugging" (of via F5). De regel waarop je code gepauzeerd is, wordt aangeduid.
In dit geval is er maar één instructie: "voer MethodeA
uit". We kunnen dit als één geheel doen via "step over" of we kunnen ook de stappen binnenin MethodeA
bekijken via "step into".
In dit geval kunnen we in detail kijken hoe MethodeA
werkt via "step into". We kunnen "step into" niet altijd uitvoeren: we hebben hiervoor toegang nodig tot de broncode van de instructie die we verder willen bekijken.
Door enkele stappen uit te voeren, kunnen we pauzeren op de toekenning van getal2
:
Op dit punt in de code, kunnen we onder "variables" bekijken welke waarden onze variabelen op dit moment hebben (merk op: de toekenning van 13 is nog niet gebeurd, we staan stil vlak ervoor):
En via de "call stack" zien we dat we niet gewoon in MethodeA
zitten, maar dat MethodeA
is opgeroepen door Main
:
Er komen geen oefeningen specifiek rond debuggen. Dat gaat niet, want debuggen helpt je alleen informatie verzamelen. Toch is het een zéér, zéér belangrijke skill. Debuggen kan je helpen inzien wat je programma doet. Er worden geen rechtstreekse punten op gegeven, maar als je goed debugt, kan je opdrachten afwerken die je anders niet zou kunnen maken. Gedraagt je programma zich tijdens de labosessies niet zoals je verwacht? Zet een breakpoint en kijk wat er aan de hand is!
vind je een tabel terug die uitlegt welke operaties voorrang hebben.
Volgende document bevat een overzicht van de basis C# syntax zaken van het eerste en (deel van het) tweede semester:
: Verplichte app! Simple as that!
Net zoals SoloLearn maar dan anders.
(aanrader!) Origineel vooral bedoeld om spreektalen te leren, maar bevat ook tal van andere zaken. Hoofdzakelijk nuttig om nieuwe aspecten te 'drillen'. Enkel dus nuttig indien je de basismaterie eerst hebt verwerkt. Bekijk zeker ook de tal van andere cursussen die er staan. Let er op dat je bij de filter Engels instelt, er zijn nog niet veel (goede) Nederlandstalige C# cursussen naar mijn weten. Opgelet: je kan je enkel via de browser inschrijven op niet-spreektaal-cursussen. De app toont enkel spreektaalcursussen.
Speels en vrij beperkt in gratis versie, maar ideale aanvulling op SoloLearn.
Een steam spel om te leren programmeren. Weliswaar Javascript (nuttig voor Web Programming) maar het concept is te cool om niet hier te vermelden en zoals je zal ontdekken: leren programmeren kan je in eender welke taal, en het zal ook je andere programmeer-ervaring verbeteren. Give it a go!
(specifiek voor C#!)
(behandelt leerstof van volledig eerste jaar en meer)
Pittige programmeeroefeningen die jaarlijks in december verschijnen.
Handig vorm gegeven gratis ebooks met tal van onderwerpen waaronder ook C# en het .NET Framework.
: De uitgebreidere, Engelstalige variant van deze cursus zeg maar.
: Aanrader.
: Zeer aan te raden indien je een bepaald concept uit de les niet begrijpt.
: Nederlandstalige cursus met veel toffe oefeningen waarvan je sommige zelfs in deze cursus zal terugvinden.
: Microsoft heeft een virtual academy cursus "C# fundamentals" uitgebracht. Ik kan deze cursus zeer erg aanbevelen.
: Zeer vermakelijk, vlot geschreven C# boek(je)
: "This site tries to gather open-source remakes of great old games in one place." Je vindt er ook tal van C# projecten terug zoals .Klik bovenaan op "languages" en filter maar eens op C#.
: nog steeds relevant.
: deze programmeur heeft een volledige RPG gemaakt en het hele proces gestreamd.
Opmerking .NET 5.0 Runtime kan je ook afzonderlijk installeren via
Flowgorithm is een visuele programmeertaal. We zullen deze gebruiken om bepaalde concepten duidelijk zichtbaar te maken. Je moet de Windows Installer van halen. Tijdens de installatie klik je gewoonweg elke keer op "Next".
Gebruikers van Mac OS installeren dit best via of in een virtuele machine. Als dit echt niet lukt, kunnen ze tijdelijk gebruiken, maar deze software werkt wat anders en ondersteunt geen arrays.
Een voorbeeld van hoe je begint te werken met Flowgorithm kan je hier bekijken.
We baseren ons op . Je hoeft deze conventies niet te lezen. Hieronder volgt een vereenvoudigd overzicht van de afspraken die aan bod kunnen komen in de cursus en enkele eigen conventies:
sleutelwoorden (keywords) In de editor zie je dat sommige woorden in blauw worden weergegeven. Visual Studio geeft alle sleutelwoorden in het blauw weergeven. Sleutelwoorden zijn vooraf gedefinieerde, gereserveerde woorden die een speciale betekenis hebben voor de C# compiler. Je kan ze niet als namen (identifiers) in je programma gebruiken. Een lijst van alle gereserveerde sleutelwoorden vind je op .
namespace
Letterlijk vertaald is dat een naamruimte. Deze structuur biedt de mogelijkheid om namen van o.a. klassen en methode - in programmeerjargon spreekt men van identifiers - in een naamspace te groeperen. Je moet er dan enkel voor zorgen dat deze namen uniek zijn binnen de namespace.
De regels en afspraken voor het geven van namen aan namespaces vind je op .
Je kan een namespace vergelijken met een familienaam. Binnen de 'naamruimte' van een familienaam moeten de voornamen uniek zijn. Maar dezelfde voornaamen kunnen in verschillende familienamen gebruikt worden: De namespace in het voorbeeld hierboven is BeginnenMetCSharp
. Visual Studio gebruikt de naam van het project voor het benoemen van deze namespace.
klasse
Gegevens (data) en algoritmes (programma's) worden ondergebracht in klassen. Dit maakt een programma overzichtelijker. In het tweede semester gaan we dieper in op klassen en objecten ('exemplaren' van een klasse). In het eerste semester gebruiken we klassen alleen om code te ordenen.
De klassenaam in het voorbeeld hierboven is Program
. Deze klassenaam wordt door Visual Studio standaard gebruikt wanneer je een nieuw project aanmaakt.
De regels en afspraken voor het geven van namen aan klassen vind je op .
naamgeving De namen van methoden worden in pascalnotatie geschreven, meer info op .
De verschillende datatypes bespreken we in een volgend .
Er zijn geen vaste afspraken over hoe je je variabelen moet noemen toch hanteren we enkele coding guidelines die doorheen je opleiding moeten gevolgd worden. Naarmate we meer C# leren zullen er extra guidelines bijkomen (zie ).
We zullen het bool
datatype erg veel nodig hebben wanneer we met zullen werken, specifiek de die afhankelijk van de uitslag van een bool
bepaalde code wel of niet zullen doen uitvoeren.
Meer info vind je later in .
Let er op dat de van variabelen bij loops zeer belangrijk is. Indien je een variabele binnen de loop definieert dan zal deze steeds terug "verdwijnen" wanneer de cyclus van de loop is afgewerkt. Latere declaraties voor variabelen met dezelfde naam hebben niets meer te maken met de oorspronkelijke variabele.
Scope is al in het algemeen behandeld, maar zeker in loops worden er veel fouten tegen gemaakt. Daarom beklemtonen we het hier nog eens.
Maak een programma om de BMI van de gebruiker te berekenen. () De BMI wordt berekend aan de hand van de lengte en het gewicht van de persoon.
identifier
toegelaten?
uitleg indien niet toegelaten
werknemer
ja
kerst2018
ja
pippo de clown
neen
geen spaties toegestaan
4dPlaats
neen
mag niet starten met getal
_ILOVE2019
ja
Tor+Bjorn
neen
enkel cijfers, letters en liggende streepjes toegestaan
ALLCAPSMAN
ja
B_A_L
ja
class
neen
gereserveerd keyword
WriteLine
ja
Type
Geheugen
Bereik
Meer info
sbyte
8 bits
-128 tot 127
byte
8 bits
0 tot 255
short
16 bits
-32768 tot 32767
ushort
16 bits
0 tot 65535
int
32 bits
-2 147 483 648 tot 2 147 483 647
uint
32 bits
0 tot 4294967295
long
64 bits
-9 223 372 036 854 775 808 tot 9 223 372 036 854 775 807
ulong
64 bits
0 tot 18 446 744 073 709 551 615
char
16 bits
0 tot 65535
Soort data
Notatie
Enkelvoud?
Vorm
Default zichtbaarheid
Klasse
PascalCase
altijd
[A-z]+[0-9]
public
Methode
PascalCase
meervoud toegestaan
[A-z]+[0-9]
public
Argument
camelCase
meervoud toegestaan
[A-z]+[0-9]
niet van toepassing
Lokale variabele
camelCase
meervoud toegestaan
[A-z]+[0-9]
niet van toepassing
Constante
PascalCase
altijd
[A-z]+[0-9]
public
Veld
camelCase
meervoud toegestaan
[A-z]+[0-9]
private
Property
PascalCase
meervoud toegestaan
[A-z]+[0-9]
public
Enum type
PascalCase
altijd
[A-z]+
public
Waarde enum
PascalCase
altijd
[A-z]+
niet van toepassing
abstract
as
base
bool
break
byte
case
catch
char
checked
class
const
continue
decimal
default
delegate
do
double
else
enum
event
explicit
extern
false
finally
fixed
float
for
foreach
goto
if
implicit
in
int
interface
internal
is
lock
long
namespace
new
null
object
operator
out
override
params
private
protected
public
readonly
ref
return
sbyte
sealed
short
sizeof
stackalloc
static
string
struct
switch
this
throw
true
try
typeof
uint
ulong
unchecked
unsafe
ushort
using
using static
virtual
void
volatile
while
C#-syntax
Betekenis
>
groter dan
<
kleiner dan
==
gelijk aan
!=
niet gelijk aan
<=
kleiner dan of gelijk aan
>=
groter dan of gelijk aan
gewenst type
methode
int
ToInt32
double
ToDouble
string
ToString
boolean
ToBoolean
byte
ToByte
C#-syntax
Betekenis
&&
en-operator
||
of-operator
!
niet-operator
AND
niet waar
waar
niet waar
niet waar
niet waar
waar
niet waar
waar
OR
niet waar
waar
niet waar
niet waar
waar
waar
waar
waar
Iedere array heeft een aantal methoden waar meteen gebruik van gemaakt kan worden. Al deze methoden hier beschrijven zou ons te ver nemen, maar enkele methoden zijn echter zeer handig om te gebruiken en hebben een vanzelfsprekende betekenis: Max()
, Min()
, Sum()
en Average()
.
Volgende code geeft bijvoorbeeld het grootste getal terug uit een array genaamd "leeftijden".
Met de rest kan je het minimum, de som en het gemiddelde bepalen.
Daarnaast beschikken arrays ook over functionaliteit die niet specifiek gelinkt is aan getallen. We bekijken hier een paar mogelijkheden die heel vaak van pas komen. Voor deze methoden is de syntax, om redenen die we later zullen zien, wat anders: je schrijft eerst Array.
en je geeft de array in kwestie mee tussen de haakjes.
Om arrays te sorteren roep je de Sort()
-methode op als volgt, als parameter geef je de array mee die gesorteerd moet worden.
Volgende voorbeeld toont hier het gebruik van:
Wanneer je de Sort-methode toepast op een array van strings dan zullen de elementen alfabetisch gerangschikt worden. Merk op dat je niet meteen iets merkt nadat je Sort
hebt opgeroepen. Intern zijn er normaal elementen van plaats gewisseld, maar je ziet dit pas als je bijvoorbeeld de elementen afprint.
Met de Array.Reverse()
-methode kunnen we dan weer de elementen van de array omkeren (dus het laatste element vooraan zetten en zo verder.
Ook hier zie je pas iets als je de elementen toont.
De IndexOf
-methode maakt het mogelijk om te zoeken naar de index van een gegeven element in een index. De methode heeft twee parameters, enerzijds de array in kwestie en anderzijds het element dat we zoeken. Als resultaat wordt de index van het gevonden element teruggegeven. Indien niets wordt gevonden zal het resultaat (in de praktijk) -1 zijn.
Volgende code zal bijvoorbeeld de index teruggeven van het item "koffie"
indien dit in de array items
staat:
Volgend voorbeeld toont het gebruik van deze methode:
IndexOf
kan erg van pas komen om stukjes informatie aan elkaar te linken. Bijvoorbeeld: de prijs van de producten staat steeds op dezelfde index in de andere array:
We weten nu genoeg om de gebruiker de prijs van elk product te kunnen tonen.
We tonen nu hoe we eerst het juiste product zoeken en dan vervolgens die index bewaren en gebruiken om de prijs te tonen:
Standaard weigert de compiler het gebruik van variabelen die niet geïnitaliseerd zijn. Probeer volgende code maar eens in een testprogramma:
Je krijgt een foutmelding en je programma voert niet uit. Lees de tekst van de foutmelding. Er wordt uitgelegd dat je naam
probeert te gebruiken zonder er een waarde aan te geven. Een variabele moet dus geïnitialiseerd zijn.
We hebben eerder gezien dat elementen van een array ongeveer gebruikt kunnen worden als variabelen, bijvoorbeeld:
Maar hier is een aandachtspuntje: getallen[1]
, getallen[2]
, enzovoort zijn nog steeds niet geïnitialiseerd. Anderzijds is de grotere structuur getallen
wel geïnitialiseerd. Kunnen we ze dan wel al gebruiken?
Het antwoord is ja. De echte variabele getallen
is geïnitialiseerd. Je kan dit uittesten en er zal meteen its opvallen:
Alle niet-ingevulde getallen hebben een defaultwaarde gekregen, namelijk 0.
In het algemeen krijgen niet-geïnitialiseerde waarden een defaultwaarde. Welke waarde dat is, hangt af van hun type. Er bestaan verschillende categorieën van types die deze waarde vastleggen, maar voorlopig mag je het volgende onthouden:
elk getaltype
0
bool
false
string
null
elk type array
null
Random
null
null
is een speciale waarde die eigenlijk betekent "er is geen waarde". Van een ontbrekende waarde kunnen we geen eigenschappen of methoden opvragen. Daarom crasht volgende code, die identiek gestructureerd is aan het vorige voorbeeld:
Het probleem is dat we null.ToString()
oproepen. Let dus op: je mag geen eigenschap of methode gebruiken door een punt achter een waarde te zetten die null
bevat. Je kan hier op controleren met is null
:
Een List<>
collectie is de meest standaard collectie die je kan beschouwen als een flexibelere variant op een een doodnormale array.
De Generieke List<>
klasse bevindt zich in de System.Collections.Generic
namespace. Je dient deze namespace dus als using
bovenaan toe te voegen wil je deze klasse kunnen gebruiken.
De klasse List<T>
is een zogenaamde generieke klasse. Tussen de < >
tekens plaatsen we het type dat de lijst zal moeten gaan bevatten. Vaak wordt dit genoteerd als T
voor "type". Bijvoorbeeld:
List<int> alleGetallen= new List<int>();
List<bool> binaryList = new List<bool>();
List<Pokemon> pokeDex = new List<Pokemon>();
List<string[]> listOfStringarrays = new List<string[]>();
Zoals je ziet hoeven we bij het aanmaken van een List
geen begingrootte mee te geven, wat we wel bij arrays moeten doen. Dit is een van de voordelen van List
: ze groeien mee. Als we toch een begingrootte meegeven (zoals in de kennisclip even getoond wordt) is dat enkel om de performantie van de code wat te verhogen in bepaalde scenario's. Wij gaan dit nooit doen.
Via de Add()
methode kan je elementen toevoegen aan de lijst. Je dient als parameter aan de methode mee te geven wat je aan de lijst wenst toe te voegen. Deze parameter moet uiteraard van het type zijn dat de List
verwacht.
In volgende voorbeeld maken we een List aan die objecten van het type string mag bevatten en vervolgens plaatsen we er twee elementen in.
Het leuke van een List is dat je deze ook kan gebruiken als een gewone array, waarbij je met de indexer elementen kan aanspreken. Stel bijvoorbeeld dat we een lijst hebben met minstens 4 strings in. Volgende code toont hoe we de string op positie 3 kunnen uitlezen en hoe we die op positie 2 overschrijven:
Ook de klassieke werking met for
blijft gelden. De enige aanpassing is dat List<T>
niet met Length
werkt maar met Count
.
Interessante methoden en properties voorts zijn:
Clear()
:methode die de volledige lijst leegmaakt
Insert()
: methode om element op specifieke plaats in lijst toe te voegen, bijvoorbeeld:
voegt de string toe op de tweede plek en schuift de rest naar achter
Contains()
: geef als parameter een specifiek object mee (van het type T
dat de List<T>
bevat) om te weten te komen of dat specifieke object in de List<>
terug te vinden is. Indien ja dan zal true worden teruggeven.
IndexOf()
: geeft de index terug van het element item in de rij. Indien deze niet in de lijst aanwezig is dan wordt -1 teruggegeven.
RemoveAt()
: verwijder een element op de index die je als parameter meegeeft.
Remove():
verwijder het gegeven element
Contains, Remove
en IndexOf
zullen zich met jouw eigen klassen niet noodzakelijk gedragen zoals je verwacht. De verklaring hierachter komt later aan bod, wanneer we Equals en GetHashCode bekijken. Ze zullen wel werken zoals verwacht voor voorgedefinieerde types, inclusief DateTime
.
Je kan met een eenvoudige for
of while-loop over een lijst itereren, maar het gebruik van een foreach-loop is toch handiger.
Dit is dan ook de meestgebruikte operatie om eenvoudig en snel een bepaald stuk code toe te passen op ieder element van de lijst:
Er bestaat strikt gesproken geen meerdimensionale List<T>
, maar een lijst kan wel andere lijsten bevatten. Je kan de functionaliteit ervan dus nabootsen.
De reeds besproken manier om arrays te maken is veelzijdig en toont alle aspecten, maar vraagt vrij veel schrijfwerk. Er zijn nog manieren om arrays aan te maken, maar deze veronderstellen dat je de array in één keer kan opvullen.
Manier 1: met new
Ook op deze manier moet je array gedeclareerd worden als volgt:
Dit is hetzelfde als eerder. Je kan bijvoorbeeld volgende concrete code hebben:
Nu kan je deze array meteen initialiseren op een andere manier dan eerder. Je kan kiezen de grootte niet in te vullen en de gebruikte waarden tussen accolades te zetten, bijvoorbeeld:
Er staan vijf elementen tussen de accolades, dus de compiler kan achterhalen dat je een nieuwe array van vijf elementen wil maken, zonder dat je dat uitdrukkelijk zegt.
Manier 2: verkorte declaratie en initialisatie
Indien je direct waarden wilt toekennen (initialiseren) tijdens het aanmaken van de array zelf dan mag dit ook. We noemen dit de array literal syntax:
Hier zal je array dus een vaste lengte van 5 elementen hebben. Merk op dat deze manier dus enkel werkt indien je al weet hoe groot je array moet zijn voor je je programma opstart. De andere opties bieden in dat opzicht meer flexibiliteit ten koste van meer schrijfwerk.
In dit hoofdstuk kijken we naar meer gevorderde operaties die vooral te maken hebben met getallen.
Een groot deel van je leven als ontwikkelaar zal bestaan uit het bewerken van variabelen in code. Meestal zullen die bewerkingen de vorm aannemen van berekeningen. De Math
klasse zal ons hier bij kunnen helpen.
De Math klasse bevat aardig wat handige methoden. Deze klasse bevat methoden voor een groot aantal typische wiskundige methoden (sinus, cosinus, vierkantswortel, macht, afronden, etc.) en kan je dus helpen om leesbaardere expressies te schrijven.
Stel dat je de derde macht van een variabel getal
wenst te berekenen. Zonder de Math-klasse zou dat er zo uitzien:
Met de klasse kunnen we schrijven:
Het voordeel is dat je dit makkelijk kan aanpassen naar een hogere macht.
De Math klasse ontdekken
Als je in Visual Studio Code Math
schrijft, gevolgd door een punt .
krijg je alles te zien wat de Math-klasse kan doen.
Methoden gebruiken
Schrijf de Methode zonder argumenten. Bijvoorbeeld Math.Pow()
(je mag de rode error negeren).
Je krijgt nu de help-files te zien van deze methode op MDSDN.
Klik desnoods op de pijltjes voor de verschillende versies van deze methode.
PI (π)
Ook het getal Pi (3.141...
) is beschikbaar in de Math klasse. Dit is geen methode, maar een gewone waarde. Het woordje const
betekent dat je deze waarde niet kan aanpassen.
Je kan deze als volgt gebruiken in berekeningen:
"Klassiek" afronden is afronden tot het dichtstbijzijnde getal. Dit doe je met Math.Round
. Deze methode heeft twee parameters: het getal dat je wil afronden en het aantal cijfers na de komma dat je wil bewaren. Let hierbij goed op: dit berekent een nieuwe waarde (van type double
) met de gewenste precisie, maar zorgt er niet automatisch voor dat deze waarde ook met dat aantal cijfers getoond wordt. Anders gezegd: Math.Round(12.0, 2)
kan exact voorgesteld worden met hooguit twee cijfers na de komma, maar wordt standaard niet getoond met twee cijfers na de komma. Dat laatste behandelen we verder bij stringformattering.
Naast "klassiek" afronden kan je ook zuiver in één richting afronden. Dan krijg je het dichtstbij gelegen gehele getal waarvoor geldt benadering =< getal (bij naar beneden afronden) of benadering >= getal (bij naar boven afronden). Dit doen we met Math.Floor
en Math.Ceiling
. Bijvoorbeeld: Math.Floor(12.6)
is 12.0
, ook al is 13 een benadering die dichter bij ligt. Dat komt omdat 12 =< 12.6, terwijl 13 > 12.6.
Volgende sectie is grotendeels gebaseerd op het volgende artikel.
Wanneer je een methode aanroept is de volgorde van je argumenten belangrijk: deze moeten meegeven worden in de volgorde zoals de methode parameters ze verwachten.
Met behulp van named parameters kan je echter expliciet aangeven welke argument aan welke methode-parameter moet meegegeven worden.
Stel dat we een methode hebben met volgende signatuur:
Zonder named parameters zou een aanroep van deze methode als volgt kunnen zijn:
We kunnen named parameters aangeven door de naam van de parameter gevolg door een dubbel punt en de waarde. Als we dus bovenstaande methode willen aanroepen kan dat ook als volgt met named parameters:
of ook:
Je mag ook een combinatie doen van named en gewone parameters, maar dan is de volgorde belangrijk: je moet je dan houden aan de volgorde van de methode-volgorde. Je verbetert hiermee de leesbaarheid van je code dus (maar krijgt niet het voordeel van een eigen volgorde te hanteren). Enkele voorbeelden:
Enkele NIET GELDIGE voorbeelden:
Soms wil je dat een methode een standaard waarde voor een parameter gebruikt indien de programmeur in z'n aanroep geen waarde meegaf. Dat kan met behulp van optionele of default parameters.
Je geef aan dat een parameter optioneel is door deze een default waarde te geven in de methode-signatuur. Deze waarde zal dan gebruikt worden indien de parameter geen waarde van de aanroeper heeft gekregen.
Optionele parameters worden steeds achteraan de parameterlijst van de methode geplaatst .
In het volgende voorbeeld maken we een nieuwe methode aan en geven aan dat de laatste parameters optioneel zijn. discountPercentage
past een "gewone" korting toe op de aankoop van het aantal items. doubleDiscount
is een speciale extra korting die het percentage verdubbelt. We veronderstellen dat items meestal verkocht worden zonder korting:
Volgende manieren zijn nu geldige manieren om de methode aan te roepen:
Je mag enkel de optionele parameters van achter naar voor weglaten. Volgende aanroep is dus niet geldig:
Door de argumenten te benoemen, kunnen we dit indien gewenst omzeilen. Volgende aanroep is wel geldig:
Method overloading wil zeggen dat je een methode met dezelfde naam en returntype meerdere keren definieert maar met andere parameters qua type en aantal. De compiler zal dan zelf bepalen welke methode moet aangeroepen worden gebaseerd op het aantal en type parameters dat je meegeeft.
Volgende methoden zijn overloaded:
Afhankelijk van de aanroep zal dus de ene of andere uitgevoerd worden. Volgende code zal dus werken:
Method overloading is de reden waarom je in Visual Studio Code de documentatie van meerdere versies van een methode kan bekijken. Dit doe je door op de pijltjes naast de info over die methode te klikken.
Met meerdimensionale arrays kunnen we de richtingen los van elkaar bekijken. Bijvoorbeeld, om ons OXO-rooster af te printen (eerste rij op één regel, tweede rij op een volgende regel, derde rij op een derde regel), kunnen we dit doen:
Als we het rooster in één dimensie zouden voorstellen met een variabele oxoArray
, moesten we dit doen:
We hebben de tweedimensionale versie voor een beter inzicht met een hulpmethode gedaan. Dat hoeft niet, want je kan de inhoud van PrintRij
ook rechtstreeks invullen. Dan krijg je een geneste lus:
In het begin kan deze structuur wat intimiderend zijn, maar er is niets nieuws aan deze luscode. Kijk eerst naar wat één uitvoering doet en zie dat als één geheel. Denk dan na over wat er gebeurt als je dat geheel herhaalt, zonder na te denken over hoe dat geheel werkt.
In het vorige voorbeeld toonden we per rij even veel symbolen. Dus de werking van PrintRij
hing niet echt af van de kolom waarop we ons bevonden. Dat is niet altijd zo. Soms is er interactie tussen de tellers van de buitenste en de binnenste for-lus.
Volgend voorbeeld toont hoe je enkel een driehoekig deel van een rooster toont:
Opnieuw kan je de lussen in elkaar schuiven:
En opnieuw hanteer je dezelfde denkwijze om hier uit wijs te raken. Kijk eerst naar wat in de lus staat. Zie rij
gewoon als een getal dat bepaalt hoe veel karakters je maximum kan printen, zonder na te denken over hoe dat getal verandert. Pas als je de werking van de inhoud van de lus goed in je hoofd hebt, denk je na over wat er gebeurt als je die inhoud gaat herhalen met verschillende waarden voor rij
.
Geneste lussen komen vaak voor bij meerdimensionale arrays, maar dat is vooral omdat de twee technieken goed samen gaan. Er is geen verplichting om ze samen te gebruiken.
Je kan geneste lussen gebruiken voor allerlei taken waarbij je herhaling hebt op meerdere niveaus. Bijvoorbeeld voor volgende figuur:
Hier zijn twee vormen van herhaling:
de herhaling die het snelst terugkeert, namelijk van links naar rechts (we printen een aantal keer hetzelfde getal)
de herhaling die minder vaak gebeurt, namelijk van boven naar onder (we printen een aantal keer een regel)
We denken eerst na over de herhaling die het snelst terugkeert. We kennen alles om een getal een aantal keer (gelijk aan zichzelf) te printen:
We kennen ook alles om een newline een aantal keer te printen:
Om de twee lussen te combineren, moeten we vermijden dat we dezelfde naam i
gebruiken, dus we hernoemen die van de binnenste herhaling naar j
. En het aantal keer dat we een getal op een regel willen herhalen, is gelijk aan het nummer van die regel. Dan krijgen we dus:
Kennisclip basis (meer kennisclips lager op de pagina)
Wanneer je de waarde van een variabele wil toekennen aan een variabele van een ander type mag dit niet zomaar. Volgende code zal bijvoorbeeld een error geven, omdat aan de linkerkant staat dat je een geheel getal wil bijhouden (door middel van int
) en aan de rechterkant een kommagetal staat:
Een kommagetal past niet in het geheugen voorzien voor een geheel getal: als er toch een zinvolle omzetting mogelijk is, zal je moeten converteren of casten.
Dit kan op o.a. volgende manieren:
Via casting: de (klassieke) manier die ook werkt in veel andere programmeertalen.
Via de Convert
klasse. Deze staat omzettingen toe die handig zijn, maar niet op het niveau van de taal zijn vastgelegd.
Een klassieke, in veel talen voorziene manier om date om te zetten is casten. Hierbij dien je aan de compiler te zeggen: "Volgende variabele die van het type x is, moet aan deze variabele van het type y toegekend worden. Ik besef dat hierbij data verloren kan gaan, maar zet de variabele toch maar om naar het nieuwe type, ik draag alle verantwoordelijkheid voor het verlies." Je kan bijvoorbeeld bovenstaand kommagetal enkel omzetten naar een geheel getal als je de cijfers na de komma verloren laat gaan. In het Engels betekent to cast "in een vorm gieten".
Casting heb je nodig om een waarde van een bepaald type om te zetten naar een waarde van een ander type. Stel dat je een complexe berekening hebt waar je werkt met verschillende types (bijvoorbeeld int, double en float). Door te casten kan je het soort bewerkingen sturen. Je gaat namelijk bepaalde types even als andere types gebruiken.
Het is belangrijk in te zien dat het casten van een variabele naar een ander type enkel een gevolg heeft tijdens het uitwerken van de expressie waarbinnen je werkt. Het casten op zich verandert niets aan de variabele of waarde zelf.
Casting duid je aan door voor de variabele of literal het datatype tussen haakjes te plaatsen naar wat het omgezet moet worden:
of
Casting gebruik je vaak als je een variabele wil gebruiken als een ander type dat deze waarde niet kan bevatten. We moeten dan aan narrowing doen, letterlijk het versmallen van de data.
Bekijk eens het volgende voorbeeld:
Dit zal niet gaan. Je probeert namelijk een waarde van het type double in een variabele van het type int te steken. Dat gaat enkel als je informatie weggooit. Je moet aan narrowing doen.
Dit gaat enkel als je expliciet aan de compiler zegt: het is goed, je mag informatie weggooien, ik begrijp dat en zal er rekening mee houden. Dit noemen we expliciete casting.
En je doet dit door voor de variabele die dienst moet doen als een ander type, het nieuwe type, tussen ronde haakjes te typen, als volgt:
Het resultaat in var2
zal 20
zijn (alles na de komma wordt bij casting van een double naar een int weggegooid).
Merk op dat
var1
nooit van datatype is veranderd; enkel de inhoud ervan (20.4
) werd eruit gehaald, omgezet ("gecast") naar20
en dan aanvar2
toegewezen dat enkelint
aanvaardt.
Casting kan je ook gebruiken als je aan widening doet (een kleiner type in een groter type steken), als volgt:
Deze code zal zonder problemen gaan. var2
zal de waarde 20.0
bevatten. De inhoud van var1
wordt verbreed naar een double
, eenvoudigweg door er een kommagetal van te maken. Er gaat geen inhoud verloren. Dit wordt ook impliciete casting genoemd. Er bestaan scenario's waarin je dit toch expliciet wil doen, maar die komen minder vaak voor en zullen we benoemen als we ze tegenkomen.
Casting is een in de taal ingebakken manier van data omzetten, die vooral zeer nuttig is daar deze ook werkt in andere C#-related programmeertalen zoals C, C++ en Java. Om te weten hoe deze omzettingen gebeuren, kan je kijken in de handleiding van de taal C# zelf.
Echter, .NET heeft ook methoden die je kunnen helpen om data van het ene type naar het andere te brengen. Deze methoden zitten binnen de Convert
-klasse.
Het gebruik hiervan is zeer eenvoudig. Enkele voorbeelden:
Je plaatst tussen de ronde haakjes de variabele of literal die je wenst te converteren naar een ander type. Merk op dat naar een int
converteren met .ToInt32()
moet gebeuren. Om naar een short
te converteren is dit met behulp van .ToInt16()
. Het voordeel van deze werkwijze is dat ze vastgelegd kan worden door de programmeur en dat ze vaak meer "intuïtieve" omzettingen doet dan een cast. En, als de omzettingen je niet bevallen, kan je een eigen klasse MyConvert
schrijven die de omzettingen doet zoals je zelf wil. Het zijn namelijk gewoon methodes.
Type
Geheugen
Bereik
Precisie
float
32 bits
6 to 9 digits
double
64 bits
15 to 17 digits
decimal
128 bits
28 to 29 digits
In het vorige hoofdstuk zagen we dat er verschillende datatypes bestaan. Deze types hebben we nodig om variabelen aan te maken. Een variabele is een koppeling van een naam aan gegevens. In C# heeft elke variabele ook een type.
Een variabele wordt bijgehouden in het geheugen van je machine, maar in een programmeertaal als C# vragen we ons niet af waar in het geheugen. In plaats daarvan gebruiken we de naam van de variabele, de zogenaamde identifier, om de gekoppelde gegevens op te vragen.
De naam (identifier) van de variabele moet voldoen aan de identifier regels onder "Inleiding -> Afspraken code".
Om een variabele te maken moeten we deze declareren, door een type en naam te geven. Vanaf dan zal de computer een hoeveelheid geheugen voor je reserveren. Hiervoor dien je op te geven:
Het datatype (bv int
, double
).
Een identifier zodat de variabele uniek kan geïdentificeerd worden (volgens de naamgevingsregel van C#).
Een variabele declaratie heeft als syntax:
Bijvoorbeeld: int leeftijd;
Je mag ook meerdere variabelen van het zelfde datatype in 1 enkele declaratie aanmaken door deze met komma's te scheiden:
Bijvoorbeeld string voornaam, achternaam, adres;
Indien je reeds weet wat de beginwaarde moet zijn van de variabele dan mag je de variabele ook reeds deze waarde toekennen bij het aanmaken. Dit noemen we de initialisatie van de variabele.
Eens een variabele is geïnitaliseerd, kunnen we deze (op de meeste plaatsen) gebruiken alsof we de gekoppelde waarde rechtstreeks gebruikten.
Een initialisatie is een speciaal geval van een toekenning. Een toekenning houdt in dat je de waarde die bij een bepaalde naam hoort instelt. In C# mag dit ook indien de variabele al een waarde heeft.
Met de toekennings-operator (=) kan je een waarde toekennen aan een variabele. Hierbij kan je zowel een letterlijke waarde toekennen oftewel het resultaat van een berekening (een "expressie").
Je kan ook een waarde uit een variabele uitlezen en toewijzen aan een andere variabele:
Literals (of "letterlijke waarden") zijn expliciet uitgeschreven waarden in je code. Als je in je code expliciet de waarde 4 wilt toekennen aan een variabele dan is het getal 4 in je code een zogenaamde literal. Wanneer we echter data bijvoorbeeld eerst uitlezen of berekenen (via bijvoorbeeld invoer van de gebruiker of als resultaat van een berekening) en het resultaat hiervan toekennen aan een variabele dan is dit geen literal.
Voorbeelden van een literal toekennen:
Het is belangrijk dat het type van de literal overeenstemt met dat van de variabele waaraan je deze zal toewijzen. Een string
literal stel je voor met aanhalingstekens. Volgende code zal dan ook een compiler-fout generen, daar je een string
literal aan een int-variabele wil toewijzen, en vice versa.
Als je bovenstaande probeert te compileren dan krijg je volgende error-boodschappen:
De manier waarop je een literal schrijft in je code zal bepalen wat het datatype van de literal is:
Gehele getallen worden standaard als int
beschouwd, vb: 125
.
Kommagetallen (met punt .
) worden standaard als double
beschouwd, vb: 12.5
.
Via een suffix na het getal kan je aangeven als het om andere types gaat:
U
of u
voor uint
, vb: 125U
.
L
of l
voor long
, vb: 125L
.
UL
of ul
voor ulong
, vb: 125ul
.
F
of f
voor float
, vb: 12.5f
.
M
of m
voor decimal
, vb: 12.5M
.
Voor bool
(zie verder) is dit enkel true
of false
.
Voor char
(zie verder) wordt dit aangeduid met een enkele apostrof voor en na de literal, vb: 'q'
.
Voor string
(zie verder) wordt dit aangeduid met aanhalingsteken voor en na de literal, vb: "pikachu"
.
De overige types sbyte
, short
en ushort
hebben geen literal aanduiding. Er wordt vanuit gegaan wanneer je een literal probeert toe te wijzen aan een van deze types dat dit zonder problemen zal gaan (ze worden impliciet geconverteerd).
Wanneer je een reeds gedeclareerde variabele een nieuwe waarde toekent dan zal de oude waarde in die variabele onherroepelijk verloren zijn. Probeer dus altijd goed op te letten of je de oude waarde nog nodig hebt of niet. Wil je de oude waarde ook nog bewaren dan zal je een nieuwe, extra variabele moeten aanmaken en daarin de nieuwe waarde moeten bewaren:
In dit voorbeeld zal er dus voor gezorgd worden dat de oude waarde van temperatuurGisteren, 20
, overschreven zal worden met 25
.
Volgende code toont hoe je bijvoorbeeld eerst de vorige waarde kunt bewaren en dan overschrijven:
We hebben dus aan het einde van het programma zowel de temperatuur van eergisteren, 20
, als die van vandaag, 25
.
Veel code die we hebben geschreven wordt meerdere keren, al dan niet op verschillende plaatsen, gebruikt. Dit verhoogt natuurlijk de foutgevoeligheid. Door het gebruik van methodes kunnen we de foutgevoeligheid van de code verlagen omdat de code maar op 1 plek staat én maar 1 keer dient geschreven te worden. Ook de leesbaarheid en dus onderhoudbaarheid van de code wordt verhoogd wanneer we methoden gebruiken om verschillende deeltaken van elkaar te scheiden.
Een methode (bijna hetzelfde als een "functie" in andere programmeertalen) genoemd, is in C# een stuk code ('block') bestaande uit een 0, 1 of meerdere statements. Het is eigenlijk een klein stappenplan voor een onderdeel van je totale programma.
Zoals bij elk stappenplan is er een verschil tussen het vastleggen van de stappen en het uitvoeren. Het vastleggen van de stappen noemen we definiëren van de methode. Vergelijk met het tekenen van een flowchart voor een bepaalde taak, maar dan in code: de definitie van de methode stemt overeen met de flowchart die de deeltaak beschrijft.
Het uitvoeren van de stappen, dus het volgen van de flowchart, noemen we het oproepen of uitvoeren van de methode. De code die dit proces in gang zet noemen we een oproep (in het Engels: call) van de methode.
Je hebt intussen al vaak (ingebouwde) methodes opgeroepen, waaronder:
de Main
methode van de klasse Program
(voor deze hoef je de call zelf niet te schrijven, ze start automatisch op)
de WriteLine
methode van de klasse Console
de Substring
methode van een stuk tekst
In het begin schrijven we onze methodes zo (binnenkort zien we uitbreidingen):
Dit stukje code definieert dus hoe het stappenplan er uitziet. Het zorgt er niet voor dat het ook ooit wordt uitgevoerd. Daar is een call voor nodig. public
zorgt dat deze methode opgeroepen kan worden van uit andere klassen, static
is nodig omdat we klassen alleen als organisatieprincipe voor onze code gebruiken, void
behandelen we iets verder op. Deze zaken, samen met de naam en de haakjes, noemen we ook de signatuur van de methode.
In Flowgorithm stemt de definitie van een methode overeen met een flowchart die in haar geheel getoond kan worden. Onderstaand voorbeeld bevat drie definities (flowcharts): Main
, ToonGroen
en ToonBlauw
.
Je kan elke definitie (flowchart) terugvinden via dit menu:
Om de methode te gebruiken, moeten we een statement uitvoeren die er als volgt uitziet:
Aangezien elk programma begint met een oproep van de Main
methode, zal dit programma dus wel de tekst Groen
laten zien en niet de tekst Blauw
:
In Flowgorithm herken je de oproep als volgt:
Een oproep van een methode betekent dus dat je een andere flowchart uitvoert alsof het één stap is van de flowchart waarin je bezig bent.
Net zoals eerder kan je dit ook in C# stap voor stap uitvoeren door de debugger te gebruiken. Om de werking van een methode in detail te zien, gebruik je "step into".
Declareren van arrays
Initialiseren van arrays
Opvullen van arrays
Arrays gebbruiken - afprinten
Maak een array gevuld met afwisselend true en false (de array is 30 lang)
Per array: output de array naar het scherm, maar ieder element naast elkaar met komma gescheiden. Dus niet:
1 true
2 false
3 true
4 \\etc
maar wel: true, false, true, ...
Implementeer onderstaande flowchart in C#.
Declareren van arrays
Initialiseren van arrays
Opvullen van arrays
Arrays gebruiken
Maak een programma dat aan de gebruiker vraagt om 10 waarden (int) in te voeren in een array. Vervolgens toont het programma de som, het gemiddelde en het grootste getal van deze 10.
Vervolgens vraagt het programma de gebruiker om een getal in te voeren. Het programma toont dan alle getallen die groter of gelijk zijn aan dit ingevoerde getal zijn die in de array aanwezig zijn. Indien geen getallen groter zijn dan verschijnt een bericht Niets is groter op het scherm.
Implementeer onderstaande flowchart in C#. Merk op: je kan in principe de statistieken al bijhouden terwijl de getallen worden ingevoerd, maar hier worden ze pas achteraf berekend. Doe het ook zo.
Declareren van arrays
Initialiseren van arrays
Opvullen van arrays
Arrays gebruiken
Maak een programma dat de gebruiker een boodschappenlijstje laat samenstellen.
Het programma vraagt eerst hoeveel items de boodschappenlijst moet bevatten en laat dan de lijst vullen.
Vervolgens wordt een gesorteerde lijst van de items getoond.
Daarna, in de winkel, kan de gebruiker aangeven welke items er gekocht worden. De gebruiker kan dit blijven doen zolang er 'ja' geantwoord wordt op de vraag 'Nog winkelen?'. Als de gebruiker een item intypt dat niet op de lijst staat, wordt er een bericht getoond.
Na het winkelen toont het programma welke items van de lijst niet gekocht zijn.
Implementeer onderstaande flowchart in C#.
Declareren van arrays
Initialiseren van arrays
Opvullen van arrays
Arrays gebruiken
Array methodes gebruiken
Maak een programma dat kan gebruikt worden om kerstinkopen te doen, rekening houdend met een budget. Na de inkopen, wordt het totaal gespendeerd bedrag getoond en het hoogste, laagste en gemiddelde bedrag.
Probeer zelf een flowchart te maken en zet die dan om naar C#.
Leerdoelen
Arrays in flowcharts gebruiken
Declareren van arrays
Initialiseren van arrays
Opvullen van arrays
Opzoeken waarde in arrays
Functionele analyse
Je vraagt de gebruiker zijn 6 lotto getallen (getal tussen 1 en 42) in te geven. Je hoeft geen controle te doen op de getallen die de gebruiker ingeeft. Je bewaart deze getallen in een array lottoFormulier.
Vervolgens simuleer je een trekking. Dat doe je door random 6 getallen te genereren (zie hiervoor Random) en die ook in een array lottoTrekking te bewaren. Let op: hier moet je wel controleren of het inderdaad 6 verschillende getallen zijn (hoe kan je nagaan of iets aanwezig is in je array?).
Dan doe je een validatie. Heeft de gebruiker 3 juiste cijfers wint hij 10 €, bij 4 juiste cijfers 1000 €, bij 5 juiste cijfers 100.000 € en bij 6 juiste cijfers 10.000.000 €.
Zorg dat alles mooi wordt getoond aan de gebruiker. Lotto getallen in stijgende volgorde, Trekking getallen in stijgende volgorde, winst. Zie voorbeeldinteractie.
Technische analyse
Maak een flowchart en zet die om naar C#
Wat betreft het random genereren van 6 getallen tussen 1 en 42. Daar krijg je het volgend stukje code voor (probeer te begrijpen hoe het werkt en zoek op internet het gebruik van Random eens op):
Test de ingegeven lotto getallen en zorg er voor dat er geen getallen kleiner dan 1 of groter dan 42 ingegeven kunnen worden en dat er ook geen dubbels voorkomen..
Arrays in flowcharts gebruiken
Opzoeken waarde in integer arrays – zelf geschreven methode
Probeer d.m.v. Flowgorithm de flowchart te gebruiken en zet dan om naar C#.
Leerdoelen
Arrays in flowcharts gebruiken
De array gebruiken, in een array zoeken.
Functionele analyse
Heel eenvoudig uitgelegd zal het algoritme de te zoeken waarde vergelijken met de middelste waarde (van een gesorteerde array). Als de waarde niet gelijk is wordt dat gedeelte van de array waar de waarde zich niet in kan bevinden weggegooid. De zoektocht gaat verder totdat de waarde is gevonden (of niet gevonden indien alle mogelijkheden zijn doorzocht)
Implementeer onderstaande flowchart in C#. Om twee strings te ordenen, gebruik je string1.CompareTo(string2)
. Dit levert -1 als string1 voor string2 komt, 1 als string1 na string2 komt en 0 als ze op dezelfde plaats komen.
Declareren van een List
Initialiseren van een List
List
gebruiken
Maak een een alternatieve versie van H6-Boodschappenlijst. Maak ditmaal gebruik van List<T> in plaats van een array.
Je hoeft de grootte van de lijst niet meer op voorhand te vragen.
Een lijst myList
sorteren doe je door myList.sort()
op te roepen.
Je mag elementen die gekocht zijn gewoonweg verwijderen uit de lijst.
Op het eindetoon je de resterende items met een foreach
lus.
Je geeft een lege regel in om aan te geven dat je niets wil toevoegen aan je lijstje.
Noem je methode BoodschappenList
Declareren van een List
Initialiseren van een List
List
gebruiken
Maak een een alternatieve versie van H6-Kerstinkopen. Maak ditmaal gebruik van List<T> in plaats van een array.
Je hoeft niet meer te vragen op voorhand hoe veel cadeautjes er gekocht zullen worden.
Je mag stoppen bij een lege regel invoer.
Je moet ook stoppen zodra je over budget gaat.
Noem je methode KerstinkopenList
Een methode is een stappenplan voor een bepaalde taak. Met de syntax die we tot hiertoe gezien hebben, wordt deze taak altijd op exact dezelfde manier uitgevoerd. Dat beperkt de mogelijkheden enorm.
Een niet-digitaal voorbeeld. Ik plan een feest. Een van mijn deeltaken is om een simpele appeltaart te maken. De tweede is om een simpele perentaart te maken.
Het stappenplan voor de appeltaart is als volgt:
leg taartdeeg in een ronde bakvorm
doe er wat pudding op
snijd een appel in stukken
beleg met de stukjes fruit
zet in de oven
Het stappenplan voor de perentaart is als volgt:
leg taartdeeg in een ronde bakvorm
doe er wat pudding op
snijd een peer in stukken
beleg met de stukjes fruit
zet in de oven
Dit is hetzelfde stappenplan, op het gebruikte fruit na. Eigenlijk kan je in stap 3 allerlei soorten fruit gebruiken, afhankelijk van het resultaat dat je wenst. We kunnen het stappenplan dus algemener formuleren:
leg taartdeeg in een ronde bakvorm
doe er wat pudding op
snij het stuk fruit dat je wil gebruiken in stukken
beleg met de stukjes fruit
zet in de oven
Op het moment dat we een taart willen bakken, moeten we pas voor een bepaald stuk fruit kiezen. In de tekst hierboven is "het stuk fruit" dus een soort variabele: we schrijven een naam maar we bedoelen daarmee een waarde die in de uitvoering wordt vastgelegd.
In code zijn dit soort variabelen parameters van een methode. In de definitie van de methode werken we met hun naam (zoals "het stuk fruit"). Wanneer we de methode oproepen, voorzien we hun waarde (zoals een appel of een peer of een ananas). In de call spreken we ook over argumenten in plaats van parameters. De termen worden soms door elkaar gehaald, maar ze betekenen dus niet exact hetzelfde.
In Flowgorithm kunnen kort noteren dat we een appeltaart en een perentaart willen maken:
In de praktijk maken we natuurlijk geen taarten, maar doen we bewerkingen met gegevens. Volgende methode berekent en toont een bepaalde macht van een bepaald getal. De werkwijze is altijd dezelfde, maar de gebruiker beslist zelf welke macht hij wil berekenen en van welk getal:
Dit is een definitie van een methode, dus vergelijkbaar met een stappenplan of een flowchart. We geven aan dat er twee stukjes informatie zijn die per uitvoering kunnen verschillen: het grondtal en de macht. Omdat dit eigenlijk variabelen zijn (met een scope beperkt tot deze methode) geven we ook hun type, net zoals bij variabelen die we op de reeds gekende wijze declareren.
Als we deze methode willen gebruiken, kunnen we dit doen:
De laatste regel bevat de call van de methode. De definitie bevat een paar "gaten" en de call vult deze in met de gewenste getallen. Het is belangrijk dat de gebruikte types en de verwachte types compatibel zijn. Het is bijvoorbeeld niet mogelijk Macht("Hello World","blabla");
te schrijven als call want strings zijn geen ints.
Als je het stappenplan bovenaan deze pagina gebruikt om een appeltaart te maken, heb je achteraf geen appel meer. Hier kan het mechanisme van methodes je wat verrassen. Je moet in het achterhoofd houden dat de parameters van een methode hetzelfde werken als gewone variabelen.
Een voorbeeld:
Dit programma zal je eerst 3
tonen en dan, op de volgende regel, 4
. Dit komt omdat de =
op regel 2 niet betekent "a
is hetzelfde als b
", maar wel "kopieer het ding met naam a
en geef de kopie de naam b
". Dit is hoe toekenning werkt met alle types die we tot hiertoe gezien hebben.
Dit is niet hoe toekenning altijd werkt. Met name voor arrays is het wat anders, maar dat geval zullen we later in meer detail behandelen.
Met argumenten van een methode-oproep is het hetzelfde. Nog een voorbeeld:
Je ziet dat het getal enkel gewijzigd is binnen de methode VeranderGetal en daarna weer teruggezet lijkt op de oude waarde. Dit is niet helemaal correct. Wat eigenlijk gebeurt, is het volgende:
een waarde 4 krijgt de naam mijnGetal
de waarde met naam mijnGetal
wordt getoond
de waarde met naam mijnGetal
wordt gekopieerd en krijgt de naam getal
de naam mijnGetal
wordt toegekend aan het getal dat je verkrijgt door de kopie met één te verminderen en verwijst nu dus naar een 3
deze 3 wordt getoond
de oproep eindigt
heel de tijd is mijnGetal
blijven verwijzen naar 4 en daarom wordt er terug een 4 getoond
mijnGetal
en getal
hebben dus nooit dezelfde data bevat. Bij het begin van een oproep van VeranderGetal
zijn hun data wel kopieën van elkaar, maar na regel 2 is zelfs dat niet meer waar.
Je hebt al vaak waarden als argument meegegeven:
de tekst die je laat zien via WriteLine
de grenzen waarbinnen een willekeurig getal bepaald moet worden met Next
de tekst die je wil vervangen en de tekst die hem vervangt met Replace
Ook hier wordt altijd hetzelfde stappenplan gevolgd, maar de manier waarop dat plan doorlopen wordt, verschilt afhankelijk van de gebruikte argumenten.
Voor de eenvoud hebben we in bovenstaand voorbeeld twee namen gebruikt: mijnGetal
en getal
. Eigenlijk hoefde dat niet. We kunnen ook dit doen:
De parameter getal
van VeranderGetal
is niet dezelfde variabele als de variabele getal
van Main
. Elke functiedefinitie bakent een scope af. Vermits de definitie van VeranderGetal
niet genest is in de definitie van Main
(en het omgekeerde ook niet waar is) staan hun scopes los van elkaar. Binnenin Main
bestaat er een variabele met naam getal, binnenin VeranderGetal
bestaat er een andere variabele met naam getal.
Er staat wel een oproep van VeranderGetal
in Main
, maar dit heeft geen effect op de variabelen in scope. Enkel de definities zijn hier van belang.
Een stappenplan, en dus een methode, voer je uit met het oog op een bepaald resultaat. Soms moet dat resultaat terugbezorgd worden aan een opdrachtgever. Soms niet. Methodes staan toe beide variaties op een stappenplan te schrijven.
We gebruiken opnieuw een proces uit het echte leven om de interactie te omschrijven voor we de sprong maken naar code.
Denk aan een ouderwetse bakker. Het stappenplan dat deze bakker volgt is er een om brood te maken. Om brood te maken heb je bloem nodig. Bloem maken is niet echt een onderdeel van het takenpakket van de bakker. Hij laat de molenaar dit voor hem doen. Hij vraagt niet gewoon aan de molenaar om bloem te maken, maar ook om deze aan hem te bezorgen zodat hij er zelf mee verder kan werken. De bakker is niet geïnteresseerd in hoe de molenaar bloem maakt, maar hij heeft het eindproduct nodig om zijn brood af te maken.
We vertalen nu deze interactie naar code en verklaren daarna de vertaling:
Dit is maar een voorbeeld om de flow te verduidelijken. Er is geen "juiste" manier om een methode te schrijven om brood te bakken (tenzij je misschien een broodmachine programmeert).
Hier vallen twee zaken op: in de signatuur (d.w.z. alles van het begin van de regel tot en met het gesloten ronde haakje) van de methode Molenaar staat niet void
, maar string
. Dit komt omdat deze methode een resultaat teruggeeft aan de opdrachtgever en dat resultaat van het type string
is. Daarnaast heb je het sleutelwoord return
, gevolgd door de tekst "bloem"
. Het woordje return
betekent: "bezorg dit aan de code die deze methode heeft opgeroepen". Anders gezegd: dit is hoe de molenaar de geproduceerde bloem overhandigt aan zijn opdrachtgever, de bakker. Waar je de call Molenaar()
schrijft komt tijdens de uitvoering het geproduceerde resultaat dankzij die return
.
return
typeJe moet dus noteren wat voor resultaat er achter return
staat: een string of eventueel iets anders. Als er een string volgt na return
, zeggen we dat string het return type is van die methode. Als een methode een geheel getal produceert, heeft ze het return type int
of een verwant getaltype.
Maar niet alle methodes bezorgen iets terug aan hun opdrachtgever. Sommige hebben gewoon een effect. Voor deze methodes schrijven we void
als return type. Als een methode dit return type heeft, kunnen we het resultaat van een call dus niet toekennen aan een variabele, want er is geen resultaat.
"Er is geen resultaat" wil niet zeggen dat een void
methode niets doet. Console.WriteLine is bijvoorbeeld een methode met return type void
. Het punt is dat de call je niets terugbezorgt waarmee je verder kan werken. Je kan bijvoorbeeld niet schrijven: string tekst = Console.WriteLine("Dit is tekst");
Er is dus een groot verschil tussen return "tekst";
en Console.WriteLine("tekst");
Bij de eerste code is er geen garantie dat de geproduceerde tekst ooit op het scherm verschijnt, maar je kan er wel mee verder werken. Bij de tweede verschijnt hij per definitie wel op het scherm, maar kan je hem niet toekennen aan een variabele om later mee verder te werken.
We kunnen dus wel doen: string ingredient3 = Molenaar();
(want het return type van Molenaar()
is string
) maar we kunnen niet schrijven: string product = Bakker();
(want Bakker()
heeft return type void
en produceert dus geen resultaat). Deze code compileert dan ook niet.
Je kent de methode Replace
van strings. Je gebruikt deze als volgt:
Veel beginners doen het volgende:
Dit is fout. Replace
berekent het resultaat van de aanpassing en geeft dat terug aan de opdrachtgever. Het past de ingevoerde tekst niet aan. In de code van Replace
staat dus ook een return
!
Als je parameters en returnwaarden combineert, worden methoden erg flexibel. Dan kan je al echte bouwsteentjes van een programma gaan schrijven.
Een voorbeeld: de stelling van Pythagoras vertelt ons hoe lang de schuine zijde van een driehoek met twee rechte hoeken is:
Als we deze berekening regelmatig uitvoeren voor verschillende waarden van a
en b
, kunnen we ze als volgt in een methode gieten:
Nu kunnen we heel snel allerlei schuine zijdes uitrekenen, bv.:
Bovendien maken we gebruik van het feit dat de methode Sqrt
(square root is vierkantswortel) ook een parameter heeft en een resultaat teruggeeft. Eigenlijk verloopt een uitvoering dus zo:
de methode Main
start op
we willen bepaalde tekst uitprinten, maar we moeten die eerst nog bepalen met de methode Pythagoras
de methode Pythagoras
start op met de argumenten 4 en 3
de methode berekent de som van de kwadraten en noemt deze som
de methode maakt zelf gebruik van nog een methode, Sqrt
, en geeft daarbij som als argument
Sqrt
rekent de vierkantswortel uit en geeft deze terug aan Pythagoras
Pythagoras
geeft deze verder door aan Main
de zin wordt correct afgeprint
Meestal is het dus een goed idee zo weinig mogelijk informatie te communiceren met WriteLine
en zo veel mogelijk met return
te werken. Meerbepaald: gebruik WriteLine wanneer je de uiteindelijke informatie in haar uiteindelijke vorm hebt en deze echt aan de gebruiker moet tonen. Gebruik return
wanneer je het resultaat van een deeltaak aan een opdrachtgever wil bezorgen.
Volgend Flowgorithm toont aan hoe je dankzij methoden een probleem kan opbreken in meerdere kleine stappen, waarbij je informatie doorgeeft via parameters en returnwaarden:
Al deze oefeningen schrijf je in een klassen Methodes
.
Methodes definiëren met parameters
Oproepen van de methodes
Je stelt een aantal typische berekeningen voor met methodes.
Voor deze oefening schrijf je meerdere methodes. Je plaatst er slechts één in je keuzemenu, namelijk ReeksOperaties
. Deze voert alle andere methodes van deze oefening één voor één uit.
Enkel ReeksOperaties
mag Console.ReadLine
statements bevatten. De andere methodes tonen hun resultaat op het scherm door middel van Console.WriteLine
statements in de methode zelf die het resultaat bepaalt. (Met andere woorden, alle methodes zijn void
.)
De andere methodes zijn:
BerekenStraal
, die de straal van een cirkel kan berekenen waarvan je de diameter meegeeft (de diameter geef je mee als argument). De straal is de helft van de diameter.
Idem voor BerekenOmtrek
en BerekenOppervlakte
. De omtrek is de diameter maal het getal pi, dat je voorstelt als Math.Pi
. De oppervlakte is straal maal straal maal pi.
Methode Maximum
die het grootste van 2 getallen teruggeeft (beide getallen geef je mee als argument).
Methode IsEven
die toont of een getal even of oneven is.
Methode ToonOnEvenGetallen
die alle oneven getallen van 1 tot n toont waarbij n als argument wordt meegegeven.
Methodes definiëren met return type en parameters
Oproepen van de methodes
Schrijf een programma dat op basis van je voornaam, naam en het feit of je al dan niet een student bent een AP e-mailadres genereert. Het e-mailadres moet uit allemaal kleine letters bestaan en moet een geldig e-mailadres zijn.
Je mag geen String methodes gebruiken maar je schrijft je eigen methodes. Als aanzet is de methode StringToLower al uitgewerkt.
Uitbreiding: kijk of voor al je lectoren en medestudenten het gegenereerde e-mailadres geldig is. Pas eventueel je code aan indien niet.
Werk volgend flowchart verder af en zet om in C#.
Leerdoelen
Methodes definiëren met parameters en returnwaarde
Oproepen van de methodes
Gebruiken van returnwaarden
Je stelt een aantal typische berekeningen voor met methodes die geschikt zijn voor gebruik in andere programma's.
Voor deze oefening schrijf je meerdere methodes. Je plaatst er slechts één in je keuzemenu, namelijk ReeksOperatiesMetReturn
. Deze voert alle andere methodes van deze oefening één voor één uit.
Enkel ReeksOperaties
mag Console.ReadLine
of Console.WriteLine
statements bevatten. De andere methodes berekenen hun resultaat, zonder het te tonen op het scherm. Met andere woorden, hun return type is niet void
.
De andere methodes zijn:
BerekenStraalMetReturn
, die de straal van een cirkel kan berekenen waarvan je de diameter meegeeft (de diameter geef je mee als argument). De straal is de helft van de diameter. Voor je deze toont, rond je ze in ReeksOperatiesMetReturn
af tot één cijfer na de komma met Math.Round(reedsBerekendeStraal,1)
.
Idem voor BerekenOmtrekMetReturn
en BerekenOppervlakteMetReturn
. Ook deze waarden moet je, nadat ze uitgerekend zijn, afronden tot 1 cijfer na de komma.
Methode MaximumMetReturn
die het grootste van 2 getallen teruggeeft (beide getallen geef je mee als argument).
Methode IsEvenMetReturn
die bepaalt of een getal even of oneven is. De returnwaarde vertelt dus of iets waar of niet waar is.
Methode BepaalEvenGetallenMetReturn.
Deze geeft als antwoord een List
van alle even getallen tot n. ReeksOperatiesMetReturn
toont deze getallen dan, gescheiden door een komma.
Zoals boven, met de vermelde aanpassingen (afronding tot 1 cijfer na de komma en getallen gescheiden door ,
)
Functionele analyse
We schrijven een simpel tekenprogramma, dat driehoeken van een bepaalde afmeting, met een bepaald patroon kan tekenen.
Technische analyse
Schrijf een methode TekenDriehoek
. Deze methode verwacht twee zaken: een karakter om het patroon voor te stellen en de hoogte van de driehoek. Ze roept zelf meermaals een andere methode TekenRegel
op, die één regel van de driehoek tekent. TekenRegel toont niets op het scherm via Console.WriteLine
!
Voorbeeldinteractie
Al deze oefeningen maak je als statische methoden van een klasse Meerdimensionaal. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.
Werken met multidimensionale arrays
We wensen de gegevens in één rij te groeperen door hun som te bepalen, een beetje zoals je dat in een Excel spreadsheet zou kunnen doen.
Schrijf in de klasse Meerdimensionaal
een methode SomNdeRij
. Deze heeft twee parameters: een meerdimensionale array van double
en een rijnummer (een int
). De returnwaarde is de som van alle waarden op de rij met het gevraagde nummer.
Deze methode toont niets op de console, maar je kan ze wel testen via deze voorbeeldcode, die je mag gebruiken wanneer deze oefening wordt opgestart via het keuzemenu:
Werken met multidimensionale arrays
We wensen de gegevens in één rij te groeperen door hun som te bepalen, een beetje zoals je dat in een Excel spreadsheet zou kunnen doen.
Schrijf in de klasse Meerdimensionaal
een methode SomPerRij
. Deze maakt een tweedimensionale array met het gevraagde aantal rijen en het gevraagde aantal kolommen aan. Je mag veronderstellen dat er een geldig aantal wordt ingegeven. Vervolgens vraagt ze, rij per rij en kolom per kolom, een getalwaarde. Wanneer elke positie is ingevuld, toont ze de som per rij van de ingegeven getallen.
Werken met multidimensionale arrays
Werken met enumeratietypes
We willen een simpel tekenprogramma maken in de terminal. De gebruiker kan pixel per pixel een gewenste kleur aangeven.
Schrijf in Meerdimensionaal
een methode Pixels
.
Vraag hierin eerst aan de gebruiker welke afmetingen hij wil gebruiken voor zijn afbeelding. Dit bepaalt het aantal rijen en kolommen en dus het aantal pixels. Maak vervolgens een array van ConsoleColor
waarden aan met deze afmetingen. Vraag tenslotte in een lus wat de gebruiker wil doen:
een pixel kleuren
vraag hierbij de rij-index en kolom-index
vraag ten slotte in welke kleur deze moet worden ingevuld
je kan sneller een array van alle kleuren krijgen met volgende code: ConsoleColor[] kleuren = (ConsoleColor[]) Enum.GetValues(typeof(ConsoleColor));
je hoeft deze instructie niet volledig te begrijpen: ze doet hetzelfde als ConsoleColor[] kleuren = {ConsoleColor.Back, ConsoleColor.DarkBlue, ...}
maar vraagt gewoon minder typwerk
de afbeelding zoals ze momenteel is tonen
toon hiervoor elke pixel als een spatie met Console.Write(" ")
Werken met multidimensionale arrays
Werken met enumeratietypes
We zullen ons tekenprogramma uitbreiden met een extra functie die de array bewerkt. Met andere woorden, een (simpele) afbeeldingsfilter. Deze zal bepaalde pixels automatisch rood kleuren.
Schrijf een functie RedFilter
. Deze heeft een afbeelding (een ConsoleColor[,]
) als invoer en vervangt alle vakjes die zich op en oneven rij én een oneven kolom bevinden door de kleur rood. Ze toont de afbeelding niet.
Deze oefening kan je niet rechtstreeks oproepen via het keuzemenu voor deze klasse. Het wordt een extra optie om iets met je afbeelding te doen.
Werken met multidimensionale arrays
Werken onderling afhankelijke indexen
We zullen ons tekenprogramma uitbreiden met nog een extra functie die de array bewerkt. Deze functie "knipt" de afbeelding diagonaal in twee.
Schrijf een functie LowerTriangleFilter
. Deze heeft een afbeelding (een ConsoleColor[,]
). Ze zorgt ervoor dat alle pixels op of onder de diagonaal van links boven tot rechts onder wit worden gemaakt. Dit vereist wat te veel rekenwerk als de afbeelding geen vierkant is, dus geef de foutmelding "deze filter kan niet worden toegepast op deze afbeelding"
als het aantal rijen verschilt van het aantal kolommen.
Deze oefening kan je niet rechtstreeks oproepen via het keuzemenu voor deze klasse. Het wordt een extra optie om iets met je afbeelding te doen.
Werken met multidimensionale arrays
Werken met geneste iteratie
Werken met Random
Ik wil een paard een aantal willekeurige (random) sprongen laten maken op een schaakbord vertrekkende van een gegeven positie.
Na de sprongen wil ik een "heatmap" van deze sprongen. Hoe vaak is het paard op een positie geweest.
Volg de conventie:
Schrijf een nieuwe klasse HeatmapPaardensprong
. Het startpunt in deze klasse is de methode HeatmapPaardensprongMain
.
Vraag hierin eerst aan de gebruiker hoe vaak het paard moet springen en vraag ook de beginpositie in de vorm A1..H8
Je gaat vervolgens vertrekkende van deze positie een aantal willekeurige sprongen kiezen. Per sprong zijn er maximaal 8 mogelijkheden (zie afbeelding). Je zorgt er voor dat de sprong geldig is (binnen het bord). Deze positie is de startpositie voor de volgende sprong. De positie zelf is ook bezocht, dat houd je bij.
Werken met multidimensionale arrays
Werken met geneste iteratie
Werken met Random
Surf naar de webstek: https://www.compadre.org/osp/EJSS/3577/12.htm. Hier wordt het "Game of Life" uitgelegd. We gaan een vereenvoudigde console versie maken. Dit wordt bijvoorbeeld veel gebruikt om automatisch omgevingen met structuur te genereren in games met procedurele generatie.
Schrijf een nieuwe klasse ConwayGameOfLife
. Het startpunt in deze klasse is de methode ConwayGameOfLifeMain
.
Vraag hierin eerst aan de gebruiker hoe veel cellen hij wil: 10 geeft een veld van 10 op 10 cellen.
Vervolgens vraag je de gebruiker hoeveel generaties hij wil zien.
Je gaat vervolgens het bord willekeurig opvullen. Gebruik een enum type om de status van een cel (levend, dood) bij te houden.
Vervolgens ga je generatie per generatie laten zien. Van de éne generatie op de andere gelden volgende regels:
Any live cell with fewer than two live neighbors dies, as if by loneliness
Any live cell with more than three live neighbors dies, as if by overcrowding.
Any live cell with two or three live neighbors lives, unchanged, to the next generation.
Any dead cell with exactly three live neighbors comes to life.
Laat elke generatie 2 seconden zien en bereken dan de volgende generatie (kijk even naar de oefening met de chronometer!).
Je gegevens in een C#-programma zijn altijd van een bepaald type: string
, int
, DateTime
, Student
, wat dan ook. Je moet voortdurend nadenken over welke datatype je aan het gebruiken bent. Dit is niet alleen belangrijk om te weten welke methoden en attributen je mag gebruiken. Je moet het ook weten om te begrijpen wat gebeurt als je een waarde toekent of als je een waarde meegeeft als argument van een methode.
Er zijn namelijk twee mogelijkheden:
bij value types overschrijf je de data zelf wanneer je een toekenning doet en geef je een kopie van je data mee aan de methode
Deze types kunnen standaard niet de waarde null
aannemen. Als je een variabele van een van deze types maakt en niet toekent, krijgt hij een defaultwaarde, zoals 0
, false
,...
bij reference types noteer je een geheugenadres wanneer je een toekenning doet en geef je een geheugenadres mee wanneer je een methode oproept
deze types kunnen (standaard) de waarde null
aannemen, wat betekent dat er geen adres voor de data is. Dit is ook de defaultwaarde voor alle reference types.
Dit heeft belangrijke gevolgen. Als je dit systeem niet begrijpt, ga je gegarandeerd bugs in je code zien. Als voorbeeld zullen we het uitvoeren van een methode vergelijken met "iets noteren op papier" in het echte leven. Afhankelijk van de situatie wil je dan met een kopie of met een adres voor het origineel werken.
Op een toets krijgt iedereen een blad met de vragen en vult hij/zij persoonlijke antwoorden in. Elke toets is individueel. Het is niet zo dat het antwoord van persoon 1 zichtbaar mag zijn voor persoon 2. We nemen dan ook geen toetsen af in Google docs.
Wanneer er een geboortekaartje moet ondertekend worden, wordt er op de werkvloer vaak een e-mail uitgestuurd waarin staat in welk lokaal het kaartje ligt. Alle aanpassingen komen samen op hetzelfde kaartje.
We vragen niet aan iedereen om dezelfde toets in te vullen en we voorzien geen geboortekaartje per persoon die ondertekent. Je ziet dus andere resultaten wanneer je een handeling telkens uitvoert met een kopie dan wanneer je ze uitvoert met een verwijzing naar het origineel.
Onze eigen klassen zijn reference types. Dat wil zeggen dat, in de ruimte die voorzien wordt wanneer we een variabele van een bepaalde klasse declareren, er een verwijzing wordt bijgehouden. Zo'n verwijzing is een adres voor de bytes die ons object vormen. Dit is in tegenstelling tot value types. Daarvoor wordt de waarde zelf bijgehouden op de plaats die voorzien is voor de variabele. De meeste types die je in het begin gezien hebt, zijn value types: int
(en varianten), boolean
, float
(en varianten), enum
types.
Deze code produceert toont twee verschillende waarden: 18
en 19
.
We verklaren de 18 als volgt:
leeftijdAlsInt
is een int
, dus een value type, waaraan we de waarde 18 geven
als we VerhoogLeeftijd
met een int
als parameter oproepen, maken we dus een kopie van 18
en geven we die kopie mee aan de methode. Het is alsof we de methode een eigen exemplaar van de toets geven. In de body van de methode is "leeftijd" dus een naam om de kopie aan te duiden.
als we leeftijd
met 1 verhogen (via leeftijd += 1
ofwel leeftijd = leeftijd + 1
) koppelen we de naam "leeftijd" aan een nieuwe waarde. Dat heeft geen enkel effect op leeftijdAlsInt
, waarvan we bij oproep van VerhoogLeeftijd
een kopie hadden genomen.
de Console.WriteLine
toont de originele leeftijd en die is nooit aangepast
We verklaren de 19 als volgt:
Student
is een zelf geschreven klasse, dus een reference type
als we student.Leeftijd
instellen op 18, gaan we eerst op zoek naar de geheugenlocatie waar de gegevens over student zijn bijgehouden en overschrijven we daar de bytes die de leeftijd voorstellen.
als we VerhoogLeeftijd
met student
als parameter aanroepen, vertellen we de methode waar in het geheugen de informatie over student
gevonden kan worden. Het is alsof we zeggen: "Het geboortekaartje (de student) ligt in de refter".
VerhoogLeeftijd
gaat de leeftijd dus aanpassen bij de bron: de bytes die overschreven worden, zijn dezelfde die we de eerste keer hebben ingevuld.
de Console.WriteLine
gaat naar het adres waar de bytes van het Student
object zich bevinden en haalt daar de leeftijd op: deze zijn in de vorige stap aangepast.
Het feit dat klassen reference types zijn, heeft praktische gevolgen. We zullen dit demonstreren door dezelfde functionaliteit te implementeren met een class
en een struct
. Je kan struct
zien als bijna hetzelfde als class
, in die zin dat je er ook objecten van kan maken, maar struct
-objecten zijn value types.
We gaan je nergens in deze cursus vragen zelf een struct
te maken. We gebruiken ze alleen omdat ze het verschil tussen value en reference duidelijker kunnen maken.
Vergelijk volgende twee vereenvoudigde varianten op DateTime
:
Beide stellen een datum voor en hebben dezelfde attributen. We voorzien ook een methode WijzigDatums
, met twee parameters: één voor ons value type en één voor ons reference type. Ten slotte voorzien we een demonstratie in Main
:
Als je dit programma uitvoert, merk je dat de waarde van d1
niet gewijzigd is door de methode en d2
wel. Dat komt omdat val een kopie bevatte van de waarde van d1
, terwijl reference naar dezelfde data verwees als d2
.
We willen een lijst bijhouden met alle objecten van de klasse Cursus
. Zorg dat deze lijst automatisch wordt ingevuld.
Voorzie op klasseniveau een array met plaats voor 10 Cursus
objecten en noem hem AlleCursussen
. Zorg ervoor dat een cursus bij aanmaak in de eerste vrije positie in deze array wordt geplaatst. Schrijf hiervoor een private hulpmethode registreerCursus(Cursus cursus)
. Deze kan je uitvoeren zonder bestaand Cursus
object. Ze gaat op zoek naar de eerste vrije positie in de array en slaat cursus
op op deze positie.
Je kan registreerCursus
als volgt implementeren:
start met een variabele vrijePositie
van type int?
met waarde null
controleer één voor één de posities in de array
onthoud de eerste positie waarop je null
tegenkomt
controleer nadat de array doorlopen is of er nog een vrije positie is
zo ja, sla de cursus daar op
zo nee, print "Er zijn geen vrije posities meer"
Commit je aanpassingen.
We willen cursussen makkelijk kunnen opvragen via Id. Schrijf een methode ZoekCursusOpId
die dit doet.
Deze methode werkt op klasseniveau, want je hebt geen cursus nodig om een andere cursus te vinden. Ze heeft één parameter, id
. Het return type is Cursus
, maar het kan zijn dat je geen cursus met het gevraagde Id kan terugvinden.
De methode werkt door AlleCursussen
element per element te doorlopen en de waarde van het attribuut Id
van elk element te vergelijken met het meegegeven argument. Als de gevraagde cursus niet bestaat, mag je programma niet crashen, maar krijg je null
terug.
Commit je aanpassingen.
Het is niet handig dat onze klasse VakInschrijving
een cursus voorstelt als string. Dat maakt dat we de functionaliteit van Cursus
niet kunnen gebruiken. Pas daarom VakInschrijving aan zodat de klasse echt gelinkt is aan Cursus
. Dit vereist aanpassingen op een aantal plaatsen.
Voor de aanpassing heb je dit:
Erna heb je dit:
Controleer ook dat al je testmethodes nog dezelfde resultaten leveren als eerder.
Commit je aanpassingen.
We wensen cursussen te groeperen in studieprogramma's.
Schrijf een klasse StudieProgramma
. Deze heeft een naam, bevat een aantal cursussen en kan getoond worden op het scherm.
Implementeer als volgt:
Gebruik volgende code voor de demonstratiemethode:
Hier loopt iets mis. Benoem zelf de oorzaak en corrigeer de fout.
Commit je aanpassing.
Zoals boven, maar gebruik nu volgende code voor de demonstratiemethode:
Opnieuw loopt het fout. Benoem zelf de oorzaak en corrigeer de fout.
Commit je aanpassing.
Vanaf hier veronderstellen we dat je in één groot project werkt dat één klasse Program
heeft. Deze klasse heeft een Main
methode die een keuzemenu opstart. Oefeningen rond eenzelfde topic worden (statische) methodes van één klasse met een methode ToonSubmenu
, die je een menu toont van alle oefeningen over dat topic en die je toestaat een oefening naar keuze te testen. Dit wordt uitgelegd in de eerste oefening.
een ordelijke menustructuur voor je code voorzien
We willen dat we alle oefeningen die we in dit vak maken op een ordelijke manier kunnen opstarten. We doen dit door een keuzemenu met twee niveaus te voorzien: de gebruiker kan elke reeds geschreven oefening uitvoeren door eerst het algemene onderwerp aan te geven en vervolgens de specifieke oefening.
Laat in je Main methode een lijst van alle topics zien waarover oefeningen gemaakt zijn. In het begin is dit enkel DateTime
. De gebruiker kan een topic aanduiden door een nummer in te geven, want voor elk topic staat ook een nummer.
Gebruik een switch op de gebruikersinput om te bepalen van welk topic de ToonSubmenu
methode moet worden opgeroepen. Deze methode heeft return type void
en geen parameters.
Voorzie een eerste klasse, DateTimeOefeningen
, met deze methode ToonSubmenu
. Totdat je oefeningen hebt om te demonstreren, toont ToonSubmenu
gewoonweg de tekst "Er zijn nog geen oefeningen over dit topic"
.
Indien er wel oefeningen zijn (deze oefening moet je dus updaten naarmate je vordert), wordt elke reeds geprogrammeerde oefening genummerd en getoond en kan de gebruiker kiezen om deze uit te voeren.
Nadat een oefening getest is, kan je opnieuw een topic en een oefening kiezen. Het programma eindigt nooit.
Dit is maar een voorbeeld! De getoonde topics en oefeningen gaan afhangen van wat je al gedaan hebt.
aanmaken van DateTime
objecten
formatteren van DateTime
objecten
We willen voor een willekeurige datum kunnen bepalen welke dag van de week het is.
je moet eerst de dag, maand en jaar opvragen en een DateTime
aanmaken
daarna moet je laten zien over welke dag van de week het gaat
gebruik hiervoor formattering van een DateTime
laat ook de datum zelf zien in een formaat dat leesbaar is voor de gebruiker
als je computer niet volledig ingesteld is op Belgisch Nederlands, kan het resultaat er wat anders uitzien.
maak deze methode toegankelijk via ToonSubmenu
van de klasse DateTimeOefeningen
noem de methode waarin je dit schrijft WeekdagProgramma
aanmaken van DateTime
objecten
We willen weten hoe veel fracties van een seconde al verlopen zijn sinds het begin van de jaren 2000.
.NET stelt deze fracties (1 / 10000 milliseconden) voor als "ticks"
We willen weten hoe veel ticks er voorbijgegaan zijn sinds het absolute begin van het jaar 2000
maak deze methode toegankelijk via ToonSubmenu
van de klasse DateTimeOefeningen
Noem de methode waarin je dit schrijft Ticks2000Programma
gebruik van een statische methode
We willen bepalen hoe veel schrikkeljaren er zijn tussen 1799 en 2021.
implementeer zelf geen logica voor schrikkeljaren, maar laat dit over aan de klassen DateTime
maak gebruik van een statische methode van deze klasse
maak deze methode toegankelijk via ToonSubmenu
van de klasse DateTimeOefeningen
noem je methode SchrikkeljaarProgramma
eenvoudig code leren timen
gebruiken van DateTime
herhaling arrays
We zijn benieuwd hoe lang het duurt een array van 1 miljoen int
s te maken en op te vullen met de waarden 1,2,...
Bepaal het tijdstip voor en na aanmaken van de array.
Vul de array in met een for
-lus.
maak deze methode toegankelijk via ToonSubmenu
van de klasse DateTimeOefeningen
Noem de methode waarin je dit schrijft ArrayTimerProgramma
Leerdoelen
leren werken met objecten
gebruik maken van properties en methodes
We zullen het programma uit om het aantal dagen tot een verjaardag te bepalen (uit de theorie) aanpassen zodat het aantal dagen tot de volgende verjaardag wordt getoond. Dit betekent dat er nooit 0 dagen tot een verjaardag zijn, maar in extreme gevallen duizenden dagen kunnen over gaan (bijvoorbeeld: van 29 februari 1996 tot 29 februari 2004).
Je moet één geval toevoegen, namelijk het geval waarin het oude programma 0 dagen tot de volgende verjaardag zou geven.
Noem je methode VerjaardagProgramma
. Maak deze methode toegankelijk via ToonSubmenu
van de klasse DateTimeOefeningen
werken met klassen en objecten
instantieattributen
instantiemethoden
Dit programma geeft op basis van de input van twee getallen de som van beide getallen, het verschil, het product en de deling. In het laatste geval en indien er een deling door nul zou worden uitgevoerd, wordt dit woordelijk weergegeven.
Voorzie voor volgende oefening eerst een nieuwe submenuklasse met als naam EigenObjectOefeningen
.
Maak een eigen klasse GetallenCombinatie
in een eigen file, GetallenCombinatie.cs
. Deze klasse bevat 2 getallen (type int
). Er zijn 4 methoden, die allemaal een double
teruggeven:
Som
: geeft som van beide getallen weer
Verschil
: geeft verschil van beide getallen weer
Product
: geeft product van beide getallen weer
Quotient
: geeft deling van beide getallen weer. Print "Fout"
naar de console indien je zou moeten delen door 0 en voer dan de deling uit. Wat er dan gebeurt, is niet belangrijk.
Gebruik public
attributen Getal1
en Getal2
. Plaats onderstaande code in een publieke statische methode van EigenObjectOefeningen
met naam DemonstreerOperaties
met return type void
:
Zorg dat je DemonstreerOperaties
kan oproepen via het submenu van EigenObjectOefeningen
.
Deze oefening veronderstelt dat je de theoriefilmpjes hebt gevolgd en dat je daar de klasse Student
al hebt aangemaakt in een SchoolAdmin project.
werken met klassen en objecten
opstart van het project
Dit programma vraagt om de naam en leeftijd van een student. Vervolgens worden de punten voor 3 vakken gevraagd, waarna het gemiddelde wordt teruggegeven.
Breid je klasse Student uit met een tweede array CursusResultaten
. Voorzie ook een methode Kwoteer
om een cijfer aan een cursus met een bepaalde index toe te kennen. Signaleer ongeldige waarden met een Console.WriteLine("Ongeldig cijfer!")
. Je kan ook nooit een cijfer boven 20 behalen.
Voeg aan de klasse een methode Gemiddelde()
toe. Deze berekent het gemiddelde van de niet-null
cursussen als double
.
Maak het return type van BepaalWerkbelasting
ook een byte
.
Voeg ook een methode ToonOverzicht
toe, die de persoonsgegevens en behaalde cijfers voor elke student toont. Kijk naar de voorbeeldinteractie voor de juist voorstellingswijze.
Test je programma door een statische methode (in de klasse Student
), DemonstreerStudenten
te voorzien, die twee studenten aanmaakt via variabelen student1
, student2
. Elke student is ingeschreven voor minstens drie vakken die je zelf kiest en krijgt een geldig cijfer (naar keuze) voor elk vak, een naam en een geldige leeftijd. Vervolgens wordt van elke student de ToonOverzicht
-methode opgeroepen. In je Main
-methode voorzie je een (niet-genest) keuzemenu dat vraagt wat je wil doen en op dit moment is de enige optie DemonstreerStudenten
uitvoeren.
Een overzicht van de klasse na al deze wijzigingen:
Voorbeeldcode om de eerste student aan te maken:
Commit je aanpassingen na deze oefening!
Deze klasse hoort bij het SchoolAdmin project.
werken met klassen en objecten
opstart van het project
arrays van objecten maken
We zullen studenten groeperen in cursussen. Bij elke cursus horen op dit moment exact twee studenten.
Werk verder in het SchoolAdmin project. Maak in dit nieuw project een nieuwe klasse Cursus
in een file Cursus.cs
. Deze klasse heeft twee attributen: Studenten
en Titel
. Studenten
is een array van Student
-objecten. De initiële waarde voor dit attribuut is een array met een capaciteit van 2 studenten. Titel
is gewoonweg een string
. Cursus
heeft ook een methode ToonOverzicht
die de titel van de cursus toont, gevolgd door de namen van alle studenten die de cursus volgen.
Test je programma door een statische methode (in de klasse Cursus
), DemonstreerCursussen
te voorzien, die vier cursussen ("Communicatie", "Databanken", "Programmeren" en "Webtechnologie") aanmaakt via variabelen communicatie
, programmeren
, webtechnologie
en databanken
. Maak ook twee studenten aan (dezelfde als in DemonstreerStudenten
) en maak hen lid van de cursussen waarvoor ze een cijfer hebben (zie voorbeeldinteractie van de vorige oefening). Toon tenslotte voor elke cursus het overzicht via ToonOverzicht
. De methode DemonstreerCursussen
kan ook opgeroepen worden via het keuzemenu in Main
.
Je klasse Cursus
ziet er uiteindelijk zo uit:
Commit je aanpassingen!
wegwerken gesynchroniseerde arrays
encapsulatie
access modifiers
De eerdere oefening H10-StudentKlasse gebruikte gesynchroniseerde arrays. We willen deze wegwerken.
Voorzie een klasse CursusResultaat met twee velden: Naam
en Resultaat
. Het eerste stelt de naam van de cursus voor, het tweede het behaalde resultaat.
Vervang vervolgens de arrays cursussen
en CursusResultaten
door één private
array van objecten van deze nieuwe klasse met naam cursusResultaten
. Vervang RegistreerVoorCursus
door RegistreerCursusResultaat
om dit mogelijk te maken (met een parameter voor de naam en een parameter voor het cijfer). DemonstreerStudenten
moet identiek dezelfde uitvoer blijven produceren als tevoren.
Deze oefening vraagt om veel aanpassingen, maar ze zijn niet zo groot. Hou vooral je hoofd erbij en denk goed na over hoe je elk fout gemarkeerd stukje code kan herschrijven wanneer je de oude arrays hebt verwijderd.
Commit je aanpassingen!
werken met klassen en objecten
gebruik maken van properties om geldige waarden af te dwingen
Dit programma maakt enkele rechthoeken en driehoeken met gegeven afmetingen (in meter) aan, berekent hun oppervlakte en toont deze info aan de gebruiker. De rechthoeken en driehoeken die worden aangemaakt, zijn al gecodeerd in het programma. De gebruiker hoeft dus niets anders te doen dan het programma te starten.
Er is een klasse Rechthoek
met full properties Breedte
en Hoogte
en een klasse Driehoek
met Basis
en Hoogte
. Je programma maakt de figuren die hierboven beschreven worden aan met beginwaarde 1.0
voor elke afmeting en stelt daarna hun afmetingen in via de setters voor deze properties. De oppervlakte wordt bepaald in een read-only property (dus met alleen een getter en geen setter). Deze heet Oppervlakte
en is van het type double
.
Indien om het even welk van deze properties wordt ingesteld op 0
of minder, signaleer je dit via de code Console.WriteLine($"Het is verboden een (afmeting) van (waarde) in te stellen!")
(zie voorbeeldcode).
De wiskundige formule voor de oppervlakte van een driehoek is basis * hoogte / 2.
Schrijf de voorbeelden uit in een static
methode DemonstreerFiguren
van de klasse EigenObjectOefeningen
.
(Er worden twee rechthoeken en twee driehoeken aangemaakt. De afmetingen van de eerste rechthoek worden eerst op -1
en 0
ingesteld. Daarna krijgen ze de waarden die je ziet in het bericht hieronder. Formatteer ook met 1 cijfer na de komma.)
Al deze oefeningen maak je in een klasse Loops
flowchart omzetten naar code
gebruik van een while
-lus
Je vraagt de gebruiker een positief geheel getal en vervolgens ga je aftellen. Alle getallen vanaf dat getal tot 1 worden getoond onder elkaar. Na het tonen van het laatste getal toon je “Start!”.
Maak een methode met de naam CountDown
Zet volgende flowchart om in code:
Voer een negatief getal in.
Gebruik van een do while
-lus
De gebruiker wordt gevraagd om een wachtwoord in te geven totdat het juiste antwoord (“AP”) wordt gegeven. Toon daarna “Wachtwoord in orde!”.
Tel ook het aantal pogingen dat de gebruiker nodig had om het juiste antwoord te geven en toon het aantal pogingen.
Maak een methode met de naam Wachtwoord
Zet volgende flowchart om in code:
· Duw meteen op ENTER
Gebruik van een while
of do while
lus.
Bereken het gemiddelde van een aantal ingegeven getallen. De invoer van de getallen stopt met ingeven van de waarde 0.
Maak een methode met de naam Gemiddelde
. Het gemiddelde bereken je door de ingegeven getallen te delen door het aantal ingegeven getallen. De 0 die de reeks getallen stopt, wordt niet meegeteld als ingegeven getal.
Werk een oplossing uit met gebruik van een while lus of een do while lus. Zorg ervoor dat je gemiddelde ook de cijfers na de komma bevat.
Voorbeeldinteractie(s
Test met negatieve waarden
Geef dadelijk een 0 in. Wat wordt er dan getoond als waarde van het gemiddelde en wat betekent dit?
· Gebruik van een while
met samengestelde booleaanse expressie
Je organiseert een feestje en met een programma ga je de inschrijvingen noteren. Je kan echter maximaal 20 personen inschrijven én je kan elk moment beslissen dat je geen volgende persoon meer wil inschrijven. Op het einde toon je alle namen van de ingeschrevenen.
Maak een methode met de naam Feestje
Werk een oplossing uit met gebruik van een while lus.
Geef dadelijk “nee” in.
Probeer meer dan 20 personen in te schrijven.
Gebruik van een do while
Schrijf een programma dat het aantal cijfers in een getal telt (het getal 12348 heeft bijvoorbeeld 5 cijfers). Het is de bedoeling dat je dit doet met een loop, dus niet door het getal als tekst te behandelen.
Maak een methode met de naam AantalCijfers
Het is de bedoeling dat je het aantal digits telt met een do while loop, dus niet door het getal als tekst te behandelen.
Gebruik van een while
met een geneste if
.
Flowchart omzetten in code
Schrijf een programma dat de som maakt van alle even getallen van 1 tot een waarde n. Toon niet enkel de eindsom maar ook alle even getallen met de tussentijdse sommen.
Maak een methode met de naam SomEvenGetallen
Zet volgende flowchart om in code:
Druk dadelijk op ENTER.
Geef 0 in.
Geef 1 in.
Gebruik van een while
met geneste if
’s.
Schrijf een programma een getal n ontbindt in factoren. Factoren zijn de getallen waardoor je n kan delen zonder rest (van bijvoorbeeld het getal 100 zijn de factoren 1,2,4,5,10,20,25,50,100.
Maak een methode met de naam Factoren
Gebruik een while loop. Gebruik een extra if om ervoor te zorgen dat er op het einde van de factoren geen komma teveel staat.
Geef 0 in.
Geef 1 in.
Geef een negatief getal in.
Geef een priemgetal in.
· Gebruik van een do while
met geneste if
.
DNA heeft steeds een RNA-complement (DNA is het gevolg van RNA transscriptie). Schrijf een programma dat een ingevoerde DNA-string omzet naar een RNA-string. De gebruiker voert steeds 1 DNA-nucleotide (m.a.w. één letter) in per keer en duwt op enter, de RNA string wordt steeds groter. De omzetting is als volgt:
· G wordt C
· C wordt G
· T wordt A
· A wordt U
· “stop” dan stopt de gebruiker met letters ingeven en wordt het resultaat getoond.
· Andere letters of meer dan één letter worden genegeerd
Noem de methode voor deze oefening RNATranscriptie
.
Geef andere letters in dan G, T, C of A.
Geef meerdere letters in.
Geef een getal in.
Druk op ENTER.
Gebruik van een do while
met geneste if
Gebruik van een oneindige lus
Maak een 'boekhoudprogramma': de gebruiker kan continu positieve en negatieve getallen invoeren. Dit programma houdt volgende zaken bij:
· de totale balans
· de som van de positieve getallen
· de som van de negatieve getallen
· het gemiddelde
Maak een methode met de naam Boekhouder
Voor de eerste drie zaken kom je toe met een variabele. Voor de laatste is dit lastiger, omdat elk nieuw getal een kleiner effect heeft op het gemiddelde dan het vorige. Je houdt beter een teller bij met het aantal ingevoerde getallen. Dan is het gemiddelde de totale balans gedeeld door het aantal ingevoerde getallen.
(Dit programma kan blijven verder lopen zo lang je wil.)
· Druk dadelijke op ENTER.
Gebruiken van een for
-lus
Aanpassen 'update'
Flowchart omzetten in code
Basis: Toon alle natuurlijke getallen van -100 tot 100.
Uitbreiding: Toon alle even natuurlijke getallen van -100 tot 100.
Maak een methode met de naam VanMin100Tot100
.
Zet volgende flowcharts om in code: (als er geen update vermeld is, wordt de wachtervariabele verhoogd met 1)
Basis:
Uitbreiding:
voorbeeldinteractie(s)
Basis
Uitbreiding:
Gebruiken van een for
-lus
Vraag aan de gebruiker van welk getal de tafel van vermenigvuldiging tot 10 moet getoond worden. Toon elke vermenigvuldiging onder elkaar.
Maak een methode met de naam EenTafel
.
voorbeeldinteractie(s)
Testscenario's
Geef 0 in.
Gebruiken van een for
-lus met een geneste if
met een samengestelde booleaanse expressie
Toon alle getallen van 1 tot en met 100 die een veelvoud zijn 6 en die een veelvoud zijn van 8.
Maak een methode met de naam Veelvouden6En8
.
voorbeeldinteractie(s)
Gebruiken van een for
-lus met geneste if
.
Je krijgt een getal van de gebruiker. Je moet nagaan of dit een priemgetal is, d.w.z. of het precies 2 gehele delers heeft.
Maak een methode met de naam PriemChecker
.
Elk geheel getal vanaf 2 heeft minstens 2 gehele delers: 1 en zichzelf. Als dat de enige delers van het gegeven getal zijn, is het priem. Je kan dus nagaan of een getal een priemgetal is door alle getallen vanaf 2 tot het getal zelf te overlopen en na te gaan of deze delers zijn van het getal. Je mag veronderstellen dat de gebruiker minstens 2 intypt.
voorbeeldinteractie(s)
Testscenario's
Test met een negatief getal.
Gebruiken van een while
lus met een geneste for
lus
Je toont de priemgetallen tussen een laagste waarde en een hoogste waarde die door de gebruiker worden ingegeven.
Maak een methode met de naam PriemGenerator
.
Je kan een deel van de code van de vorige oefening gebruiken
voorbeeldinteractie(s)
Testscenario's
Test met een negatieve laagste waarde.
Voorlopig hebben we enkel met 1-dimensionale arrays gewerkt. Je kan er echter ook meerdimensionale maken. In plaats van "reeksen" van data kan je deze zien als tabellen of als figuren.
Als we een array tekenen, doen we dat vaak als een lijn met vakjes op. En als we door een volledige array lopen, doen we dat door de index steeds te verhogen. Om deze reden zeggen we dat arrays een "lineaire" gegevensstructuur zijn, dus "lijnvormig". Een andere manier om dit uit te drukken is "ééndimensionaal".
Een array met de getallen 4, 9, 2, 7, 3, 6, 1, 12 en 9 zouden we ons kunnen inbeelden als volgt:
Het maakt niet echt uit of je dit ziet van links naar rechts, van boven naar onder,... Wat wel belangrijk is, is dat je dit ziet als een reeks waar je in een richting door loopt. Onderstaande voorstelling kan dus ook:
Vaak is het handig om niet met een ééndimensionale voorstelling te werken, maar met een tweedimensionale of driedimensionale voorstelling. Dit kan bijvoorbeeld zijn omdat we data hebben zoals een Excel sheet, een rooster voor het spelletje OXO of vier-op-een-rij, een Sudokupuzzel, een afbeelding (die bestaat uit pixels),...
Een voorbeeld. Kan jij van onderstaande afbeelding in één oogopslag zeggen of er OXO op staat, als de eerste waarde staat voor het vakje linksboven en de laatste voor het vakje rechtsonder?
Niet meteen. Je moet alles eerst "vertalen" in je hoofd naar dit:
Nu kan je veel makkelijker aflezen dat er in de eerste kolom inderdaad OXO staat. Dus de organisatie van je data kan het makkelijker of moeilijker maken bepaalde taken uit te voeren, omdat we visueler kunnen werken.
Meerdimensionale arrays leveren hetzelfde voordeel op wanneer je programmeert. Ze staan toe data op te delen in roosters, zodat het makkelijker wordt cellen in een bepaalde richting te doorlopen (rij per rij, kolom per kolom, in de diepte,...). Dit is vaak nuttig om zaken die we "op het zicht" doen te kunnen toepassen op arrays.
Met meerdimensionale arrays kan je technisch niet meer of minder dan met ééndimensionale arrays. Maar afhankelijk van het probleem maken ze het programmeerwerk wel een pak eenvoudiger.
De syntax voor een meerdimensionale array is heel gelijkaardig aan die voor een eendimensionale array, maar per dimensie boven de eerste zet je een komma tussen de rechte haken. Bijvoorbeeld:
Hierin passen dus 9 strings, maar de extra structuur zal het makkelijker maken rij per rij of kolom per kolom te werken.
Je kan de array ook als volgt initialiseren.
Of hij mag al ingevuld zijn:
Technisch gesproken bevatten de binnenste accolades groepjes waarden in de tweede dimensie van de array en de buitenste ordenen deze groepjes dan in de eerste dimensie. Maar het volstaat als je onthoudt dat je hier zaken kan noteren alsof ze in rijen en kolommen stonden.
Je kan nu elementen opvragen door per dimensie een index mee te geven. Indexen blijven nog steeds vanaf 0 tellen. Bijvoorbeeld:
Indien je de lengte opvraagt van een meer-dimensionale array dan krijg je het totaal aantal posities in de array. Onze OXO-array zal bijvoorbeeld dus lengte 9 hebben. Je kan echter de lengte van iedere aparte dimensie te weten komen met de GetLength()
methode die iedere array heeft. Als parameter geef je de dimensie mee waarvan je de lengte wenst.
Het aantal dimensies van een array wordt trouwens weergegeven door de Rank
eigenschap die ook iedere array heeft. Bijvoorbeeld:
De Split
methode laat toe een string te splitsen op een bepaald teken. Het resultaat is steeds een array van strings.
Uiteraard kan je dit dus gebruiken om op eender welk char
te splitsen en dus niet enkel op een ','
(komma).
Dit is bijzonder nuttig voor het verwerken van bestanden met gestructureerde data, bijvoorbeeld CSV-bestanden. Dit zijn bestanden waarin elke regel een groepje verwante gegevens voorstelt, gescheiden via een afgesproken symbool zoals ,
. Deze files worden vaak gebruikt als exportformaat voor spreadsheets en databasetabellen.
Via Join
kunnen we array van string
terug samenvoegen. Het resultaat is een nieuwe string
.
Volgende voorbeeld zal de eerder array van het vorige voorbeeld opnieuw samenvoegen maar nu met telkens een ;
tussen iedere string:
Alle gegevens in het geheugen van je computer worden voorgesteld als een opeenvolging van binaire cijfers. Dat is gewoon veruit de handigste manier om computers te bouwen. Helaas is het niet de handigste voorstelling. Als je een bestand opent in een text editor, zie je bijvoorbeeld niet 0010001110001101010101110
(enzovoort), maar wel hallo wereld
, مرحبا بالعالم of 你好,世界. Dat komt omdat er afspraken gemaakt zijn rond welke reeksen bits welke karakters voorstellen. Die afspraken zijn doorheen de tijd ontstaan. Een van de simpelste is 7-bit ASCII, waarbij één byte gebruikt werd om een karakter voor te stellen. 7 bits bepaalden welk karakter werd voorgesteld (er waren er maximum 128), de achtste diende om te controleren dat er geen datacorruptie was opgetreden.
Naarmate computers in meer regio's gebruikt werden, werd duidelijk dat 128 karakters niet volstonden om elke taal van de wereld digitaal voor te stellen. Laat staan de emoji die we vandaag hebben. Als oplossing voor dat probleem is de Unicode standaard vastgelegd. Deze koppelt, eenvoudig gesteld, aan een hele grote reeks getallen een overeenkomstig symbool.
Het is duidelijker als je dit in de praktijk ziet. Volgende voorbeelden komen van :
De Unicode standaard is niet genoeg om tekst voor te stellen in de praktijk. Je moet immers ook weten hoe je bits moet samen nemen. Anders gezegd: maak je groepjes van één byte, van twee bytes, van drie bytes,... om zo de getallen te vinden die symbolen voorstellen? Er zijn meerdere antwoorden op die vraag en in de praktijk gaat het eerder om combinaties. Zo kan je zeggen dat je mééstal 1 byte gebruikt, maar soms meer als je aan één byte niet genoeg hebt (omdat je een symboolnummer groter dan 255 hebt). Hoe dat precies werkt, laten we hier achterwege. Maar we onthouden wel dat we hier per bestand een afspraak rond moeten maken. Deze afspraak noemen we de karakterset of encodering van ons bestand. C#-programma's veronderstellen intern bijvoorbeeld (net als JavaScript programma's) dat je gewoonlijk twee bytes gebruikt om een karakter voor te stellen, maar ze hebben een achterpoortje waardoor je meer dan twee bytes kan gebruiken. Deze encodering heet UTF-16. Dit betekent niet dat je in C# niet met bestanden in een andere karakterset kan werken of zelfs dat je .cs-files UTF-16 moeten gebruiken. Maar het bepaalt wel hoe we speciale karakters kunnen noteren via escape symbolen.
Om één karakter voor te stellen, gebruiken we het type char
. Je kan hier een waarde aan toekennen door ze tussen enkele quotes te plaatsen, als volgt:
Tegelijkertijd kan je er mee rekenen. Dan ga je eigenlijk met het Unicode nummer van dat symbool werken. Om terug toe te kennen aan een karakter, moet je wel weer casten. Bijvoorbeeld:
Dit werkt, want in de Unicode standaard komt de g na de f.
Je kan karakters ook noteren via hun Unicode nummer, door een "Unicode escape" te gebruiken. Hierbij noteer je eerst een backslash, dan een u
en dan het Unicode nummer in hexadecimale notatie. Als je dit print op je scherm, zie je net hetzelfde als met de notatie hierboven. Het voordeel is dat je het symbool niet op je toetsenbord moet hebben staan.
Er zijn ook "gewone" escapes voor tekens die je vaak nodig hebt. Deze zijn onder andere '\n'
(newline) en '\t'
(tab). '\u000A'
en '\u0009'
werken ook, maar zijn wat lastiger te onthouden. Let wel op: als je een backslash wil gebruiken zonder dat deze een escape sequentie activeert, moet je de backslash zelf escapen. Dus \\
stelt eigenlijk het karakter \
voor.
Wat hier verder opvalt: we hebben maximum 4 hexadecimale cijfers, dus in theorie 65536 karakters. Het is waar dat we maar 65536 waarden van het type char
hebben. Maar binnenin een string
kan je ook karakters noteren en in deze situatie zijn er combinaties van karakters die als één geheel gezien worden. We noemen deze surrogate pairs en ze geven weer als één karakter. Bijvoorbeeld:
Je kan emoji gebruiken in je programmatekst, maar je moet de terminal ook verwittigen dat je dit wil doen. Hiervoor zet je de encodering van de terminal via Console.OutputEncoding = Encoding.UTF8
.
Door een @
(verbatim character) voor een string te plaatsen zeggen we concreet: "de hele string die nu volgt moet je beschouwen zoals hij er staat. Je mag alle escape karakters negeren en er mogen line breaks voorkomen in deze string."
Dit wordt vaak gebruikt om een filepath iets leesbaarder te maken.
Zonder verbatim: string path= "c:\\Temp\\myfile.txt";
Met verbatim: string path= @"c:\Temp\myfile.txt";
Bovenstaande strings zijn identiek voor C#! De @ geeft geen enkele extra functionaliteit, maar zorgt gewoon dat we dezelfde tekst (meerbepaald: backslashes en line breaks) wat makkelijker kunnen intypen.
Volgende stukken code doen dus hetzelfde:
Intern is er géén verschil. Voor de WriteLine
wordt uitgevoerd, worden beide waarden in dezelfde voorstelling gegoten. Maar in dit geval is de eerste versie wel veel makkelijker te lezen voor de programmeur.
Via stringinterpolatie schrijf je je string min of meer zoals hij er uiteindelijk moet uitzien, maar vervang je de niet-letterlijke delen door geformatteerde waarden. Dit levert een goed leesbaar resultaat.
Door het $
-teken VOOR de string te plaatsen geef je aan dat alle delen in de string die tussen accolades staan als code mogen beschouwd worden. Een voorbeeld maakt dit duidelijk:
In dit geval zal dus de inhoud van de variabele name
tussen de string op de plek waar nu {name}
staat geplaatst worden. Idem voor age
. Dit mag, zelfs al is age
geen string: hetgeen tussen de accolades staat, wordt altijd intern omgezet naar een string voor het in het resultaat wordt geplaatst. Dit gebeurt via de methode ToString
, die voor elk soort data bestaat. Dus eigenlijk wordt het achter de schermen:
Je mag eender welke expressie tussen de accolades zetten bij string interpolation. Een expressie is iets dat je kan uitrekenen en dat je een resultaat oplevert. Bijvoorbeeld:
Alle expressies tussen de accolades zullen eerst uitgevoerd worden voor ze tussen de string worden geplaatst. De uitvoer wordt nu dus: Ik ben Finkelstein en ik ben 17 jaar oud.
Eender welke expressie is toegelaten, dus je kan ook complexe berekeningen of zelfs andere methoden aanroepen:
Bij stringinterpolatie kan je ook bepalen hoe de te tonen variabelen en expressies juist weergegeven moeten worden. Je geeft dit aan door na de expressie, binnen de accolades, een dubbelpunt te plaatsen gevolgd door de manier waarop moet geformatteerd worden:
Wil je bijvoorbeeld een kommagetal tonen met maar 2 cijfers na de komma dan schrijf je:
Hier stelt 2
het aantal cijfers na de komma voor. Je kan zelf kiezen om meer of minder cijfers te tonen door het getal aan te passen. Dit voorbeeld is gelijkaardig aan Console.WriteLine(Math.Round(number,2))
, maar niet identiek. Dat komt omdat de geformatteerde string verplicht twee cijfers na de komma toont, zelfs als die niets veranderen aan de waarde van het getal. Als je een double zonder formattering weergeeft, worden nullen achteraan niet getoond. Dus voor getallen als 12
of 15.1
of 4.999
(met minder dan twee cijfers na de komma na afronding) maakt het een verschil.
Nog enkele nuttige vormen:
D5: geheel getal bestaande uit 5 cijfers (123
wordt 00123
) (werkt enkel op gehele getallen en je kan het getal 5 vervangen door een positieve waarde naar keuze)
C: geldbedrag (12,34
wordt € 12,34: teken van valuta afhankelijk van instellingen pc)
Je kan je data ook een vaste breedte en uitlijning geven. Dit doe je door meteen na de waarde in kwestie een komma te plaatsen, met daarna de gewenste breedte. De tekst wordt dan rechts gealigneerd. Je kan ook een minteken gebruiken en dan wordt de tekst links gealigneerd. Bijvoorbeeld:
Dit levert:
Dus de tekst "hallo"
wordt opgevuld totdat hij in totaal 20 karakters telt. Elke regel telt 27 karakters, want " wereld"
is niet mee geformatteerd, maar is gewoon achter de geformatteerde tekst gezet.
Formattering is een handig hulpmiddel, maar het zal niet altijd alle scenario's dekken. In het voorbeeld hierboven is het bijvoorbeeld verplicht een constante waarde te gebruiken. Je kan geen variabele invullen waar 20
staat. Het is bijvoorbeeld ook niet mogelijk tekst te centeren (in plaats van links of rechts uit te lijnen). Dat hoeft geen probleem te zijn: je kan altijd een methode schrijven die tekst op de gewenste manier omzet.
Via PadLeft
en PadRight
kan je tekst opvullen tot de gewenste lengte met andere karakters dan een spatie. Bijvoorbeeld, voor hetzelfde effect als eerder:
Zoals vaak lever je hiermee wat gemak in voor extra flexibiliteit. Je kan namelijk opvullen met een karakter naar keuze, tot een variabele lengte:
Als er geen mehode is om een string juist te tonen, kan je er altijd zelf een maken. Je zorgt dat de string en extra opties zoals de gewenste breedte parameters zijn en je geeft de geformatteerde string terug als resultaat. Er is bijvoorbeeld geen methode om tekst in te korten tot een maximale lengte, maar je kan ze zelf schrijven als volgt:
Je zou bijvoorbeeld ook een methode kunnen schrijven om tekst centraal te aligneren.
In deze laatste les zullen we nog een waardevolle verbetering toevoegen aan het TextCell project. Meerbepaald zullen we er voor zorgen dat we rijen en kolommen hebben, zoals een echte spreadsheet.
We starten hierbij vanaf de code die we vorige keer hebben opgebouwd.
Om deze uitbreiding te doen, onthouden we best dat een tweedimensionale array een uitbreiding is op een ééndimensionale. Je denkt altijd best na over wat er gebeurt los van de code en doet dan pas de aanpassing.
Bekeken van uit het perspectief van de eindgebruiker:
Bekeken uit het perspectief van de programmeur:
TextCellMain
Bij het aanmaken van het rooster, vragen we nu hoe veel rijen en hoe veel kolommen het rooster zal tellen in plaats van hoe veel cellen. We vullen deze ook allemaal op met lege stringwaarden.
WijzigCel
In plaats van te vragen welke cel je wil wijzigen (als lettercode), vraag je nu welke rij (als getalcode) en welke kolom (nog steeds als lettercode) je wil wijzigen.
BerekenEnToonRooster
Het is de bedoeling dat deze methode een berekende weergave van ons achterliggend rooster bepaalt. Deze berekende weergave heeft dezelfde afmetingen als ons achterliggend rooster. Verder moeten we nu meerdere niet één rij tonen, maar meerdere rijen. We nummeren de rijen voortaan ook in de visuele weergave.
KopieerCellenZonderFormule
Het idee blijft hetzelfde: als een cel een letterlijke waarde bevat, kunnen we ze meteen overnemen in de overeenkomstige cel van de uitgerekende versie van het rooster. We moeten alleen rekening houden met het feit dat we nu niet één rij kopiëren, maar meerdere rijen.
BevatNullWaarden
Het idee blijft hetzelfde: we doorlopen alle cellen. Als we er een tegenkomen die null bevat, is het antwoord meteen true
.
BerekenOntbrekendeWaardenEenKeer
Zowel ons inputrooster als ons outputrooster zijn nu tweedimensionaal. We moeten dus meerdere rijen doorlopen in plaats van één rij, maar het idee blijft hetzelfde: als we een cel tegenkomen waarvan we de waarde nog niet eerder konden invullen, bekijken zullen we bekijken of we de formule intussen kunnen uitrekenen.
BerekenOntbrekendeCelRechtstreeks
We splitsen een som nog steeds door op zoek te gaan naar de plustekens. Het enige verschil is dat ons berekend rooster van type verandert.
WaardeVanCel
Deze methode diende om een symbolenreeks te vertalen naar een (tekstvoorstelling van een) getal. Deze symbolenreeks kon al een getal voorstellen, of het kon een verwijzing naar een andere cel zijn. Deze verwijzingen zijn iets complexer geworden. Eerder was het gewoon een (reeks) letter(s) die een kolom aanduidde, zoals A
of B
. Die werden dan vertaald naar 1 of 2 en dan werd het zoveelste element van de ééndimensionale array opgezocht. Nu is een verwijzing een reeks letters (voor de kolom) gevolgd door een reeks cijfers (voor de rij). Om deze aanpassing te doen, moeten we de twee delen van elkaar scheiden. We kunnen dat doen door te kijken waar in de celnaam we het eerste cijfer tegenkomen. Eens we de rij en kolom hebben in een gebruiksvriendelijke notatie, zetten we ze om naar array indexen.
Onze spreadsheet ziet er al een stuk nuttiger uit. Zoals eerder gezegd vormt een uitbreiding op dit project een oefening van het examen. Zorg dus dat je de code hebt, maar ook dat je de structuur ervan goed begrijpt. Je kan je ook voorbereiden door zelf wat uitbreidingen te verzinnen en deze in aangepaste kopieën van je klasse te schrijven. Enkele goede oefeningen voor jezelf:
zorgen dat rijnummers mooi blijven weergeven boven 2 cijfers (door eerst uit te rekenen hoe veel cijfers de laatste rij nodig heeft)
zorgen dat je kolommen vanzelf breed genoeg zijn om de breedste waarde in die kolom volledig voor te stellen (door eerst alle waarden in de kolom te doorlopen)
positieve getalwaarden groen kleuren en negatieve getalwaarden rood kleuren
niet alleen sommen, maar ook vermenigvuldigingen ondersteunen
je spreadsheet omzetten naar een CSV-bestand (dat je dan zou kunnen openen in een volwaardige spreadsheet editor)
hoofdingen en/of content automatisch centreren of rechts uitlijnen
Object Oriented Programming of korter OOP is een techniek afkomstig van higher level programmeertalen zoals Java, C#, VB.NET, ... en vindt zijn oorsprong bij de programmeertaal Smalltalk, die het eerst de term Object Oriented Programming introduceerde.
In recentere jaren heeft deze techniek echter ook zijn weg gevonden naar scripting talen zoals Python, Ruby, Perl en zelfs PHP.
OOP streeft ernaar om een project zo structureel mogelijk op te bouwen in objecten. Dit heeft voor de programmeur het grote voordeel dat code vanaf nu in logische componenten wordt opgedeeld en veel makkelijker te hergebruiken is.
Om het concept van objecten te illustreren wordt vaak een voorwerp uit het dagelijks leven als voorbeeld gebruikt. Neem bijvoorbeeld een auto. De auto is het object en dit object heeft zowel geassocieerde data als functionaliteit. Deze data stellen eigenschappen of onderdelen van het object voor. Een eigenschap van de auto kan de kleur, de lengte, het aantal kilometers op de teller, of zelfs zijn huidige locatie of snelheid zijn. Deze zaken worden geïmplementeerd met ofwel instantievariabelen, ofwel properties. De functionaliteit is iets dat het object kan doen. Deze wordt geïmplementeerd met instantiemethodes. Als je een auto als object ziet, kunnen deze bijvoorbeeld starten, versnellen of remmen zijn.
Auto's zijn een veelgebruikt voorbeeld, maar eigenlijk schrijf je er zelden objecten voor tenzij je een soort simulator wil maken. Objecten hoeven niet altijd tastbare zaken te zijn. Je kan bijvoorbeeld ook een lening bij de bank voorstellen als een object. Dit object heeft dan onder andere volgende eigenschappen:
een begunstigde, d.w.z. de persoon die de lening heeft aangegaan
een restsaldo, d.w.z. het bedrag dat je moet betalen
een looptijd, d.w.z. de periode waarbinnen de lening afbetaald moet zijn
een intrestvoet, die uiteindelijk bepaalt hoe veel je bovenop het oorspronkelijke bedrag betaalt
Maar met een lening kunnen we ook bepaalde zaken doen die we wel in een computersysteem zouden implementeren. Deze zaken vormen de functionaliteit, dus de verzameling instantiemethodes, van het lening-object:
een afbetaling verwerken
de uiteindelijk af te betalen som berekenen
het afbetaald kapitaal en de afbetaalde interest na een bepaald aantal maanden berekenen
Dus voor zaken die (minstens) zo uiteenlopend zijn als een auto en een lening hebben we een combinatie van data en functionaliteit. Zo zit een object in een programmeertaal er ook uit.
Een belangrijk concept bij OOP is het black-box principe waarbij we de afzonderlijke objecten en hun werking als "zwarte dozen" gaan beschouwen. Dat wil zeggen dat we (meestal) niet gaan kijken hoe ze in elkaar zitten. Neem het voorbeeld van de auto: deze is in de echte wereld ontwikkeld volgens het blackbox-principe. De werking van de auto kennen tot in het kleinste detail is niet nodig om met een auto te kunnen rijden. De auto biedt een aantal zaken aan de buitenwereld aan (het stuur, pedalen, het dashboard), wat we de publieke interface of gewoonweg interface noemen. Deze zaken kan je gebruiken om de interne staat van de auto uit te lezen of te manipuleren. Stel je voor dat je moest weten hoe een auto volledig werkte voor je ermee op de baan kon.
Binnen OOP wordt dit blackbox-concept encapsulatie genoemd. Het doel van OOP is andere programmeurs (en jezelf) zoveel mogelijk af te schermen van de interne werking van je code. Net als bij een auto, moet je alleen weten wat elk stukje van de interface klaarspeelt, maar hoef je niet te weten hoe dit allemaal is geprogrammeerd.
Encapsulatie wordt vaak een van de vier pijlers van objectgeoriënteerd programmeren genoemd. De andere zijn abstractie, overerving en polymorfisme. Deze komen allemaal later in de cursus aan bod.
Een elementair aspect binnen OOP is het verschil beheersen tussen een klasse en een object.
Wanneer we meerdere objecten gebruiken van dezelfde soort dan kunnen we zeggen dat deze objecten allemaal deel uitmaken van een zelfde klasse. Zo hebben we bijvoorbeeld de klasse van de leningen. Er zijn leningen van €50.000, er zijn leningen van €10.000.000. Er zijn er met een intrestvoet van 0.2% en er zijn er met een intrestvoet van 2%. Er zijn er met een looptijd van 5 jaar en er zijn er met een looptijd van 20 jaar. Het zijn allemaal leningen, maar ze hebben allemaal andere eigenschappen.
Als je vertrouwd bent met relationele databanken: een klasse is vergelijkbaar met een tabel of entiteittype, terwijl een object vergelijkbaar is met een record of een entiteit.
Objecten van dezelfde soort volgen wel dezelfde regels en hebben in dat opzicht dezelfde functionaliteit. Maar de eigenschappen van object bepalen ook het resultaat van de instantiemethodes. Eerder zagen we dat de uiteindelijk af te betalen som berekend kan worden door een lening. Deze som zal per definitie hoger zijn van een lening met een hoge rentevoet dan voor een lening met een lage rentevoet (als de andere eigenschappen gelijk zijn).
Samengevat:
Een klasse is een beschrijving en verzameling van dingen (objecten) met soortgelijke eigenschappen en gedrag.
Een individueel object is een instantie (exemplaar, voorbeeld, verschijningsvorm) van een klasse
De data en functionaliteit van een klasse, en ook haar relatie tot andere klassen, wordt vaak voorgesteld in een UML-klassendiagram. Hoe je verschillende klassen met elkaar verbindt, houden we voor iets verderop. Ook enkele andere details volgen later. Wat we wel al kunnen vertellen:
elke klasse wordt voorgesteld als een rechthoek, met bovenaan in die rechthoek de naam van de klasse
in een tweede vakje worden dan de eigenschappen gegeven, gewoonlijk met hun datatype
in een derde vakje worden dan de methoden gegeven, met hun parameters en hun returntype
Het (return) type kan voor de naam van een attribuut of methode staan (zoals in C#), of het kan helemaal achteraan staan, voorafgegaan door een dubbele punt (zoals in TypeScript).
Dit vertelt ons dat een object van klasse Lening
beschikt over een saldo en een intrestvoet en het vertelt ons ook welke datatypes we gebruiken om deze voor te stellen. Bovendien kan een Lening
zelf bepalen hoe veel we in totaal zullen moeten betalen als we elke maand een vast bedrag aflossen. Dit is functionaliteit van het object, die we zullen implementeren met een instantiemethode.
Dit vertelt ons dat elke Auto
een eigen kilometerstand, bouwjaar en huidige snelheid heeft. Het is mogelijk de Auto
te laten starten en om hem te laten versnellen met een geven aantal kilometer per uur.
Een UML-klassendiagram dient voor... klassen. Als je wil kijken naar objecten, gebruik je een sequentiediagram. We gaan hier nog niet erg diep op in, maar we maken alvast duidelijk dat je op een klassendiagram geen objecten ziet.
Een voorbeeld van een sequentiediagram voor een programma dat twee leningen aanmaakt (bijvoorbeeld een programma in een bank om een simulatie te maken voor een klant):
Het figuurtje met als naam ExterneCode
stelt iets voor dat ons eigenlijk niet interesseert. Wat de pijlen en de berichten daarop technisch betekenen, maakt voorlopig ook niet zo uit. Het belangrijkste hier is dat er twee objecten van de klasse Lening
zijn. Dit wordt extra in de verf gezet omdat ze twee verschillende namen hebben (lening1
en lening2
), maar allebei hetzelfde type na de dubbele punt (Lening
). Op een klassediagram ga je nooit twee keer een box voor dezelfde klasse zien.
In C# kunnen we geen objecten aanmaken voor we een klasse hebben gedefinieerd die de algemene eigenschappen (properties) en werking (methoden) beschrijft.
Een heel elementaire klasse heeft de volgende vorm:
We maken hier normaal gebruik van Pascal case. Je kan ook nog enkele toevoegingen doen die niet zuiver data en functionaliteit zijn, maar die houden we voor verder in de cursus.
Volgende code beschrijft de klasse Auto
in C#
Binnen het codeblock dat bij deze klasse hoort zullen we verderop dan de werking beschrijven.
Je kan "eender waar" een klasse aanmaken, maar het is een goede gewoonte om per klasse een apart bestand te gebruiken. Dit is ook de afspraak die wij zullen volgen.
In de solution explorer, rechterklik op je project
Kies Add
Kies Class...
Geef een goede naam voor je klasse
De naam van je klasse moet voldoen aan de identifier regels die ook gelden voor het aanmaken van variabelen!
Je kan nu objecten aanmaken van de klasse die je hebt gedefinieerd. De klasse is een type en je kan dus ook variabelen aanmaken die bedoeld zijn om objecten van deze klasse in bij te houden. Je doet dit door eerst een variabele te definiëren met als type de naam van de klasse en vervolgens een object te instantiëren met behulp van het new
keyword:
We hebben nu twee objecten aangemaakt van het type Auto.
Let goed op dat je dus op de juiste plekken dit alles doet (bekijk de onderstaande screenshot):
Klassen maak je aan als aparte files in je project
Objecten creëer je in je code op de plekken dat je deze nodig hebt, bijvoorbeeld in je Main
methode bij een Console-applicatie
Je hebt dus in het verleden ook al objecten aangemaakt met new
. Telkens je met Random werkt deed je dit al. Dit wil zeggen dat er dus in .NET ergens reeds een voorgeprogrammeerde klasse Random
bestaat met de interne werking.
Het voelt misschien nog niet zo, maar je kent intussen al redelijk veel programmeerconcepten. Je kent onder meer variabelen van verschillende types, beslissingen, loops, arrays en methoden. Nu is het niet alleen kwestie van die zaken te kennen, maar ook van die zaken te gebruiken. Daarom zullen we deze les een beetje anders aanpakken dan gewoonlijk. We zullen een vereenvoudigde versie van een bestaand programma schrijven. Dit is een uitstekende manier om te leren. Het maakt niet uit dat dat programma al bestaat. Het doel is om bij te leren. Door bestaande software na te bouwen, heb je een duidelijk referentiepunt.
De software die wij hier zullen bouwen is een tekstgebaseerde spreadsheet, zoals Excel of Calc. Onze versie zal meer lijken op de allereerste spreadsheets dan op een moderne spreadsheet, maar de essentie zal er wel zijn. De belangrijkste beperkingen zijn:
We werken tekstgebaseerd. Anders zouden we tijd moeten spenderen aan het bestuderen van een grafische toolkit. In principe zou je dit later kunnen oplossen door een versie in TypeScript te bouwen die tabellen in de browser gebruikt.
We werken ééndimensionaal. We hebben dus een spreadsheet van één rij, in plaats van een spreadsheet met rijen en kolommen. We zullen dit op het einde van het semester rechtzetten, maar we hebben nog wat meer ervaring met arrays nodig om dit vlot te laten verlopen.
We ondersteunen alleen optellingen, geen andere berekeningen. Op zich is het systeem hetzelfde voor bijkomende berekeningen, maar je moet alle gevallen analyseren. In de praktijk wordt zoiets gedaan met een parser en dat gaat wat te ver voor deze demonstratie.
Dat zijn serieuze beperkingen, maar vergeet niet dat spreadsheets als tientallen jaren bestaan en ontwikkeld worden door programmeerteams. Ze kunnen het resultaat zijn van letterlijk duizenden jaren werk door professionele programmeurs.
Tijdens deze les moet je zelf de gedemonstreerde code uitschrijven. Ik zal telkens een stapje uitleggen en dan doen we een uitbreiding van het project. Je zal tijdens de labo's en tijdens het examen een paar uitbreidingen doen aan dit project, dus zorg dat je actief nadenkt over de code en niet gewoonweg overtypt. Als dat betekent dat je moet teruggaan naar een eedere les, doe dat dan. Er is geen snelle manier om goed te leren programmeren, maar dit is de minst trage.
Laat me ook even duidelijke verwachtingen scheppen: Er wordt niet verwacht dat je dit project zelf kan bedenken. Er zal in dit vak nooit een oefening zijn die de omvang van dit project benadert. De concepten die we hier gebruiken zijn bijna allemaal al gekende leerstof, maar om dit project te bedenken heb je naast de concepten ook al redelijk wat ervaring nodig. Het is dus volledig normaal als je tijdens dit project denkt: "Dit had ik zelf nooit bedacht." Het is zelfs de bedoeling. Het punt hier is om je te laten zien dat je niet noodzakelijk erg veel concepten nodig hebt om complexere programma's te schrijven. Je kan best veel met de basics.
Een spreadsheet is in essentie een rooster op basis van rijen en kolommen.
De combinatie van rij en kolom duidt één "vakje" aan. Zo'n vakje noemen we in technische termen een "cel".
Een cel bevat data of een berekening:
data is heel vaak een getal, een stuk tekst of een datum
een berekening begint met =
een berekening mag ingebouwde methodes gebruiken
een berekening mag ook verwijzen naar andere cellen
Merk op: kolommen worden aangeduid met letters en rijen met getallen.
Moderne spreadsheets kunnen natuurlijk veel meer, zoals je ook ziet in de interface van Calc, maar leer stappen voor je leert lopen. Dit is al niet zo makkelijk. Je moet dit eerst goed onder de knie hebben voor je probeert meer te doen.
We zullen de spreadsheet maken in hetzelfde project als onze labo-oefeningen. Dat maakt dat er niet te veel gewisseld moet worden tussen projecten wanneer je hier uitbreidingen voor schrijft tijdens de labosessies. We noemen de klasse TextCell
. Dat is de naam die we verder zullen gebruiken voor onze eigen spreadsheet.
We kijken eerst naar Calc voor wat inspiratie. Wanneer we opstarten, staat er een lege spreadsheet klaar. Het programma wacht eigenlijk gewoon tot we een cel invullen. Wanneer we dat doen, worden alle cellen met daarin een berekening opnieuw berekend. In Calc kan ik bijvoorbeeld cel B1 aanpassen en dan zie je dat cel B2 ook wijzigt, omdat die een berekening op basis van cel B1 bevat. Daarna kunnen we weer aanpassingen doen.
We zullen dit zo goed mogelijk nabootsen. Om te beginnen zetten we een nieuwe, lege spreadsheet klaar. Hoe doen we dit? Een cel lijkt erg op een variabele, want je kan er letterlijke data of het resultaat van een berekening in plaatsen. Anderzijds kunnen we niet voor elke cel een variabele in onze programmacode zetten. Dan zou onze code gigantisch worden en zou er geen manier zijn om systematisch alle cellen te doorlopen. We waren op hetzelfde probleem gebotst toen we arrays introduceerden. Daar konden we geen variabele voorzien voor elk item op een boodschappenlijstje. We zullen onze cellen dan ook voorstellen door middel van een array.
Dit levert drie belangrijke beperkingen op:
Ten eerste, een array hebben we voorgesteld als een rij of een kolom, maar niet als een combinatie van rijen en kolommen. We zullen ons hier tijdelijk bij neerleggen en een een spreadsheet maken waarvan we alleen de eerste rij kunnen invullen. We zullen dit later nog recht zetten.
Ten tweede, de grootte van een array wordt vastgelegd zodra de array wordt aangemaakt. Ook hier leggen we ons voorlopig bij neer. We zullen bij het opstarten van de spreadsheet vragen hoe groot de array moet zijn. Later zullen we zien dat hier ook rond gewerkt kan worden.
Ten derde, een array bevat één type waarden. Dus strings of getallen of booleans of iets anders, maar geen mengeling. Maar merk op dat je in Calc gewoon iets intypt en dat het systeem er dan zelf een interpretatie aan geeft. Bijvoorbeeld: als ik 01/03/1987
intyp, verschijnt dit als 01/03/87
. Dat komt omdat deze tekst er uitziet als een datum. Terwijl Vincent
ongewijzigd blijft. Dus op zich kan je gewoon tekst ingeven en zaken die er uitzien als getallen of datums behandelen als getallen of datums wanneer nodig. Met andere woorden, we zullen een string[]
gebruiken om ons rooster voor te stellen.
Dus we hebben een rooster (i.e. een string[]
) dat bij opstart wordt aangemaakt. Elke cel bevat op dat moment ""
. Eens dat gebeurd is, tonen we het huidige rooster en wachten we tot de gebruiker een cel aanpast, tot het einde ter tijden, of tot het programma gestopt wordt.
In Flowgorithm ziet dit er zo uit:
Ik vul eerst de lege string in in elke cell, omdat er anders de defaultwaarde null
staat. Lege cellen gedragen zich echter als cellen waarbij we letter per letter een backspace hebben uitgevoerd, dus als een cel met een lege string. Bovendien zullen we null
verderop voor iets anders gebruiken.
Voorlopig kan je BerekenEnToonRooster
en WijzigCel
met een lege body implementeren. Zo kan je tenminste het programma compileren.
Aan jullie om dit om te zetten naar code in C#. Schrijf de code met de hand. Deze code begint trouwens in Main
, terwijl het de bedoeling is dat je je spreadsheet kan opstarten van uit je keuzemenu, dus noem je methode TextCellMain
en zet ze in de klasse TextCell
.
Eigenlijk moeten we dus de twee methodes implementeren die in de lus worden uitgevoerd. We zullen beginnen met de methode BerekenEnToonRooster
, omdat we dan zo snel mogelijk onze spreadsheet kunnen zien. We zullen eerst alleen het "toon"-aspect afhandelen. Met andere woorden, we negeren even berekeningen en doen alsof elke cel gewoon een tekstwaarde bevat.
We willen wel al dat onze cellen naast elkaar getoond worden en dat boven elke cel een identifier staat.
Om de identifiers te tonen en om ze te kunnen vertalen naar getallen, moet je wat wiskunde gebruiken. Dat is hier niet de focus, dus je krijgt hier twee methoden voor in de cursustekst. Deze heten GetalVoorstellingNaarLetters
en LetterVoorstellingNaarGetal
. Je moet ze kopiëren uit de tekst en in je klasse zetten. De eerste zet een getal om naar een lettercombinatie (die je boven de cellen kan tonen), de tweede werkt in de omgekeerde richting:
Voor de weergave van de cellen starten we hiermee:
Een probleem hier is dat niet elke cel even breed is. Probeer maar eens uit.
Als oplossing zullen we elke cel 10 karakters breed tonen. Inhoud die verder gaat, wordt niet getoond.
Met een beetje creativiteit kan je elke cel exact zo breed maken als nodig is voor de content, maar om het project kort te houden, doen we dat voorlopig niet.
Dit is vrij eenvoudig. We moeten gewoon vragen welke cel we willen invullen, wat we er in willen plaatsen en dan die positie van de array invullen. Er is wel één puntje waar we aandacht aan moeten besteden.
We hebben tijdens de les rond methoden gezien dat methoden werken met een kopie van het meegegeven argument. Het gevolg daarvan was dat je een parameter kon wijzigen in een methode en dat je dit niet meer zag na de oproep van de methode. Ik herhaal even het voorbeeld uit de contactles:
De reden dat op het einde Het getal is 4
verschijnt, is dat de methode VeranderGetal
een kopie van de waarde heeft gekregen om mee te werken.
Om technische redenen, die we in het vervolgvak uitgebreider zullen bespreken, lijkt dit op het eerste zicht niet helemaal te kloppen voor arrays. Je kan namelijk een array als argument van een methode meegeven. Als je dan een aanpassing doet op een indexpositie van die array, blijft die zichtbaar na de methode. Voor dit vak is dit de enige uitzondering op de regel dat methodes argumenten niet kunnen wijzigen.
Het slechte nieuws is dus dat sommige soorten data complexere regels volgen. Het goede nieuws is dat het heel makkelijk wordt om de methode WijzigCel
te schrijven.
Nu kunnen we waarden invullen en zien we ze verschijnen. Probeer maar eens uit.
Een spreadsheet is geen spreadsheet als je geen berekeningen hebt. We moeten dit slim aanpakken. Cellen kunnen verwijzen naar andere cellen, of die nu eerder of later komen. Bovendien moeten de berekeningen zelf blijven staan in de spreadsheet. We mogen ze dus niet meteen uitrekenen op het moment dat ze worden ingetypt, want dan zouden ze hun resultaten niet updaten.
We zullen volgende aanpak volgen voor een spreadsheet met berekeningen:
We maken dus eerst een kopie van onze spreadsheet, maar we vullen alleen de cellen zonder berekening in. Voor de cellen met een berekening vullen we niets in, zodat er null
staat, de defaultwaarde van een variabele van het type string
. Herinner je dat dit niet hetzelfde is als ""
. Vanaf dan gaan we op zoek naar cellen waarvoor we nog geen resultaat hebben. Als alle nodige inputs voor die cellen al gekend zijn, vullen we ze in. We herhalen dit tot we voor elke cel een resultaat hebben.
De eerste kopie maken doen we als volgt:
De hulpmethode schrijven we als volgt:
Dit maakt dus een tweede array die er uitziet zoals de eerste, maar zonder de formules. Merk ook op dat de hoofdingen in de tekening wel bestaan, maar in code zijn ze niet aanwezig. Ze stellen gewoon indexposities van een array voor, waarbij A wijst op de eerste cel, B op de tweede,...
In een volgende stap doorlopen we de berekende array en proberen we de "vraagtekens" in te vullen. Hoe vaak dit moet gebeuren, hangt af van hoe veel formules er staan en wat de verbanden tussen cellen zijn. Zolang onze spreadsheet juist is opgesteld, eindigt het proces uiteindelijk wel en wordt alles ingevuld. We vatten dit als volgt samen:
Voor de hulpmethode BerekenOntbrekendeWaardenEenKeer
, die één stapje op de tekening voorstelt, doorlopen we alle reeds berekende cellen. Telkens we een nog niet berekende cel tegenkomen, zoeken we de formule op en proberen we die uit te rekenen voor enkel die cel. De implementatie ziet er zo uit:
Om één formule (met andere woorden, een som) uit te rekenen, moeten we eerst bepalen welke onderdelen we moeten optellen. Dat kunnen getallen of andere cellen zijn. We kunnen de tekstvoorstelling van deze onderdelen verkrijgen via de Split
-methode van een string. Deze breekt een string in stukjes op basis van het meegegeven argument.
Laat ons dit al eens uitproberen met sommen van uitsluitend getallen:
Dit werkt als je formules alleen getallen optellen. Nu moeten we nog zorgen dat verwijzingen naar andere cellen herkend worden.
Om dit klaar te spelen, moeten we het verschil kunnen zien tussen iets als A
en 1
. We houden het simpel: we kijken gewoon of het stuk tekst begint met een cijfer. Het kan nauwkeuriger, maar omdat formules alleen getallen of cellen kunnen bevatten, is het voor ons voldoende.:
Nu kunnen we een methode schrijven die namen van cellen vervangt door de reeds uitgerekende waarde, maar getallen met rust laat:
Nu kunnen we zorgen dat sommen ook identifiers van cellen mogen bevatten. We gebruiken de methode die we net geschreven hebben om de waarde van de cel op te zoeken. Als de waarde van de cel nog niet gekend is, kunnen we ook geen waarde voor een formule op basis ervan berekenen. We hebben de basisonderdelen van de som niet echt meer nodig eens we ze hebben opgezocht, dus we mogen ze overschrijven:
Nu nog even uittesten.
De System.IO
namespace bevat tal van nuttige methoden en klassen om met bestanden te werken. Om gebruik hiervan te maken plaats je bovenaan je file:
Via System.File.ReadAllLines()
kunnen we een tekstbestand uitlezen. De methode geeft een array van string terug. Per lijn die eindigt met een newline (onder Windows \r
) zal een nieuwe string aan de array toegevoegd worden.
Deze methode probeert automatisch de juiste encodering van het bestand te detecteren. Dit zal niet voor elke encodering werken. Als je vreemde karakters ziet, zal je moeten uitzoeken wat de encodering is en uitdrukkelijk aangeven dat je deze wenst te gebruiken.
Dankzij ReadAllLines
en Split
hebben we nu alle bouwstenen om eenvoudig een csv-bestand te verwerken.
Stel je voor dat een bestand soccer.csv
volgende tekst bevat:
Volgende code zal dit bestand uitlezen en de individuele data op het scherm tonen:
Je kan tekst uit een bestand lezen, maar uiteraard kan je ook naar een bestand wegschrijven. De 2 eenvoudigste manieren zijn:
File.WriteAllText
: deze gebruik je als je 1 enkele string wilt wegschrijven. Typisch bevat deze tekst newline karakters.
File.WriteAllLines
: deze is de omgekeerde van ReadAllLines()
en zal een array van strings wegschrijven. Elke string in de array wordt geschreven als een regel, dus het is alsof je de strings eerst verbindt met een newline via String.Join
en dan wegschrijft met File.WriteAllText
.
Een voorbeeld:
Opgelet met het schrijven naar bestanden: dit zal onherroepelijk het target bestand overschrijven. .Gebruik if(File.Exists(pathtofile))
om te controleren of een bestand bestaat of niet. Eventueel kan je dan aan de gebruiker bevestiging vragen of je deze effectief wilt overschrijven.
Access modifiers bepalen welke code door welke andere code mag worden uitgevoerd of aangepast. We hebben al een aantal dingen public
gemaakt bij wijze van demonstratie. Dit is handig om voorbeeldjes te tonen, maar in code van hoge kwaliteit, denk je grondig na voor je dit doet.
De access modifier geeft aan hoe zichtbaar een bepaald deel van de klasse is voor code buiten de klasse zelf. Wanneer je niet wilt dat "van buitenuit" (bv in Main
, terwijl je een andere klasse dan Program
schrijft) een bepaalde methode kan aangeroepen worden, dan dien je deze als private
in te stellen. Wil je dit net wel dat moet je er expliciet public
voor zetten.
Test in de voorgaande klasse Auto
eens wat gebeurt wanneer je public
verwijdert voor een van de methodes. Inderdaad, je krijgt een foutmelding. Lees deze. Ze zegt dat de methode die je wil oproepen wel bestaat, maar niet gebruikt mag worden. Dat komt omdat je testcode in de klasse Program
staat en je methode deel uitmaakt van een andere klasse (meerbepaald Auto
).
Als je duidelijk wil maken dat bepaalde code niet van buitenaf aangeroepen kan worden, schrijf dan private
in plaats van public
. Er zijn nog tussenliggende niveaus waar we later op ingaan en als je geen van beide modifiers schrijft, kan het zijn dat je code op zo'n tussenliggend niveau terecht komt. Als beginnende programmeur maak je er best een gewoonte van duidelijk te kiezen voor public
of private
.
Waarom zou je bepaalde zaken private
maken? Het antwoord is opnieuw encapsulatie.
Neem als voorbeeld de kilometerstand. Het is wettelijk verboden een kilometerstand zomaar aan te passen. Hij mag alleen veranderen doordat er met een auto gereden wordt. Dan is het logisch dat dit ook alleen maar op deze manier kan voor onze auto-objecten. We kunnen dit realiseren door kilometers
van public
naar private
te wijzigen. Dan kunnen we de kilometerstand nog wijzigen, maar enkel door de (publieke) methode Rijden
te gebruiken. Als we hem dan nog willen kunnen bekijken (maar niet rechtstreeks wijzigen), kunnen we een extra (publieke!) methode ToonKilometerstand
voorzien.
Volgens de conventie maken we dan van de grote "K" in kilometers
een kleine "k", omdat publieke members met Pascal case genoteerd worden en private members met camel case.
De richtlijnen rond naamgeving van Microsoft met betrekking tot attributen, methoden,... vind je terug.
We kunnen ook methoden private
maken. Dit gebeurt niet zo vaak als bij attributen, maar het kan wel. Dit doen we bijvoorbeeld als het gaat om een hulpmethode die binnen de klasse nuttig is, maar buiten de klasse fout gebruikt zou kunnen worden. Een typisch voorbeeld: een stukje code dat letterlijk herhaald wordt in twee of meer verschillende publieke methoden. In plaats van deze code te kopiëren, zonderen we ze af in een hulpmethode. Zo hoeven we eventuele bugfixes,... geen twee keer te doen.
Een studente vroeg in een van de afgelopen jaren: "Kunnen we niet gewoon afspreken dat we van sommige zaken afblijven?" In principe wel. Python doet het min of meer zo. Langs de andere kant: als wij meedelen dat de examenvragen op een publieke website staan en dat je er niet naartoe mag surfen, zou niemand dat dan doen? Private velden aanpassen kan soms een goed idee lijken op korte termijn, maar een project saboteren op langere termijn.
Cursussen
Het is moeilijk afspraken te maken over hoe Cursussen
gebruikt mag worden. Iemand zou zich altijd kunnen vergissen en een cursus invullen op een positie die niet vrij is, of op een andere vrije positie dan de eerste. Daarom leggen we de regels rond dit attribuut vast in de klasse. We doen dit door over te schakelen op volgende structuur:
In C# schrijven we private members in camel case. Daarom wordt het attribuut hernoemd.
Hierbij gaat RegistreerVoorCursus
zelf op zoek naar de eerste vrije positie. Als zo'n positie gevonden wordt, wordt de student ingeschreven voor de cursus.
Push je vooruitgang naar Gitlab!
Instantiemethoden, ook objectmethoden genoemd, weerspiegelen staan toe om functionaliteit toe te voegen aan objecten van een bepaalde klasse. Soms wordt ook gezegd dat ze "gedrag" van de objecten voorzien. Ze verschillen van statische methoden omdat ze niet alleen gebruik kunnen maken van statische onderdelen van klassen, maar ook van het object waar ze zelf bij horen.
Methoden behoren tot een algemenere categorie onderdelen van objecten genaamd members.
We gaan verder met de klasse Auto
. We willen bijvoorbeeld een applicatie voor de opvolging van deelauto's (Cambio, Poppy, etc.) schrijven. Er zijn verschillende soorten functionaliteit die je kan koppelen aan één auto:
voltanken
rijden
op onderhoud gaan
verkoopsprijs bepalen
Is het de auto die deze zaken doet, of is het een persoon? In werkelijkheid is het natuurlijk dat laatste. Maar de functionaliteit is wel veel sterker gelinkt aan auto's dan aan personen en misschien interesseert de persoon die de handeling uitvoert ons niet eens.
Je doet dit met objectmethoden. Deze lijken erg op static
methoden, maar ze hebben toegang tot het object waarop ze zijn toegepast.
Een simpele implementatie van dit gedrag zie je hier:
Bovenstaande code is kort om didactische redenen. Er wordt niet gecontroleerd dat je benzinepeil altijd minstens 0l is, er wordt verondersteld dat de capaciteit van je tank 50l is,...
Voor de duidelijkheid kan je het woordje this
toevoegen om het huidige object expliciet voor te stellen. Het wordt sterk aangeraden dat je dit doet. Je code wordt er beter leesbaar door.
Om een objectmethode te gebruiken, hebben we een object nodig. We schrijven dan de naam van het object, gevolgd door een punt en een methodeoproep.
Het gedrag van een object kan afhangen van de waarde van de instantievariabelen. Zo zal de verkoopswaarde van auto1
iets lager liggen dan die van auto2
. Dat komt omdat this.Kilometers
deel uitmaakt van de berekening van de verkoopsprijs. Ook dit valt onder het principe van encapsulatie: er vindt een berekening plaats "onder de motorkap". We hoeven niet te weten hoe de prijs berekend wordt, elk object weet van zichzelf hoe het de prijs berekent.
static
methodesEen statische methode is een methode die wel bij de klasse hoort, maar niet te maken heeft met een specifiek object van die klasse. We gebruiken terug de euronorm als voorbeeld:
We breiden onze klasse Student
uit zodat ze overeenstemt met volgend diagram:
Het naamkaartje is een stuk tekst, bestaande uit de naam van de student, gevolgd door (STUDENT)
.
Dit is 10u per week per cursus.
Push je vooruitgang naar Gitlab!
Met klassen kan je dus complexe concepten zoals een lening modelleren in code. Soms hoeft het niet zo complex, maar is het toch nuttig om wat meer structuur te voorzien dan met heel brede datatypes zoals int en string. Met een string kan je bijvoorbeeld om het even welke tekst voorstellen, dus oneindig veel mogelijkheden. Met een byte kan je een getal tussen 0 en 255 voorstellen. Met de andere getaltypes kan je een getal in een ander bereik voorstellen. Soms matchen de waarden die je kan voorstellen niet met de mogelijkheden in het probleemdomein.
Als je de mogelijkheden in het probleemdomein op voorhand kan vastleggen, is het vaak handig gebruik te maken van een enumeratie (enum). Een enumeratie is een opsomming van alle mogelijke waarden voor een variabele van een bepaald type.
Stel dat je een programma moet schrijven dat afhankelijk van de dag van de week iets anders moet doen. Zonder enums zou je dit kunnen schrijven op 2 zeer foutgevoelige manieren:
Met een int
die een getal van 1 tot en met 7 kan bevatten
Met een string
die de naam van de dag bevat
Beide hebben tekortkomingen, omdat ze niet goed genoeg overeenstemmen met de mogelijkheden in de werkelijkheid.
De waarde van de dag staat in een variabele int dagKeuze
. We bewaren er 1 in voor Maandag, 2 voor dinsdag, enzovoort.
Deze oplossing heeft 2 grote nadelen:
Wat als we per ongeluk dagKeuze
een niet geldige waarde geven, zoals 9, 2000, -4, etc. ? We kunnen daar wel conditionele code voor voorzien, maar de compiler zal ons niet waarschuwen als we dat vergeten!
De code is niet erg leesbaar. dagKeuze==2
? Was dat nu maandag, dinsdag of woensdag (want misschien beginnen we te tellen vanaf zondag, of misschien beginnen we te tellen vanaf 0). Je kan wel afspraken maken binnen je team, maar het is altijd mogelijk een afspraak te vergeten.
De waarde van de dag bewaren we nu in een variabele string dagKeuze
. We bewaren de dagen als "maandag"
, "dinsdag"
, etc.
De code wordt nu wel leesbaarder dan met 1, maar toch is ook hier 1 groot nadeel:
De code is veel foutgevoeliger voor typfouten. Wanneer je "Maandag"
i.p.v. "maandag"
bewaart dan zal de if
al niet werken. Iedere schrijffout of variant zal falen.
Het keyword enum
geeft aan dat we een nieuw datatype maken. Gelijkaardig aan wat we voor een klasse doen, dus. Wanneer we dit nieuwe type hebben gedefinieerd kunnen we dan ook variabelen van dit nieuwe type aanmaken (dat is waarom we spreken van een "datatype"). Anders dan bij een klasse beperken we tot alleen de opgesomde opties.
In C# zitten al veel enum
-types ingebouwd. Denk maar aan ConsoleColor
: wanneer je de kleur van het lettertype van de console wilt veranderen gebruiken we een enum-type. Er werd reeds gedefinieerd wat de toegelaten waarden zijn, bijvoorbeeld: Console.ForegroundColor = ConsoleColor.Red;
Achter de schermen worden waarden van een enum type nog steeds voorgesteld als getallen. Maar je gebruikt deze getallen niet rechtstreeks en dat verkleint de risico's.
Zelf een enum
type gebruiken gebeurt in 2 stappen:
Het type en de mogelijke waarden definiëren
Variabele(n) van het nieuwe type aanmaken en gebruiken in je code
Stap 1: het type definiëren
We maken eerst een enum type aan. Wij zullen dit doen in een aparte file met dezelfde naam als het nieuwe datatype. Bijvoorbeeld:
Vanaf nu kan je variabelen van het type Weekdagen
aanmaken. Merk op dat er geen puntkomma voorkomt. De syntax is ongeveer dezelfde als die voor de definitie van een klasse.
Stap 2: variabelen van het type aanmaken en gebruiken.
We kunnen nu variabelen van het type Weekdagen
aanmaken. Bijvoorbeeld:
En vervolgens kunnen we waarden aan deze variabelen toewijzen als volgt:
Kortom: we hebben variabelen zoals we gewoon zijn, het enige verschil is dat we nu beperkt zijn in de waarden die we kunnen toewijzen. Deze kunnen enkel de waarden zijn die in het type gedefinieerd werden. De code is nu ook een pak leesbaarder geworden.
Eens je met enums aan het werken bent in je programma, kan je gerust zijn dat deze een geldige waarde bevatten. Er is alleen het probleem dat je soms een van de mogelijkheden moet krijgen van de gebruiker. Een waarde van een enum type rechtstreeks intypen gaat niet. Net zoals wanneer je een getal inleest, is er een omzetting nodig. Het verschil is dat we hier een dubbele omzetting doen: van tekst naar getal en dan van getal naar enum waarde.
Je kan dit als volgt doen:
Dit werkt en je hoeft de achterliggende getallen voor de waarden niet te kennen. Er zijn meer geavanceerde mogelijkheden, maar dit volstaat op dit punt.
Je kan objecten aanmaken door een omschrijving ervan in tekst om te zetten naar een constructoroproep. We noemen dit proces "deserialisatie". Een object omzetten naar een omschrijving noemen we "serialisatie". Deze omschrijving kan verschillende conventies volgen: XML, JSON, YAML, CSV,...
We hebben geleerd hoe we CSV-bestanden moesten uitlezen. Fris dit zo nodig op, want we gaan nu een stap verder.
CSV wordt vaak gebruikt om objecten tussen programma's te verplaatsen. Een object inlezen vanaf een CSV bestand is een vorm van deserializeren.
Je kan tekst uit een bestand lezen, maar uiteraard kan je ook naar een bestand wegschrijven. De 2 eenvoudigste manieren zijn:
File.WriteAllText
: deze gebruik je als je 1 enkele string wilt wegschrijven
File.WriteAllLines
: deze is de omgekeerde van ReadAllLines()
en zal een array van strings wegschrijven.
Een voorbeeld:
Opgelet met het schrijven naar bestanden: dit zal onherroepelijk het target bestand overschrijven. Gebruik if(File.Exists(pathtofile))
om te controleren of een bestand bestaat of niet. Eventueel kan je dan aan de gebruiker bevestiging vragen of je deze effectief wilt overschrijven.
Wil je CSV-bestand maken dan zal je eerst met String.Join
of met stringinterpolatie een komma-separated lijst maken, bijvoorbeeld:
Properties zijn een feature van C♯ om de leesbaarheid van code te verhogen. Ze zien er uit zoals attributen, maar werken zoals methoden.
Properties behoren tot een algemenere categorie onderdelen van objecten genaamd members.
. De camerabeelden zijn wat wazig, maar de schermopname is in orde.
In dit hoofdstuk bespreken we eerst waarom properties nuttig zijn. Vervolgens bespreken we de 2 soorten properties die er bestaan:
Full properties
Auto properties
Stel dat we volgende klasse hebben:
Stel nu dat we het benzinepeil van een auto als volgt proberen aanpassen:
Misschien is de eerdere methode TankVol()
te beperkt en willen we wel een willekeurige hoeveelheid benzine kunnen toevoegen of verwijderen, zo lang we niet minder dan 0l of meer dan 50l in de tank doen.
Een eerste mogelijkheid is om hier methodes voor te schrijven:
Dit gaat. De methodes zijn public, dus we kunnen ze overal oproepen. Bovendien verhindert de SetBenzine
-methode ongeldige waarden. Het nadeel is dat de syntax om deze methodes te gebruiken zwaar is. Met publieke attributen konden we dit doen:
Met de zogenaamde getter en setter moeten we dit doen:
Het lijkt niet zo veel, maar code stapelt zich op doorheen de tijd. Properties lossen dit probleem op. Ze zorgen ervoor dat we kunnen "doen alsof" we publieke velden hebben, maar dezelfde hoeveelheid controle kunnen uitoefenen als met getters en setters.
Een full property ziet er als volgt uit:
Dankzij deze code kunnen we nu elders dit doen:
Vergelijk dit met de vorige alinea waar we dit met Get en Set methoden moesten doen. Deze property syntax is veel eenvoudiger in het gebruik.
We zullen de property nu stuk per stuk analyseren:
public double Benzine
: Merk op dat we Benzine
met een hoofdletter schrijven. Vaak wordt gekozen voor dezelfde naam als de variabele die we afschermen (in dit geval benzine
met een kleine "b"), maar dan in Pascal case. Dat is niet strikt noodzakelijk. Je zou ook Naft
kunnen schrijven. public
en double
staan er om dezelfde reden als in de oplossing met methodes GetBenzine()
en SetBenzine()
: we willen deze property buiten de klasse kunnen gebruiken en als we hem opvragen, krijgen we een double
.
{ }: Vervolgens volgen 2 accolades waarbinnen we de werking van de property beschrijven.
get {}
: indien je wenst dat de property data naar buiten moet sturen, dan schrijven we de get
-code. Binnen de accolades van de get
schrijven we wat er naar buiten moet gestuurd worden. In dit geval return benzine
maar dit mag even goed bijvoorbeeld return 4
of een hele reeks berekeningen zijn. Eigenlijk werkt dit net als de body van een methode en kan je hierin doen wat je in een methode kan doen.
We kunnen nu van buitenaf toch de waarde van benzine
onrechtstreeks uitlezen via de property en het get
-gedeelte: Console.WriteLine(auto.Benzine);
set {}
: in het set
-gedeelte schrijven we de code die we moeten hanteren indien men van buitenuit een waarde aan de property wenst te geven om zo een instantievariabele aan te passen. De waarde die we van buitenuit krijgen (eigenlijk is dit een parameter van een methode) zal altijd in een lokale variabele value
worden bewaard. Deze zal van het type van de property zijn. In dit geval dus double
, want het type bij Benzine
is double
. Vervolgens kunnen we value
toewijzen aan de interne variabele indien gewenst: benzine=value
.
Let goed op dat je in je setter schrijft benzine = value
en niet Benzine = value
. Dat eerste past de verborgen instantievariabele aan. Dat tweede roept de setter opnieuw op. En opnieuw. En opnieuw. Probeer gerust eens een breakpoint te plaatsen voor de toekenning en dan de debugger te starten als je niet ziet waarom dit een probleem is.
Visual Studio heeft een ingebouwde shortcut om snel een full property, inclusief een bijhorende private dataveld, te schrijven. Typ propfull
gevolgd door twee tabs!
De full property Benzine
heeft nog steeds het probleem dat we negatieve waarden kunnen toewijzen (via de set
) die dan vervolgens zal toegewezen worden aan benzine
.
We kunnen in de set
code extra controles inbouwen. Als volgt:
Deze code zal het benzinepeil enkel aanpassen als het geldig is en anders stilletjes niets doen. Wat je vaak tegenkomt is throw new ArgumentException($"{value} is geen geldig benzinepeil")
. Dit doet je programma crashen, maar legt ook uit waarom. We kunnen de code binnen set
(en get
) zo complex maken als we zelf willen.
Je kan dus extra controles toevoegen, maar deze hebben alleen zin als je de variabele via de property aanpast. Als je in een methode van de klasse auto benzine
met kleine "b" aanpast en niet voorzichtig bent, kan je nog steeds een negatief peil instellen. Daarom wordt aangeraden ook binnen de klasse gebruik te maken van de property, dus zo veel mogelijk Benzine
in plaats van benzine
te gebruiken.
We zijn niet verplicht om zowel de get
en de set
code van een property te schrijven.
Write-only property
We kunnen dus enkel benzine
een waarde geven, maar niet van buitenuit uitlezen.
Read-only property met private set
Soms gebeurt het dat we van buitenuit enkel de gebruiker de property read-only willen maken. We willen echter intern (in de klasse zelf) nog steeds controleren dat er geen illegale waarden aan private datafields worden gegeven. Op dat moment definieren we een read-only property met een private setter:
Van buitenuit zal enkel code werken die deget
-van deze property aanroept: Console.WriteLine(auto.Benzine);
. Code die de set
van buitenuit nodig heeft zal een fout geven zoals: auto.Benzine=40
.
Read-only Get-omvormers
Veel properties bieden toegang tot een achterliggende variabele van hetzelfde type, maar dat is niet verplicht. Je kan ook iets berekenen en dat teruggeven via een getter.
Als we verder gaan met de klasse Auto
:
Je hebt iets gelijkaardigs gezien bij DateTime
. Daar kan je allerlei stukjes informatie uit opvragen, maar eigenlijk wordt er maar één ding bijgehouden: het aantal "ticks". Dat zijn fracties van seconden sinds een bepaalde startdatum. Als je het uur of het aantal minuten of de maand of de weekdag of nog iets anders opvraagt via een DateTime
, wordt deze ter plekke berekend uit het aantal ticks. Zo moet maar één stukje informatie worden bijgehouden, wat leidt tot minder fouten.
Automatische eigenschappen (autoproperties) in C# staan toe om eigenschappen (properties) die enkel een waarde uit een private variabele lezen en schrijven verkort voor te stellen. Ze zorgen dat je al snel properties hebt, maar staan je niet toe complexe berekeningen te doen of waarden te controleren zoals full properties dat wel doen.
Voor Auto
kan je bijvoorbeeld schrijven:
Dit maakt achter de schermen een privé-attribuut aan zoals benzine
met kleine "b", maar met een een verborgen naam. We kunnen dus niet rechtstreeks aan dat attribuut.
Wij zullen geen gebruik maken van autoproperties, omdat het verschil met publieke attributen pas in meer geavanceerde scenario's zichtbaar wordt. We geven ze hier mee zodat je ze kan herkennen als je ze tegenkomt.
Het .NET gegevenstype DateTime
is de ideale manier om te leren werken met objecten. Het is een nuttig en toegankelijk gegevenstype. Je kan er je iets bij voorstellen, maar het is ook een beetje abstract.
Waarom spreken we hier over "gegevenstype" en niet over "klasse"? Omdat klassen in .NET reference types zijn. DateTime
is echter een value type, dus technisch gezien is het een "struct" en geen "klasse". Wij zullen zelf geen structs schrijven, maar het verschil met klassen is uiterst klein in C#. Instanties van zowel klassen als structs zijn objecten.
Zegt het verschil tussen value types en reference types je niets meer? Kijk dan terug naar .
Er zijn 2 manieren om DateTime
objecten aan te maken:
Door aan de klasse de huidige datum en tijd te vragen via DateTime.Now
Door manueel de datum en tijd in te stellen via de constructor
Volgend voorbeeld toont hoe we een object kunnen maken dat de huidige datum tijd van het systeem bevat. Vervolgens printen we dit op het scherm:
DateTime
op het begin van regel 1 is het type van de variabele. Dit type drukt uit met wat voor data we te maken hebben. Dit is net dezelfde redenering als bij het gebruik van string
in string mijnTekst = "hello world";
Enkele voorbeelden:
Je hebt eerder al met constructoren gewerkt: herinner je new Random()
. Hiermee maakte je eigenlijk een object aan dat willekeurige getallen kon genereren.
Ieder DateTime
object dat je aanmaakt heeft een hoop nuttige methoden.
Deze methoden kan je gebruiken om een bepaalde aantal dagen, uren, minuten en zo voort aan je huidige object toe te voegen. Al deze methoden geven steeds een nieuw DateTime object terug dat je moet bewaren wil je er iets mee doen:
AddDays
AddHours
AddMilliseconds
AddMinutes
AddMonths
AddSeconds
AddTicks
AddYears
Een voorbeeld:
(voorgaande kan ook in 1 lijn: DateTime nextWeek= DateTime.Now.AddDays(7)
)
Uiteraard mag je ook een bestaand object overschrijven met het resultaat van deze methoden:
Properties zijn een zeer uniek aspect van C#. Ze geven toegang tot de data van een object, maar op een gecontroleerde manier. We zullen deze nog tot in den treure leren maken.
Enkele nuttige properties van DateTime
zijn:
Date
Day
DayOfWeek
DayOfYear
Hour
Millisecond
Minute
Month
Second
Ticks
TimeOfDay
Today
UtcNow
Year
Alle properties van DateTime zijn read-only. Je kan een bestaande datum dus niet aanpassen (maar je kan wel een nieuwe datum baseren op een bestaande datum).
Een voorbeeld:
Uiteraard mag je ook deze properties gebruiken om direct naar het scherm te schrijven:
Je hebt een invloed op hoe DateTime
objecten naar string
worden opgezet. Omzetten naar een string is functionaliteit van het object. Je doet dit dus via een objectmethode. Deze gebruik je zoals de statische methoden van eerder, maar je roept ze op voor een specifiek object.
Je kan bepalen hoe de omzetting naar string
moet gebeuren door extra formatter syntax mee te geven. Dit zie je in volgende voorbeeld:
De manier waarop DateTime
objecten worden getoond (via ToString
) is afhankelijk van de landinstellingen van je systeem. Soms wil je dit echter op een andere manier tonen. Je doet dit door mee te geven volgens welke culture de tijd en datum getoond moet worden.
Dit vereist dat je eerst een CultureInfo
object aanmaakt en dat je dan meegeeft:
CultureInfo
is een klasse waar je je misschien al iets minder bij kan voorstellen dan DateTime
. Een CultureInfo
stelt een aantal afspraken voor een bepaalde cultuur voor. De data is de combinatie van taal en regio. De functionaliteit bestaat er dan in om zaken zoals de munt, het gebruikte formaat voor datums,... in die taal en die regio te produceren. Ook hier geldt dus het black box principe: je kan code schrijven die bijvoorbeeld het juiste datumformaat voor Rusland gebruikt wanneer je zelf niet weet wat dat formaat is.
Sommige methoden zijn static
dat wil zeggen dat je ze enkel rechtstreeks op de klasse kunt aanroepen. Vaak zijn deze methoden hulpmethoden waar de individuele objecten niets aan hebben.
Parsen laat toe dat je strings omzet naar DateTime
. Dit is handig als je bijvoorbeeld de gebruiker via ReadLine
tijd en datum wilt laten invoeren:
Zoals je ziet roepen we Parse
aan op DateTime
en dus niet op een specifiek object. Dit is logisch omdat je geen bestaande datum nodig hebt om een nieuwe datum te parsen.
Deze nuttige methode geeft een bool
terug om aan te geven het meegegeven object eens schrikkeljaar is of niet:
Dit is logisch omdat je geen volledige bestaande datum nodig hebt. Je wil gewoon iets zeggen over een jaartal, terwijl een DateTime
ook een dag, maand, uur,... heeft. Dus dit heeft niet veel te maken met een specifieke DateTime
, maar heeft duidelijk wel met de klasse te maken.
Af en toe is het een kwestie van smaak van de auteurs of een methode statisch is of niet. Maar meestal is er een duidelijke "beste" keuze.
Je kan DateTime objecten ook bij mekaar optellen en aftrekken. Deze bewerking geeft echter geen DateTime
object terug, maar een TimeSpan
object. Dit is een object dat dus aangeeft hoe groot het verschil is tussen de 2 DateTime
objecten:
Maak een applicatie die bestaat uit een oneindige loop. De loop zal iedere seconde pauzeren: System.Threading.Thread.Sleep(1000);
. Vervolgens wordt het scherm leeg gemaakt en wordt de huidige tijd getoond. Merk op dat ENKEL de tijd wordt getoond, niet de datum.
Maak een applicatie die aan de gebruiker vraagt op welke dag hij jarig is. Toon vervolgens over hoeveel dagen z'n verjaardag dan zal zijn.
Deze oefeningen maak je als statische methoden van een klasse NumeriekeData
. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.
Werken met machten
Schrijf een programma om, gegeven de lengte van een zijde, de oppervlakte van een vierkant met die zijde en het volume van een kubus met die zijde te berekenen. Je mag maximum vier keer gebruik maken van de variabele met daarin de lengte van de zijde, inclusief de initialisatie.
De oppervlakte bepaal je door de zijde met zichzelf te vermenigvuldigen. Bijvoorbeeld, een zijde van 10m betekent een oppervlakte van 10m * 10m = 100m². Het volume bepaal je door nog een keer met de zijde te vermenigvuldigen. Een zijde van 10m betekent dus een oppervlakte van 1000m³. Maak gebruik van een methode uit Math
.
Noem je methode LengteOppervlakteVolume
.
een taak herhaaldelijk uitvoeren met een lus
Maak een applicatie waarbij de gebruiker steen-schaar-papier met de computer kan spelen. De gebruiker kiest telkens steen, schaar of papier en drukt op enter. Vervolgens kiest de computer willekeurig steen, schaar of papier.
Vervolgens krijgt de winnaar 1 punt:
Steen wint van schaar, verliest van papier
Papier wint van steen, verliest van schaar
Schaar wint van papier, verliest van steen
Indien beide hetzelfde hebben wint niemand een punt.
De eerste (pc of gebruiker) die 10 punten haalt wint.
Noem je methode SchaarSteenPapier
.
Genereer een willekeurig getal tussen 1 en 3 om de computer te laten kiezen.
Teken een flowchart!
(Helemaal op het einde)
of
Maak voor deze oefening een aparte klasse TextCellMetRandom. Kopieer hierin de code van je klasse TextCell en pas daarna pas aan.
We willen onze spreadsheets gebruiken voor simulaties. Hiervoor hebben we willekeurige data nodig.
Zorg ervoor dat een cel in TextCell ook een formule van de vorm =rand()
kan bevatten. Dit initialiseert de cel op een willekeurige waarde tussen 1 en 10.
Maak voor deze oefening een aparte klasse TextCellMetKomma. Kopieer hierin de code van je klasse TextCell en pas daarna pas aan.
We willen complexere berekeningen doen in TextCell. Zorg ervoor dat kommagetallen ondersteund worden, maar wel steeds getoond worden met maximum twee cijfers na de komma.
Pas op de juiste plaatsen alle code die een int
veronderstelt aan naar een double
. Zorg ervoor dat, na het uitrekenen van alle formules, alle cellen klassiek afgerond worden tot 2 cijfers na de komma.
(met demonstratie in SchoolAdmin, zelf mee te maken!)
Normaal gezien kom je null
tegen wanneer je een variabele van een reference type hebt zonder verwijzing naar data. Het gevolg is dat null
vaak betekent dat er een waarde zou kunnen staan, maar in de huidige situatie geen geldige waarde is.
Hoewel value types niet werken met verwijzingen, zou dezelfde interpretatie ook bij value types zinvol kunnen zijn: soms heb je gewoonweg geen geldige waarde. Daarom kent C# ook nullable value types. Dit zijn speciale value types die ook de waarde null
kunnen aannemen (ook al kom je die laatste anders vooral tegen bij reference types).
Je noteert een nullable value type als een gewoon value type, gevolgd door een vraagteken. Indien je in code bijvoorbeeld een getalwaarde wil voorstellen als een variabele int mijnVariabele
, maar de mogelijkheid bestaat dat er geen waarde is voor mijnVariabele
, declareer je als volgt: int? mijnVariabele
. Dit betekent: "mijnVariabele
is een getal, maar kan ontbreken."
Dit heeft gevolgen. Je kan code die een value type verwacht niet zomaar gebruiken met een nullable versie van hetzelfde type. Anders gezegd: je mag mijnVariabele
niet meegeven aan een methode die een gewone int
verwacht. Je moet ofwel deze methode aanpassen zodat ze een int?
verwacht, ofwel moet je mijnVariabele
casten voor je hem meegeeft als argument. Let op: dit werkt alleen als mijnVariabele
niet null
is!
null
Voor de nullable versies van de value types die je al kent, kan je gekende operaties (zoals +
, -
, ... voor getallen) blijven gebruiken, maar je moet opletten. Een berekening met null
in levert je sowieso null
op als resultaat. Een vergelijking (via <=
, <
, >
, >=
) met null
levert je sowieso false
op als resultaat.
Zoals nu duidelijk is bevatten variabelen van een reference type steeds een referentie naar een object. Maar wat als we dit schrijven:
Dit zal een fout geven. stud1
bevat namelijk nog geen referentie. Maar wat dan wel?
Deze variabele bevat de waarde null
. Dit is de defaultwaarde voor reference types. Met andere woorden, als je een reference type declareert en niet initialiseert, zal de waarde null
zijn.
Een veel voorkomende foutboodschap tijdens de uitvoer van je applicatie is de zogenaamde NullReferenceException
. Deze zal optreden wanneer je code een member (attribuut, methode of property) van null
probeert op te vragen.
Laten we dit eens simuleren:
Dit zal resulteren in volgende foutboodschap:
We moeten in dit voorbeeld expliciet
= null
plaatsen daar Visual Studio slim genoeg is om je te waarschuwen voor eenvoudige potentiële NullReference fouten en je code anders niet zal compileren.
Je kan NullReferenceException
voorkomen door na te gaan dat een object verschillend is van null vooraleer je een van de members van dit object probeert te gebruiken. Bijvoorbeeld, met een klasse Auto
:
Deze code zal niet crashen. Als je de WriteLine
uitvoert zonder if, zal het programma wel crashen met een NullReferenceException
.
Waarom is null
en niet == null
? Die vraag leidt ons te ver. Meestal zal == null
ook werken, maar ==
kan aangepast worden om anders te werken dan gewoonlijk. is
is dus betrouwbaarder.
Wanneer je geen indexering nodig hebt, maar toch snel over alle elementen in een array wenst te gaan, dan is het foreach statement een zeer nuttig is. Een foreach loop zal ieder element in de array een voor een in een tijdelijke variabele plaatsen (de iteration variable). Volgende code toont de werking waarbij we een array van string
s hebben en alle elementen er in op het scherm willen tonen:
De eerste keer dat we in de loop gaan zal het element boodschappen[0]
aan boodschap
toegewezen worden voor gebruik in de loop-body, vervolgens wordt boodschappen[1]
toegewezen, enz.
Het voordeel is dat je dus geen teller/index nodig hebt en dat foreach zelf de lengte van de array zal bepalen.
De foreach iteration variable is read-only: je kan dus geen waarden in de array aanpassen, enkel uitlezen.
De foreach gebruik je enkel als je alle elementen van een array wenst te benaderen. In alle andere gevallen zal je een ander soort loop (for, while, etc.) moeten gebruiken.
C# heeft een var
keyword. Je mag dit keyword gebruiken ter vervanging van het type (bv int) op voorwaarde dat de compiler kan achterhalen wat het type moet zijn.
Opgelet: het var
keyword is in deze cursus nooit nodig. Het vergemakkelijkt het schrijfwerk, want het wordt door de compiler vertaald in een specifiek type. Er zijn scenario's waarin het wel nodig is, maar die zijn meer geavanceerd ("anonieme types").
Het betekent niet hetzelfde als de var
van JavaScript. In JavaScript hoef je namelijk geen type vast te leggen voor variabelen en kan je dit doen:
In C# levert dit een compilatiefout. Met de eerste regel zeg je dat de compiler uit de rechterzijde mag afleiden dat var
hier vervangen kan worden door string
. Je kan geen waarde 3
in een variabele van type string
plaatsen, dus dit levert een compilatiefout.
Objecten die je aanmaakt komen niet zomaar tot leven. Nieuwe objecten maken we aan met behulp van de new
operator zoals we al gezien hebben:
De new
operator doet 2 dingen:
Hij reserveert ruimte voor dit object in het geheugen
Hij zorgt ervoor dat initialisatiecode uitvoert om het object klaar te maken voor gebruik
Via de constructor van een klasse kunnen we code meegeven die moet uitgevoerd worden telkens een nieuw object van dit type wordt aangemaakt.
Constructors lijken erg op methodes. We zullen vaak zeggen dat het speciale methodes zijn, al bestaat daar wat technische discussie over. De constructor wordt in elk geval aangeroepen op een gelijkaardige manier aan een gewone methode. Daarom zetten we ronde haakjes: new Auto()
.
Als programmeur van eigen klassen zijn er 3 opties voor je:
Je gebruikt geen (expliciete) constructors: het leven gaat voort zoals het is. Je kunt objecten aanmaken zoals eerder getoond. Achter de schermen gebruik je wel een zogenaamde default constructor.
Je hebt enkel een parameterloze constructor nodig. Je kan nog steeds objecten met new Auto()
aanmaken, maar je gaat zelf beschrijven wat er moet gebeuren bij de parameterloze constructor.
Je wenst gebruik te maken van een of meerdere constructoren met parameters. Hierbij zal je dan extra argumenten kunnen meegeven bij de creatie van een object, bijvoorbeeld: new Auto(25,25000)
. Dit kan bijvoorbeeld een auto maken met 25l benzine in de tank en 25000km op de teller. De betekenis van de getallen hangt af van hoe je de constructor schrijft.
Een lege default constructor voor je klasse krijg je standaard wanneer je een nieuwe klasse aanmaakt. Je ziet deze niet en kan deze niet aanpassen. Je kan echter daarom altijd objecten met new myClass()
aanmaken.
Van zodra je echter beslist om zelf een of meerdere constructors te schrijven zal C# zeggen "Oké, jij je zin, nu doe je alles zelf". De default constructor die je gratis kreeg zal ook niet meer bestaan en heb je die dus nodig dan zal je die dus zelf moeten schrijven!
De default constructor is een constructor die je zelf niet schrijft. Hij heeft nooit parameters.
Heeft (zoals alle constructoren) als naam de naam van de klasse zelf
Stel dat we een klasse Auto
hebben:
We schrijven nergens code voor de constructor. Dan nog kunnen we new Auto()
schrijven, maar dan wordt de auto aangemaakt met 5l in de tank en 0km (de defaultwaarde van int
) op de teller.
We willen telkens een Auto-object wordt aangemaakt dat dit een random aantal kilometers op de teller heeft. Via een parameterloze constructor kunnen we dat oplossen (je kan namelijk alleen expressies gebruiken als initiële waarde).
Eerst schrijven de parameterloze constructor, deze ziet er als volgt uit:
De constructor moet de naam van de klasse hebben, public zijn en geen returntype definiëren.
Vervolgens voegen we de code toe die we nodig hebben:
Telkens we nu een object zouden aanmaken met new Auto()
zal deze een willekeurige kilometerstand hebben. Je kan trouwens ook in de constructor een initiële waarde aan benzine
geven.
Zelfs als er een letterlijke initiële waarde wordt toegekend, gebeurt dit meestal in de constructor. Het is een kwestie van smaak, maar een constructor dient toch om te initialiseren.
Soms wil je argumenten aan een object meegeven bij creatie. We willen bijvoorbeeld de inhoud van de tank en de kilometerstand meegeven die het object moet hebben bij het aanmaken. Met andere woorden, stel dat we dit willen schrijven:
Als we dit met voorgaande klasse , die enkel een parameterloze (default) constructor heeft, uitvoeren, zal de code een fout geven. C# vindt geen constructor die twee ints als parameter heeft.
Dat was eenvoudig. Maar denk eraan: je hebt een eigen constructor geschreven en dus heeft C# gezegd "ok, je schrijft zelf constructor, trek je plan. Maar de parameterloze versie zal je ook zelf moeten schrijven!" Je kan nu enkel je objecten met new Auto(int benzine, int kilometers)
aanmaken. Schrijf je new Auto()
dan zal je een error krijgen. Wil je die constructor nog hebben, dan zal je die met de hand moeten schrijven, bijvoorbeeld:
Er is geen grens op het aantal constructoren dat je kan schrijven, als ze maar verschillende parameters hebben.
Tot recent maakten we onze objecten steeds aan met de default constructor. Pas daarna gaven we eventuele properties de juiste waarde. Dat houdt een risico in: er is een periode waarin onze objecten nog niet "af" zijn. In het slechtste geval vergeten we zelfs om de properties in te stellen en krijgen we objecten die misschien ongeldig zijn.
Constructoren helpen dit probleem te voorkomen. Als we één constructor hebben, bijvoorbeeld Auto(int benzine, int kilometers)
, moeten we die gebruiken. We kunnen dus niet vergeten bijvoorbeeld auto1.Benzine = 25
te schrijven, want we worden gedwongen meteen new Auto(25,20000)
te schrijven.
Samengevat: als er eigenschappen zijn die je meteen bij het aanmaken van een object wil instellen, maak er dan parameters van een constructor voor.
Als je meerdere overloaded constructoren hebt, hoef je niet in elke constructor alle code voor objectinitialisatie te schrijven. Het sleutelwoordje this
biedt ook de mogelijkheid eerst een andere constructor aan te roepen en eventueel andere operaties toe te voegen. Dit heet constructor chaining. In bovenstaand voorbeeld kan je ook dit schrijven:
werken met klassen en objecten
gebruik maken van properties om geldige waarden af te dwingen
gebruik van een constructor
Functioneel is dit programma hetzelfde als H10-figuren.
Voorzie je eerdere figuren (Rechthoek
en Driehoek
) van een constructor met twee parameters, waarvan de tweede telkens de hoogte voorstelt en de eerste de andere afmeting. Zorg dat deze constructor gebruik maakt van de properties, zodat je makkelijker objecten met de juiste afmetingen kan maken en toch dezelfde foutmeldingen krijgt als eerder.
Je zal een extra, parameterloze, constructor moeten toevoegen omdat DemonstreerFiguren
van eerder moet blijven werken. Schrijf een nieuwe methode DemonstreerFigurenMetConstructor
om te tonen dat je hetzelfde kan bereiken met de constructor met twee parameters. Deze moet ook opgeroepen kunnen worden van uit je keuzemenu.
Schrijf DemonstreerFigurenMetConstructor
zodanig dat je exact onderstaande interactie krijgt:
makkelijker objecten aanmaken
gebruik maken van properties om geldige waarden af te dwingen
Functioneel zal je niet veel verschil zien met eerder. Dit is zuiver een aanpassing die de kwaliteit van je code verhoogt.
Pas CursusResultaat
aan zodat het huidige attribuut Naam
privaat wordt (hernoem het dan ook naar naam
) en voeg een read-only property Naam
toe om deze informatie toch toegankelijk te houden.
Voor Resultaat
doe je een gelijkaardige aanpassing, maar de property is niet read-only. Hij kan ingesteld worden, maar enkel op een waarde tussen 0 en 20. Aanpassingen naar een andere waarde worden genegeerd.
Voeg ook een constructor toe met twee parameters. De eerste is voor de naam, de tweede voor het resultaat. Doe ten slotte nodige wijzigingen in andere onderdelen van je code om met deze nieuwe code te werken, zonder iets te veranderen aan het gedrag van je systeem.
informatie op klasseniveau bijhouden
meer toepassingen van de constructor
We wensen cursussen automatisch te nummeren (zoals in DigitAP ook elke cursus een nummer heeft).
Voorzie eerst de klasse Cursus
van een read-only property Id
van type int
. Pas ook de klasse Cursus
aan zodanig dat het volgende beschikbare nummer voor een cursus wordt bijgehouden in een variabele maxId
. De eerste cursus zal nummer 1 moeten krijgen. Zorg er ten slotte voor dat elke nieuwe cursus automatisch dit eerste beschikbare nummer krijgt en dat nummer stijgt voor elke cursus die wordt aangemaakt.
Neem dit nummer ook op in de methode ToonOverzicht
van cursus, zodanig dat het cursusnummer tussen haakjes verschijnt, naast de titel van de cursus.
properties en access control
meer toepassingen van de constructor
We willen het aantal studiepunten per cursus bijhouden. We willen niet dat het veel extra werk is om dit aantal in te geven en we weten dat de meeste cursussen 3 studiepunten tellen.
Voorzie eerst de klasse Cursus
van een property Studiepunten
van type byte
. Je hoeft hierbij geen speciale controles te doen en mag gewoon het algemene patroon volgen, maar maak de setter private
.
Pas de constructor aan, zodat hij ook het aantal studiepunten aanvaardt als (derde) parameter. Zorg er met behulp van chaining ook voor dat ook calls met een of twee argumenten geldig blijven, waarbij het aantal studiepunten van vanzelf wordt ingesteld op 3.
Zet ook het aantal studiepunten mee in het overzicht dat je krijgt via ToonOverzicht
. Zet tussen haakjes naast het nummer voor de cursus, gevolgd door "stp"
.
Pas ten slotte, in DemonstreerCursussen
, je code aan zodat het vak webtechnologie wordt aangemaakt met ruimte voor 5 studenten en 6 studiepunten telt, terwijl databanken ruimte krijgt voor 7 studenten en 5 studiepunten telt.
werken met strings
werken met arrays
Voor de administratie is het handig snel en efficiënt nieuwe studenten te kunnen registreren. Zorg ervoor dat een gebruiker één regel tekst kan intypen met alle gegevens over een student, zonder veel verdere interactie.
Schrijf een methode StudentUitTekstFormaat(string csvWaarde)
die een object van de klasse Student
teruggeeft. Deze methode mag veronderstellen dat csvWaarde
eerst de naam van de student bevat, gevolgd door een puntkomma, gevolgd door de geboortedag, puntkomma, geboortemaand, puntkomma, geboortejaar. Alle elementen van de geboortedatum worden voorgesteld als getallen, volgens de afspraken die je ook toepast om datums te noteren in het Belgische formaat. Het kan zijn dat er ook informatie is om de student meteen te registreren voor een of meerdere cursusresultaten. In dat geval staat er na het geboortejaar nog een puntkomma, dan de naam van de cursus, dan het behaalde cijfer. Per cursus herhaalt deze groep van twee elementen zich.
Schrijf daarna een methode DemonstreerStudentUitTekstFormaat()
. Deze vraagt om de tekstvoorstelling van één student in te typen, maakt de student aan en toont dan het overzicht voor deze student. Neem deze methode ook op als optie in je keuzemenu voor SchoolAdmin
.
Deze methode vereist geen bestaande Student
. Ze heeft wel te maken met de klasse Student
.
De student hoeft niet opgenomen te worden in de array Studenten
van een Cursus
-object. We verbeteren dit later nog.
Naast de generieke List
collectie, zijn er nog enkele andere nuttige generieke 'collectie-klassen' die je geregeld in je projecten kan gebruiken.
In een dictionary wordt ieder element voorgesteld door een sleutel (key) en de waarde (value) van het element. Het idee is dat je de sleutel kan gebruiken om de waarde snel op te zoeken. De sleutel moet dan ook uniek zijn. Dictionaries stellen geen reeks met een volgorde voor, maar geven je de mogelijkheid data met elkaar in verband te brengen.
Enkele voorbeeldjes die het idee achter Dictionary kunnen verduidelijken:
een papieren telefoonboek is als een Dictionary met gecombineerde namen en adressen als keys en telefoonnummers als values
een echt woordenboek is als een Dictionary met woorden als keys en omschrijvingen als values
een array is als een Dictionary met getallen als keys en waarden van het type van de array als values
Bij de declaratie van de Dictionary<K,V>
dien je op te geven wat het datatype van de key zal zijn , alsook het type van de waarde (value). Met andere woorden, K
en V
komen niet letterlijk voor, maar je vervangt ze door types die je al kent.
Voor bovenstaande voorbeelden:
een echt woordenboek stel je best voor met string
als type van de key en string
als type van de value, want een woord stel je voor als string en een omschrijving ook
een array van double
kan je nabootsen door uint
te gebruiken als type van de key en double
als type van de value
het telefoonboek moeten we wat vereenvoudigen, maar als we op basis van naam meteen een telefoonnummer konden opzoeken (zonder adresgegevens,...), dan zou string
(de naam) het type van de key zijn en string
(telefoonnummer) het type van de value. Het telefoonnummer is geen getal omwille van zaken die je niet met een getaltype kan voorstellen zoals +32 of 0473.
In het volgende voorbeeld maken we een Dictionary
van klanten aan. Iedere klant heeft een unieke ID (de key, die we als int
gebruiken) alsook een naam (die niet noodzakelijk uniek is en de waarde voorstelt):
Bij de declaratie van customers
plaatsen we dus tussen de < >
twee datatypes: het eerste duidt het datatype van de key aan, het tweede dat van de values.
Merk op dat je niet verplicht bent om een int
als type van de key (of value) te gebruiken, dit mag eender wat zijn, zelfs een klasse.
We kunnen nu met behulp van bijvoorbeeld een foreach
-loop alle elementen tonen. Hier kunnen we de key met de .Key
-property uitlezen en het achterliggende object of waarde met .Value
. Value
en Key
hebben daarbij ieder het type dat we hebben gedefinieerd toen we het Dictionary
-object aanmaakten, in het volgende geval is de Key
dus van het type int
en Value
van het type string
:
We kunnen echter ook een specifiek element opvragen aan de hand van de key. Stel dat we de waarde van de klant met key 123 willen tonen:
De key werkt dus net als de index bij gewone arrays, alleen heeft de key nu geen relatie meer met de positie van het element in de collectie.
Je kan de syntax met rechte haakjes ook gebruiken om een element toe te voegen. In tegenstelling tot Add, geeft deze syntax geen fout als de key al bestaat, maar vervangt hij het bestaande verband:
Als je wil weten of een bepaalde key voorkomt in een Dictionary, gebruik je de instantiemethode ContainsKey
.
Al deze oefeningen maak je als statische methoden van een klasse GevorderdeTekstverwerking
. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.
Werken met .Split en .Join
Itereren over array
Je vraagt de gebruiker een aantal getallen gescheiden door ';' in te geven. je laat vervolgens de som van deze getallen zien.
Schrijf in de klasse GevorderdeTekstverwerking
een methode SomVanGetallen
. Deze vraagt de gebruiker een aantal getallen te geven gescheiden door een ';'. Je leest de invoer in, splitst de invoer in getallen, maakt de som en laat die zien. Let op, het aantal getallen mag de gebruiker zelf bepalen.
Formateren van tekst
String interpolatie
Methode oproep
Je vraagt de gebruiker een tekst in te geven. Vervolgens vraag je de gewenst breedte van de tekst. Je laat de tekst dan centraal gealigneerd aan de gebruiker zien.
Schrijf in de klasse GevorderdeTekstverwerking
een methode CentraalAlignerenTekst
. Deze vraagt de gebruiker een tekst in te geven en een gewenste lengte (minimaal de lengte van de tekst - controle!). Maak een tweede methode CentraalAligneren
Vervolgens wordt de tekst centraal gealigneerd getoond aan de gebruiker. De gebruiker kan het padding karakter ook kiezen.
stringformattering
string methodes
gebruik van karakters
Maak een kopie van Kerstinkopen in deze klasse.
Pas door middel van stringformattering en string methodes de code aan, zodat:
Een scheidingslijn getekend wordt onder "Info over je aankopen", die net breed genoeg is. Als we later deze hoofding veranderen (bijvoorbeeld naar "Informatie over je aankopen") moet de lijn mee groeien of krimpen.
Alle bedragen netjes onder elkaar staan in één kolom. Hiervoor mag je veronderstellen dat de berichten op de linkerkant maximum 25 karakters in beslag zullen nemen.
Het symbool voor de munteenheid van de gebruiker vanzelf gebruikt wordt. Dit kan dus € zijn maar ook $ of £ of iets anders.
Als je het symbool voor de munteenheid niet te zien krijgt in de terminal van Visual Studio, ligt dat niet aan jouw code. Voer je code dan uit via het commando dotnet run
in Git bash.
Deze voorbeeldinteractie gebruikt de List versie van Kerstinkopen. Maak de oefening met de array versie. Het gaat hem hier over de weergave onderaan en de aanpassingen daar zijn dezelfde voor beide versies.
Gebruik van input en output van tekstbestanden
We willen onze gemaakte TextCells ook kunnen opslaan en weer openen.
Maak in je oefeningen map een nieuwe map aan TextCell. In deze map zullen de TextCell bestanden bewaard worden. Je kan in je applicatie ook een relatief pad opgeven (dus niet c:\...). Dit relatief pad gaat vertrekkende van de map waar je nu op werkt een map zoeken (dit is wat kort door de bocht, de werkelijkheid is een stuk ingewikkelder maar voor nu volstaat dit). Het relatieve pad dat wij gebruiken is "./TextCell/filenaam.aptx).
Kopieer het bestand TextCell.cs naar TextCellPersistent.cs. Maak gerbuik van de in de theorie aangehaalde methodes File.ReadAllLines en File.WriteAllLines. Test bij opslaan van het bestand of het bestand reeds bestaat en bij laden van het bestand of het bestand sowieso bestaat.
We wensen de kunstwerken die we in Pixels tekenen op te slaan en in te laden.
Kopieer de code voor Pixels naar deze klasse.
Zorg ervoor dat je twee extra opties hebt in je tekenprogramma. Om een tekening op te slaan, moet je elke ConsoleColor
casten naar een int
. Dan kan je één rij pixels opslaan als één rij getallen, gescheiden door puntkomma. Om een tekening in te laden, doe je het omgekeerde.
In sommige situaties wil je dat een element geen twee keer in een datastructuur terecht kan komen. Je wil bijvoorbeeld dat de lijst met cursussen die deel uitmaakt van een studieprogramma geen tweemaal dezelfde cursus kan bevatten.
In dit geval gebruik je geen List<T>
, maar een HashSet<T>
. Elementen toevoegen doe je met de methode Add
en elementen verwijderen doe je met Remove
. Ook hier beperken we ons voorlopig tot voorgedefinieerde soorten objecten. Wanneer we System.Object
hebben bestudeerd, kunnen we ook HashSet
s van onze eigen types maken.
De immutable variant is ImmutableHashSet.
Een Queue is een collectie van elementen die in een welbepaalde volgorde behandeld moeten worden: van voor naar achter. Vergelijk met een wachtrij bij de bakker: de klant die eerst in de rij staat wordt eerst geholpen, dan steeds de volgende klant tot we aankomen bij de klant die het laatst in de rij is aangesloten. We noemen dit soort van collectie ook wel een First In, First Out oftewel FIFO-collectie: het item dat eerst in de rij is gezet, is ook het eerste dat behandeld wordt.
Een Queue is dus een speciaal soort lijst, waarbij het toevoegen en verwijderen van elementen op de lijst niet op gelijk welke plaats mag gebeuren. Een queue biedt daarom geen Add() of RemoveAt() methode aan. In plaats daarvan gebruik je:
Enqueue(T item)
om een item aan de rij toe te voegen
Dequeue()
om een item uit de rij te halen. Deze methode geeft als returnwaarde het weggehaalde item terug, zodat je er iets mee kan doen.
Peek()
geeft je het eerstvolgende item terug, maar verwijdert het nog niet uit de rij.
Op lijn 18 wordt de volgende klant uit de rij gehaald. Deze klant gebruiken we nog snel om zijn naam te tonen aan de gebruiker, maar na lijn 29 zal deze klant verdwijnen. Wil je deze klant in meer dan één statement gebruiken, zal je hem dus moeten opslaan in een lokale variabele:
Op lijn 20 wordt er eerst 'gespiekt' wie de volgende klant is: Piet. Met Peek()
wordt hij echter nog niet uit de rij gehaald, zoals je in onderstaande output kan zien.
De immutablevariant van Queue is ImmutableQueue.
Het omgekeerde van een Queue is een Stack. Dit is een lijst van items waarbij je steeds het laatst toegevoegde item eerst wilt behandelen. Vergelijk dit met een stapel borden aan de afwas: het eerstvolgende bord dat je afwast, is het bovenste bord op de stapel, dus het laatst toegevoegde. Of met een rij wagens in een lange, smalle garage met maar één toegangspoort: de eerste wagen die kan buitenrijden, is degene die laatst is binnengereden.
Dit noemen we een LIFO-collectie, oftewel Last In, First Out. Waar Queue Enqueue(T item)
en Dequeue()
gebruikte om items toe te voegen en uit de rij te halen, gebruikt Stack
Push(T item)
om een item op de stapel te leggen.
Pop()
om een item van de stapel te nemen.
Peek()
om het bovenste item op de stapel te bekijken, zonder het er af te nemen.
Dit voorbeeld demonstreert de werking van de 'Maak ongedaan' functionaliteit die je hebt in de meeste programma's op je computer. Als je op 'Maak ongedaan' (Engels: undo, commando: Ctrl+Z) klikt, wordt enkel dat wat je als laatste gedaan hebt, teruggedraaid.
Volgend filmpje demonstreert de acties die de gebruiker uitvoert in een tekstbewerkingsprogramma:
De gebruiker neemt volgende stappen, vertrekkende vanaf een wit blad:
Voeg paragraaf toe
Zet tekst in vet
Haal stuk tekst weg
Maak laatste actie ongedaan
Maak tekst groter.
Maak laatste actie ongedaan
Maak tekst kleiner.
Voeg tekst toe.
De code om deze acties bij te houden in een actiehistoriek zou kunnen zijn:
Dit geeft volgende output:
De immutablevariant van Stack is ImmutableStack.
Je hoeft de werking van surrogate pairs voor deze cursus niet in detail te kennen, maar als je ooit een applicatie schrijft die emoji,... bevat, vind je de details.
Alle format specifiers staan . Voor deze cursus volstaat het dat je deze specifiers kent voor de verschillende soorten data.
Via de constructor kunnen we beginwaarden meegeven bij het maken van een nieuw object. Er zijn 11 manieren waarop dit kan zoals je .
Een lijst van alle cultures in .NET kan je .
Wanneer je de Visual Studio voor foreach gebruikt foreach [tab][tab]
dan zal deze code ook een var gebruiken voor de iteration variabele. De compiler kan aan de te gebruiken array zien wat het type van een individueel element in de array moet zijn. De foreach van zonet kan dus herschreven worden naar:
Standaard is hij public
()
kunnen we ook constructors overloaden, d.w.z. alternatieven met dezelfde naam voorzien maar met een ander stel parameters. De code is gelijkaardig als bij method overloading:
Bij dit laatste horen wel enkele nuances. Deze worden pas behandeld . Voorlopig zullen we alleen voorgedefinieerde types opnemen in dictionaries.
De eerdere oefening kan wat mooier gepresenteerd worden. Ze zou ook ingezet moeten kunnen worden in regio's waar de euro niet gebruikt wordt.
Deze cursus wordt gebruikt als handboek binnen de opleiding graduaat programmeren van de AP Hogeschool.
Indien bepaalde hoofdstukken of onderdelen niet duidelijk zijn of je hebt suggesties ter verbetering/aanvulling, aarzel dan niet om ons te contacteren.
Veel leer- en leesplezier...
Je code kan niet gecompileerd en uitgevoerd worden zolang er fouten in je code staan.
We willen zien wat er gebeurt als er een fout in het programma staat. We verwijderen het puntkomma aan het einde van het statement in de Main functie. We vervangen
door
Het enige dat we hebben gewijzigd is de puntkomma op het einde.
Probeer het programma opnieuw uit te voeren. Je krijgt een melding dat er iets mis is. Vink eerst aan dat er onthouden moet worden wat je nu doet en geef dan aan dat je de fouten wil tonen via "Show Errors". Dit geeft je een overzicht, waarin je opmerkt dat er iets niet klopt op de regel en in de kolom waar de puntkomma ontbreekt. In de screenshot staat [9,46] en dat wijst op regel 9 van de code, positie 46. Waar de puntkomma moet staan, dus.
Je ziet ook dat het haakje net na "World!" rood onderlijnd is. Dat wijst op de positie van de fout. Let op: ook hier weet de ontwikkelomgeving niet precies wat je uiteindelijk wil, dus zie dit eerder als een hint.
Plaats de puntkomma opnieuw op het einde van het statement op regel 9.
Voer het programma opnieuw uit.
De meest voorkomende fouten in deze eerste weken zullen zijn:
Puntkomma vergeten.
Schrijffouten in je code RaedLine i.p.v. ReadLine.
Geen rekening gehouden met hoofdletter gevoeligheid Readline i.p.v. ReadLine (zie volgende hoofdstuk).
Per ongeluk accolades verwijderd.
Code geschreven op plekken waar dat niet mag.
We gebruiken Visual Studio om een C# programma te ontwikkelen. In dit onderdeel leren we hoe je een C# project opstart.
We kunnen niet genoeg benadrukken hoe belangrijk het is om afspraken te maken en te volgen bij het schrijven van programma's. Goede afspraken zorgen ervoor dat je:
code gemakkelijk kan delen met andere programmeurs; zonder afspraken kan je niet in een team werken;
veel sneller fouten in je programma zal vinden;
code leesbaar blijft, ook als je maanden later opnieuw bekijkt;
sneller en beter gaan leren programmeren;
We vinden het warm water niet opnieuw uit en volgen de richtlijnen van Microsoft:
een overzicht van alle afspraken vind je op Naming Guidelines
een overzicht van algemene afspraken, wat hier van toepassing is, vind je op General Naming Conventions
De basis voor de naamgevingsafspraken zijn:
PascalNotatie: geen spaties tussen de woorden en alle woorden beginnen met een hoofdletter: EenProgrammaOmJeGoedBijTeVoelen
camelCasenotatie: geen spaties tussen de woorden en alle woorden, behalve het eerste, beginnen met een hoofdletter, bijvoorbeeld: eenProgrammaOmJeGoedBijTeVoelen
Ik raad jullie aan een rootmap te maken voor alle vakken die je op AP volgt. Je geeft die bijvoorbeeld de naam "AP" of "Hogeschool". Daarin maak je een map met de naam Programmeren Intro. In deze map zullen we alle projecten plaatsen die we in dit vak zullen maken.
Voor vakken waarin met Git gewerkt wordt, vermijden we mappen die automatisch gesynchroniseerd worden. Zet je rootmap dus niet in OneDrive of Dropbox of Google Drive. Dat kan voor technische problemen zorgen. Meer info vind je in de cursus Git.
In onderstaande kennisclip wordt er een andere syntax gebruikt om objecten aan te maken dan wat wij gewoon zijn (namelijk constructors met parameters). Dat maakt geen verschil voor de werking van de datastructuur. Je zou net zo goed een constructor kunnen definiëren die hetzelfde doet.
Een List<>
collectie is de meest standaard collectie die je kan beschouwen als een flexibelere variant op een een doodnormale array.
De Generieke List<>
klasse bevindt zich in de System.Collections.Generic
namespace. Je dient deze namespace dus als using
bovenaan toe te voegen wil je deze klasse kunnen gebruiken.
De klasse List<T>
is een zogenaamde generieke klasse. Tussen de < >
tekens plaatsen we het type dat de lijst zal moeten gaan bevatten. Vaak wordt dit genoteerd als T
voor "type". Bijvoorbeeld:
List<int> alleGetallen= new List<int>();
List<bool> binaryList = new List<bool>();
List<Pokemon> pokeDex = new List<Pokemon>();
List<string[]> listOfStringarrays = new List<string[]>();
Zoals je ziet hoeven we bij het aanmaken van een List
geen begingrootte mee te geven, wat we wel bij arrays moeten doen. Dit is een van de voordelen van List
: ze groeien mee. Als we toch een begingrootte meegeven (zoals in de kennisclip even getoond wordt) is dat enkel om de performantie van de code wat te verhogen in bepaalde scenario's. Wij gaan dit nooit doen.
Via de Add()
methode kan je elementen toevoegen aan de lijst. Je dient als parameter aan de methode mee te geven wat je aan de lijst wenst toe te voegen. Deze parameter moet uiteraard van het type zijn dat de List
verwacht.
In volgende voorbeeld maken we een List aan die objecten van het type string mag bevatten en vervolgens plaatsen we er twee elementen in.
Het leuke van een List is dat je deze ook kan gebruiken als een gewone array, waarbij je met de indexer elementen kan aanspreken. Stel bijvoorbeeld dat we een lijst hebben met minstens 4 strings in. Volgende code toont hoe we de string op positie 3 kunnen uitlezen en hoe we die op positie 2 overschrijven:
Ook de klassieke werking met for
blijft gelden. De enige aanpassing is dat List<T>
niet met Length
werkt maar met Count
.
Interessante methoden en properties voorts zijn:
Clear()
:methode die de volledige lijst leegmaakt
Insert()
: methode om element op specifieke plaats in lijst toe te voegen, bijvoorbeeld:
voegt de string toe op de tweede plek en schuift de rest naar achter
Contains()
: geef als parameter een specifiek object mee (van het type T
dat de List<T>
bevat) om te weten te komen of dat specifieke object in de List<>
terug te vinden is. Indien ja dan zal true worden teruggeven.
IndexOf()
: geeft de index terug van het element item in de rij. Indien deze niet in de lijst aanwezig is dan wordt -1 teruggegeven.
RemoveAt()
: verwijder een element op de index die je als parameter meegeeft.
Remove():
verwijder het gegeven element
Contains, Remove
en IndexOf
zullen zich met jouw eigen klassen niet noodzakelijk gedragen zoals je verwacht. De verklaring hierachter komt later aan bod, wanneer we Equals en GetHashCode bekijken. Ze zullen wel werken zoals verwacht voor voorgedefinieerde types, inclusief DateTime
.
Je kan met een eenvoudige for
of while-loop over een lijst itereren, maar het gebruik van een foreach-loop is toch handiger.
Dit is dan ook de meestgebruikte operatie om eenvoudig en snel een bepaald stuk code toe te passen op ieder element van de lijst:
Kennisclip (let op: de demonstratie SchoolAdmin is verouderd en wordt anders aangepakt in 2022. De tekst heeft voorrang!)
Attributen, ook velden of instantievariabelen genoemd, zijn stukjes data die je bijhoudt in objecten. Ze stellen informatie voor die deel uitmaakt van een (object van een) klasse. Ze werken zoals de variabelen die je al kent, maar hun scope is een klasse of een object van een klasse, afhankelijk van de vraag of ze static
zijn of niet. Door gebruik te maken van attributen, kunnen we stukjes data die samen horen ook samen houden op het niveau van de code. Alle data die samen hoort netjes groeperen en op een gestructureerd toegankelijk maken valt onder het begrip encapsulatie dat reeds eerder aan bod kwam.
Attributen behoren tot een algemenere categorie onderdelen van objecten genaamd members.
Een typisch voorbeeld van een klasse is Auto
. Er zijn verschillende stukjes data die deel kunnen uitmaken van één auto: de kilometerstand, het benzinepeil, de datum van het laatste onderhoud,...
Een reeds gekende manier om verwante informatie bij te houden is met behulp van "gesynchroniseerde" arrays, d.w.z. arrays die verwante data bijhouden op overeenkomstige posities. Onderstaand voorbeeld toont dit voor auto's:
Als we nu een methode willen uitvoeren die iets doet met alle informatie over een auto (bijvoorbeeld een onderhoudsrapport afprinten), moeten we drie waarden meegeven. Het is ordelijker deze zaken bij te houden in een object van klasse Auto
als volgt:
Al deze velden zijn voorlopig public
. Dat hoeft niet absoluut, maar het vergemakkelijkt de presentatie. Verdere opties volgen snel. Het is ook een afspraak om publieke velden met een hoofdletter te schrijven. Met deze voorstelling kunnen we dit nu doen:
Je ziet hier al enkele voordelen van encapsulatie:
je hoeft niet bij te houden hoe de informatie verspreid is over meerdere plaatsen
als we meer informatie over auto's (bv. het oliepeil) in het onderhoudsrapport steken, hoeven we onze calls van PrintOnderhoudsrapport
niet aan te passen
een kenmerk van goede code is dat wijzigingen typisch geen grote wijzigingen vereisen
Een veld krijgt normaal de defaultwaarde voor zijn type. Defaultwaarden hebben we reeds gezien. Het is mogelijk de beginwaarde aan te passen met de syntax voor een toekenning:
Nu hebben nieuwe auto's standaard 5 km op de teller staan, enzovoort. Merk op: deze waarde wordt voor elk nieuw object opnieuw berekend. Als je dus twee auto's aanmaakt in je programma, zullen zij beide een verschillende datum van het laatste onderhoud bevatten.
static
attributenIets dat static
is, hoort niet bij de objecten, maar wel bij de hele klasse. Bijvoorbeeld: voor auto's worden de milieunormen na een paar jaar strenger. Er is een vaste set milieunormen en de te halen milieunorm wordt vastgelegd voor alle auto's. Daarom zouden we de milieunorm als volgt kunnen bijhouden:
Herhaal: static
betekent niet "onveranderlijk" of "vast". Het betekent dat iets op niveau van de klasse werkt en niet op niveau van de objecten van die klasse.
Deze opdracht maak je tijdens de les. Als je de les niet kan bijwonen, volg je de demonstratie in de kennisclip.
We willen een programma maken dat ons helpt beheren wie is ingeschreven, welke cijfers behaald zijn, enzovoort. Hiervoor brengen we de concepten en functionaliteit die we gebruiken in kaart en stellen we ze voor in code.
We doen dit in een apart project SchoolAdmin
. We gebruiken dus niet het project IndividueleOefeningen
. Je volgt om dit project op te starten dezelfde stappen als eerder. Let hierbij goed op dat je dit project niet in een bestaande Git repository maakt. Je merkt direct dat je in een Git repository staat als je de nieuwe map SchoolAdmin
opent in VSC en Git bash tussen haakjes (main)
of (master)
toont. Vraag in dat geval aan je lector om na te kijken waar je het project beter wel zet.
Student
We maken een klasse student, met publieke attributen voor de naam, geboortedatum, het studentennummer en de gevolgde cursussen. Elke student kan vijf cursussen volgen. We houden ook bij hoe veel studenten er zijn via een attribuut.
In de Main
methode maken we enkele studenten. Deze zijn als volgt:
Said Aziz, geboren 1 juni 2000, volgt Programmeren en Databanken.
Mieke Vermeulen, geboren 1 januari 1998, volgt Communicatie.
Wanneer je de opdracht gevolgd hebt, deel je deze via Gitlab. Je maakt hiervoor een nieuw project aan op Gitlab, maar wel in dezelfde groep als eerder (die met jouw naam).
Negeer de mappen bin
en obj
in je versiebeheer.
De standaard datastructuren van C# zijn reference types. Dit betekent dat iedereen die zo'n datastructuur te pakken krijgt (bijvoorbeeld omdat je hem als argument meegeeft aan een methode), de inhoud van deze datastructuur ook kan wijzigen. Dit kan met opzet of gewoonweg per vergissing gebeuren.
Hoezo, "met opzet"? Denk eraan dat je typisch niet de enige programmeur bent die met bepaalde code in contact komt.
Bijvoorbeeld:
In het algemeen geldt: als iemand bepaalde mogelijkheden niet echt nodig heeft, geef ze dan niet. Dit is opnieuw encapsulatie.
Om te verhinderen dat een datastructuur wordt aangepast, kan je er een immutable versie van maken. Dit is een versie van die datastructuur waarvan de inhoud achteraf niet gewijzigd kan worden. Er bestaan immutable versies van de standaard datastructuren en ze heten gewoonweg ImmutableList<T>
en ImmutableDictionary<K,V>
.
Om deze versies te gebruiken, moet je de System.Collections.Immutable
namespace gebruiken. Wanneer je hier een using
directief voor hebt staan, kan je methodes zoals ToImmutableList<T>
oproepen op een lijst om er een immutable versie van te produceren. Deze immutable versie kan je dan veilig delen met code waarvan je niet wenst dat ze de inhoud van je lijst aanpast.
Een tweede manier om een immutable datastructuur te maken, is met een builder. Dit is een object waar je via Add
data aan toevoegt en dat je achteraf vraagt een immutable list te produceren. Je kan er een aanmaken met de statische methode CreateBuilder
van de immutable datastructuur die je wil gebruiken. Bijvoorbeeld:
Beginnende programmeurs denken soms dat ze hetzelfde effect kunnen verkrijgen door een property voor een datastructuur "read only" te maken. Dit doen ze dan door alleen een getter te voorzien en geen setter of, als ze buiten deze cursus gaan zoeken, met het sleutelwoordje readonly
.
Dit maakt je datastructuur niet immutable! Het zorgt er wel voor dat je het object op de heap waarin je data staat niet kan vervangen. Het zorgt er niet voor dat je de inhoud van dat object niet kan vervangen.
Bijvoorbeeld, als we personen voorzien van een array met lievelingsgerechten:
Onderstaande figuur toont een vereenvoudigde weergave van wat er aan de hand is:
Random getallen genereren in je code kan nuttig zijn om verschillende scenario's te simuleren, om software te beveiligen of om de gebruiker een interactievere ervaring te geven.
De Random
-klasse laat je toe om eenvoudig willekeurige gehele en komma-getallen te maken. Je moet hiervoor twee zaken doen:
Maak eenmalig een Random-generator aan: Random randomgen = new Random();
(wat dit juist wil zeggen zien we in Semester 2).
Roep de Next
methode aan telkens je een nieuw getal nodig hebt, bijvoorbeeld: int mijnGetal = randomgen.Next();
De aanroep van de methode Next()
zal een geheel getal willekeurig genereren.
De eerste stap dien je dus maar één keer te doen. Vanaf dan kan je telkens aan de generator een nieuw getal vragen m.b.v. Next
.
Volgende code toont bijvoorbeeld 3 random getallen op het scherm:
Je kan de Next
methode ook 2 waarden meegeven, namelijk de grenzen waarbinnen het getal moet gegenereerd worden. De tweede waarde wordt nooit gegenereerd. Wil je dus een getal tot en met 10 dan schrijf je 11, niet 10.
Enkele voorbeelden:
Met de NextDouble
methode kan je kommagetallen genereren tussen 0.0
en 1.0
(1.0 zal niet gegenereerd worden).
Wil je een groter kommagetal dan zal je dit gegenereerde getal moeten vermenigvuldigen naar de range die je nodig hebt. Stel dat je een getal tussen 0.0 en 10.0 nodig hebt, dan schrijf je: