Zie Scherp Scherper - Hoofdstuk 5
Na dit hoofdstuk kan je:
if, if/else, if/else if en de ternaire operatorswitch gebruiken in plaats van een lange if-ketenenum maken en inzetten voor leesbare, veilige codeTot nu toe was elk programma een rechtlijnige lijst opdrachten. Faalt er één stap (de bakker is toe), dan loopt de rest in het honderd. Een beter algoritme beslist onderweg:
De eerste twee tests uit dit algoritme, uitgezet op de weg naar de bakker.
Alles wat je tot nu toe schreef was sequentie: de ene opdracht na de andere.
Vanaf nu komt selectie erbij: op basis van een test voert je programma een stuk code uit of slaat het over. Later volgt nog iteratie, hetzelfde stuk code herhalen.
Elk algoritme dat je ooit zal schrijven is een combinatie van die drie. Dat is geen boute bewering, maar een stelling van Böhm en Jacopini uit 1966.
Een booleaanse expressie is C#-code die een
boolals resultaat geeft.
| Operator | Betekenis |
|---|---|
> < |
groter / kleiner dan |
>= <= |
groter / kleiner dan of gelijk aan |
== |
gelijk aan |
!= |
niet gelijk aan |
bool, dus mag je het bewaren: bool isKleiner = 65 > 67;string met een int gaat niet, een int met een double welOp een string werken enkel == en !=, en die zijn hoofdlettergevoelig:
Een double bewaart kommagetallen binair, en dat gaat niet altijd exact:
Waarschuwing
De wiskundige notatie 1 < x < 10 geeft een compilerfout: 1 < x levert een bool op, en die kan C# niet meer met 10 vergelijken. Schrijf x > 1 && x < 10.
Voor samengestelde voorwaarden (“hongerig EN genoeg geld”):
&& (EN): true als beide operanden true zijn|| (OF): true als minstens één operand true is! (NIET): keert de waarde om, en verwacht maar één operanda |
b |
EN | OF |
|---|---|---|---|
false |
false |
false |
false |
false |
true |
false |
true |
true |
false |
false |
true |
true |
true |
true |
true |
Logische operators verwachten bool-operanden en geven zelf ook een bool terug.
Tip
De wetten van De Morgan: een ! vóór een samengestelde expressie mag je naar binnen brengen, maar dan wisselt de EN met de OF. !(A && B) is hetzelfde als !A || !B, en !(A || B) is hetzelfde als !A && !B.
C# werkt een logische expressie van links naar rechts af en stopt van zodra het antwoord vastligt:
5 > 10 is al false, dus kan een && nooit meer true worden. De Console.ReadLine() wordt niet uitgevoerd en je programma vraagt dus geen invoer.
Is de linkse test al false, dan gaat de slagboom dicht.
Tip
Bij || stopt de computer zodra de linkse operand true is. Zet bij && dus de test die het vaakst faalt vooraan, bij || de test die het vaakst waar is.
! (logische NIET)* / %+ -< <= > >=== !=&&||Tip
Dezelfde val zit bij !: !isKlaar == isBezig betekent (!isKlaar) == isBezig. Twijfel je? Haakjes.
false (true && false)true (false || true, let op: hoofdlettergevoelig)1 < 5 geeft een bool, en die kan niet met 10 vergeleken wordentrue (!false)== en !=Zonder accolades hoort enkel de eerste lijn bij de if:
Met accolades hoort het hele blok bij de test:
Tip
Inspringen heeft in C# geen invloed op de programmaflow (anders dan in Python). De accolades bepalen het blok.
Voorman hier, even wakker schudden. = kent toe, == vergelijkt. Bij getallen houdt de compiler je tegen (if (leeftijd = 18) compileert niet), bij een bool is er geen vangnet:
Schrijf hier gewoon if (gevonden). Een bool is zelf al een booleaanse expressie. Testen op false? Dan !gevonden.
= zet de rechtse waarde in de linkse variabele. == vergelijkt beide en geeft een bool terug.Waarschuwing
Een else krijgt geen eigen booleaanse expressie. else (a <= b) is fout: de else draait gewoon wanneer de if niet waar was.
Eén variabele die de ene of de andere waarde moet krijgen? Dan is een volledige if - else veel typwerk:
Vóór het vraagteken staat de booleaanse expressie, erna de twee mogelijke uitkomsten. Het is de enige operator in C# met drie operanden, vandaar de naam.
De drie operanden, en welke van de twee waarden in boodschap belandt.
Waarschuwing
Ze levert enkel een waarde op en voert geen codeblok uit. Meerdere lijnen code nodig bij true of false? Dan een gewone if - else.
Elke false zakt door naar de volgende test. De eerste true voert zijn blok uit en verlaat de keten meteen: alle tests eronder worden niet meer gedaan.
De afsluitende else is optioneel en draait enkel als geen enkele test waar was.
Draai je de twee tests om, dan wordt x > 100 nooit meer bereikt. Bij 110 slaat x > 10 al toe:
Links staat de strengste test eerst, rechts de brede.
Tip
Laat de logica van je algoritme het toe? Zet dan de meest voorkomende test bovenaan: hoe minder tests de computer moet doen, hoe sneller je code draait.
Steven schrijft een kortingsprogramma: wie voor meer dan 100 euro koopt krijgt 10% korting, wie voor meer dan 50 euro koopt 5%. De A.I. leverde dit en hij vindt het er logisch uitzien:
“Bij 120 euro is dat toch meer dan 100, dus 10% korting”, verwacht hij. Waarom verschijnt er toch 5?
Opmerking
Het zijn twee losse if’s, geen if/else if-keten. Bij 120 euro zijn beide tests waar, dus overschrijft de tweede korting alsnog naar 5. Met een else if was die tweede test overgeslagen. Steven zag een getal verschijnen en keek niet na of het wel het júiste getal was.
Beslissingen in beslissingen:
De binnenste if - else zit volledig in het else-blok en wordt enkel bereikt als de temperatuur te hoog is.
Laat al die uitleg je niet wijsmaken dat if-structuren eenvoudig zijn. Het is als met pijl en boog leren jagen: vlekkeloos op een stilstaande schijf, tot er een mammoet op je afkomt. Da’s andere kak hé?
Teken zeker in het begin een flowchart. Bezint eer ge begint.
De omliggende accolades bepalen waar een variabele leeft:
Dezelfde code, twee plaatsen voor de declaratie. De balk toont hoe ver de scope van getal reikt.
Declareer de variabele vóór het blok waar je ze later nodig hebt, en geef ze meteen een waarde:
Belangrijk
Die = 0 is geen detail. Laat je ze weg, dan compileert je code nog steeds niet: Use of unassigned local variable ‘getal’. C# ziet dat getal enkel een waarde krijgt als de if uitgevoerd wordt, en dat staat niet vast.
Tip
Zolang je in de scope van een variabele zit, kan je geen tweede variabele met dezelfde naam aanmaken, ook niet in een geneste blok.
Een lange if/else if-keten korter schrijven:
Vier nieuwe keywords: switch, case, break, default.
int, een char, een string, een bool of een enum-waardedefaultdefault is niet verplicht: past er geen enkele case, dan doet de switch gewoon nietsTip
Accolades per case hoeven niet, maar elke case met code eindigt op break.
Twee cases die dezelfde code delen. Dit werkt enkel omdat case 2: zelf geen code bevat.
case 2 heeft geen eigen code, dus valt de uitvoer door.Waarschuwing
Kom je van C, Java of JavaScript: in C# mag een case met code niet doorvallen. Vergeet je break, dan compileert je code niet: Control cannot fall through from one case label to another.
Ook een string kan je testen. Tekst vergelijken is hoofdlettergevoelig, dus voorzie je per keuze twee labels:
Tip
Liever met één enkel teken werken? Gebruik een char. De case-waarden staan dan tussen apostrofs: case 'a':.
Helm op. Een dag van de week bijhouden zonder enum is foutgevoelig:
int (1 tot 7): wat bij waarde 9 of -4? En was 2 nu dinsdag?string ("maandag"): één typefout ("Maandag") en je test faaltEen bool heeft dat probleem niet: die kan maar twee waarden bevatten, true en false. Een enum laat je precies dat doen, maar dan met je eigen lijstje namen:
Opmerking
Definieer het type binnen class Program, maar niet binnen Main.
| In je code | Wat is het? | Hetzelfde met int |
|---|---|---|
Weekdagen |
het type dat jij zelf maakte | int |
Weekdagen.Donderdag |
een waarde van dat type | 18 |
dagKeuze |
de variabele die zo’n waarde bewaart | leeftijd |
Een waarde schrijf je altijd als typenaam, punt, waarde. Donderdag op zichzelf bestaat niet, net zoals je ConsoleColor.Red moet schrijven en niet Red.
Er staat nergens een string "Donderdag" in je code: C# haalt die naam uit de enum-definitie die je zelf schreef.
Tip
Typ switch, druk twee keer op tab en vul je enum-variabele in als testwaarde. Visual Studio genereert dan meteen alle cases.
Je kan die nummering zelf sturen. Krijgt een waarde geen expliciet getal, dan neemt ze die van de vorige plus één:
Bovenaan de standaardnummering, onderaan de zelfgekozen nummers met de gaten ertussen.
Een menu stel je voor met een enum, en de keuze van de gebruiker cast je erin:
Sinds .NET 5 kan het ook rechtstreeks vanuit tekst:
Waarschuwing
Een cast controleert niets. Typt de gebruiker 7, dan bevat keuze gewoon 7, een waarde zonder naam. Vang dat op met een default-case. Enum.Parse crasht dan weer op onzin zoals pizza, net als int.Parse. Daarvoor bestaat Enum.TryParse.
De voorman nog eens. Deze drie zie ik elk jaar passeren, en ze komen allemaal uit dezelfde verwarring tussen het type, de waarde en de variabele:
Belangrijk
En de klassieker vóór dat alles: je enum binnen Main zetten. Je krijgt dan een reeks vreemde fouten (} expected, Invalid token ‘(’) die niets met enums te maken lijken te hebben. Schuif de lijn boven static void Main en ze verdwijnen in één klap.
Gebruik enum telkens je vooraf weet welke waarden mogelijk zijn. Klassiek bij finite state machines:
Daarna een nette switch op playerGameState. Of denk aan schaken: Schaakstuk.Paard.
playerGameState bevat altijd precies één van de vijf toegelaten waarden.bool&& komt vóór ||if, if/else, if/else if en de ternaire operator: let op accolades, puntkomma’s en de volgordeswitch verkort een lange if-keten; elke case met code eindigt op breakenum maakt code leesbaarder en veiliger: een eigen type met vaste waardenTip
Volgende halte: herhalingen met while, do-while en for.
Kennisclips
Dezelfde concepten uit dit hoofdstuk, maar dan in andere talen. Handig om later code in Python of JavaScript te leren lezen.
Python gebruikt woorden in plaats van symbolen:
Waar C# &&, || en ! schrijft, leest Python bijna als een gewone zin (and, or, not). De werking is identiek, inclusief het stoppen zodra het resultaat vaststaat.
Python kent geen accolades. De inspringing bepaalt het blok, en het tussenstuk heet elif:
In C# heeft inspringen geen invloed op de programmaflow, enkel de accolades tellen. In Python is de inspringing verplicht: vergeet je daar een spatie, dan verandert de betekenis van je code.
In JavaScript valt de uitvoer wél door naar de volgende case als je break vergeet:
Bij option gelijk aan 1 verschijnen hier beide lijnen. Een beruchte bron van bugs. C# weigert net daarom code zonder break bij een case met inhoud.
JavaScript kent geen enum, daar behelpen programmeurs zich met een object vol constanten. TypeScript (JavaScript met types) heeft er wel een, die sterk op die van C# lijkt:
Net als in C# worden de waarden vanaf 0 genummerd en blijft een variabele beperkt tot deze waarden.
Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).