3. De basis van de taal C#
3.1. Introduction
We behandelen C# eerst als een klassieke programmeertaal. We zullen later ingaan op klassen. In een programma komen twee dingen voor:
- gegevens
- de instructies die deze gegevens bewerken
Meestal proberen we de gegevens en de instructies van elkaar te scheiden:
![]() |
3.2. De gegevens in C#
C# maakt gebruik van de volgende gegevenstypen:
- gehele getallen
- reële getallen
- decimale getallen
- tekens en tekenreeksen
- booleaanse waarden
- objecten
3.2.1. Vooraf gedefinieerde gegevenstypen
C#-type | Type .NET | Weergegeven gegevens | Achtervoegsel van de letterlijke waarden | Codering | Waardenbereik |
Char (S) | teken | 2 bytes | Unicode-teken (UTF-16) | ||
String (C) | tekenreeks | verwijzing naar een reeks Unicode-tekens | |||
Int32 (S) | geheel getal | 4 bytes | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | 4 bytes | [0, 232-1] [0, 4294967295] | |
Int64 (S) | .. | L | 8 bytes | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | 8 bytes | [0, 264 -1] [0, 18446744073709551615] | |
.. | 1 byte | [-27 , 27 -1] [-128,+127] | |||
Byte(s) | .. | 1 byte | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 bytes | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 bytes | [0, 216-1] [0,65535] | ||
Enkel (S) | reëel getal | F | 4 bytes | [1.5 10-45, 3.4 10+38] als absolute waarde | |
Dubbel (S) | .. | D | 8 bytes | [-1.7 10+308, 1.7 10+308] in absolute waarde | |
Decimaal (S) | decimaal getal | M | 16 bytes | [1.0 10-28,7.9 10+28] als absolute waarde met 28 significante cijfers | |
Boolean (S) | .. | 1 byte | true, false | ||
Object (C) | objectreferentie | objectreferentie |
Hierboven zijn de C#-typen naast hun equivalente .NET-type geplaatst, met de opmerking (S) als dit type een structuur is en (C) als het type een klasse is. We zien dat er twee mogelijke typen zijn voor een 32-bits geheel getal: int en Int32. Het type int is een C#-type. Int32 is een structuur die behoort tot de naamruimte System. De volledige naam is dus System.Int32. Het type int is een C#-alias die verwijst naar de structuur .NET System.Int32. Evenzo is het C#-type string een alias voor het type .NET System.String. System.String is een klasse en geen structuur. Beide begrippen lijken op elkaar, maar er is een fundamenteel verschil:
- een variabele van het type Structure wordt gemanipuleerd via haar waarde
- een variabele van het type Classe wordt bewerkt via haar adres (referentie in objectgeoriënteerde programmeertaal).
Zowel een structure als een classe zijn complexe typen met attributen en methoden. Zo kunnen we schrijven:
Hierboven is de letterlijke waarde 3 standaard van het C#-type int, dus van het type .NET System.Int32. Deze structuur heeft een methode GetType() die een object retourneert waarin de kenmerken van het gegevenstype System.Int32 zijn ingekapseld. Een van deze kenmerken is de eigenschap FullName, die de volledige naam van het type retourneert. We zien dus dat de letterlijke waarde 3 een complexer object is dan het op het eerste gezicht lijkt.
Hier volgt een programma dat deze verschillende punten illustreert:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
// voorbeeld 1
int ent = 2;
float fl = 10.5F;
double d = -4.6;
string s = "essai";
uint ui = 5;
long l = 1000;
ulong ul = 1001;
byte octet = 5;
short sh = -4;
ushort ush = 10;
decimal dec = 10.67M;
bool b = true;
Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
Console.WriteLine("Type de b[{1}] : [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
}
}
}
- regel 7: declaratie van een geheel getal ent
- regel 19: het type van een variabele v kan worden verkregen via v.GetType().FullName. De grootte van een structuur S kan worden verkregen via sizeof(S). De instructie Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) geeft de tekst die de eerste parameter is weer op het scherm, waarbij elke notatie {i} wordt vervangen door de waarde van de uitdrukking parami.
- regel 22: aangezien het type string een klasse aanduidt en geen structuur, kan de operator sizeof niet worden gebruikt.
Dit is het resultaat van de uitvoering:
De uitvoer toont de typen .NET en niet de C#-aliassen.
3.2.2. Notatie van letterlijke gegevens
145, -7, 0xFF (hexadecimaal) | |
100000L | |
134,789, -45E-18 (-45 × 10⁻¹⁸) | |
134.789F, -45E-18F (-45 10-18) | |
100000M | |
'A', 'b' | |
"vandaag" "c:\\hoofdstuk1\\alinea3" @"c:\hoofdstuk1\alinea3" | |
true, false | |
new DateTime(1954,10,13) (jaar, maand, dag) voor 13/10/1954 |
Let op de twee letterlijke tekenreeksen: "c:\\chap1\\paragraph3" en @"c:\chap1\paragraph3". In letterlijke tekenreeksen wordt het teken \ geïnterpreteerd. Zo staat "\n" voor het einde-van-regel-teken en niet voor de opeenvolging van de twee tekens \ en n. Als men deze opeenvolging zou willen, zou men "\\n" moeten schrijven, waarbij de reeks \\ wordt geïnterpreteerd als één enkel teken \. Men zou ook @"\n" kunnen schrijven om hetzelfde resultaat te krijgen. De syntaxis @"tekst" vereist dat tekst precies zo wordt overgenomen als hij is geschreven. Dit wordt soms een verbatim-string genoemd.
3.2.3. Gegevensdefinitie
3.2.3.1. De rol van declaraties
Een programma verwerkt gegevens die worden gekenmerkt door een naam en een type. Deze gegevens worden in het geheugen opgeslagen. Bij het compileren van het programma wijst de compiler aan elk gegeven een geheugenlocatie toe die wordt gekenmerkt door een adres en een grootte. Dit doet hij aan de hand van de declaraties die door de programmeur zijn gemaakt.
Bovendien stellen deze de compiler in staat om programmeerfouten op te sporen. Zo is de bewerking
x=x*2;
als foutief worden aangemerkt als x bijvoorbeeld een tekenreeks is.
3.2.3.2. Verklaring van constanten
De syntaxis voor het declareren van een constante is als volgt:
Bijvoorbeeld:
Waarom constanten declareren?
- Het programma is gemakkelijker te lezen als de constante een betekenisvolle naam krijgt:
- Het aanpassen van het programma gaat gemakkelijker als de „constante” moet worden gewijzigd. In het voorgaande voorbeeld geldt dus dat, als het btw-tarief naar 33% gaat, de enige aanpassing die nodig is het wijzigen van de instructie is die de waarde ervan definieert:
Als we 0,186 expliciet in het programma hadden gebruikt, zouden er talrijke instructies moeten worden aangepast.
3.2.3.3. Variabelen declareren
Een variabele wordt aangeduid met een naam en heeft betrekking op een gegevenstype. C# maakt onderscheid tussen hoofdletters en kleine letters. De variabelen FIN en fin zijn dus verschillend.
Variabelen kunnen bij hun declaratie worden geïnitialiseerd. De syntaxis voor het declareren van één of meerdere variabelen is:
waarbij Identificateur_de_type een vooraf gedefinieerd type is of een door de programmeur gedefinieerd type. Optioneel kan een variabele tegelijk met de declaratie worden geïnitialiseerd.
Het is ook mogelijk om het exacte type van een variabele niet te specificeren door het sleutelwoord var te gebruiken in plaats van Identificateur_de_type:
Het sleutelwoord var betekent niet dat de variabelen geen specifiek type hebben. De variabele variablei heeft het type van de gegevens valeuri die eraan zijn toegewezen. Initialisatie is hier verplicht, zodat de compiler het type van de variabele kan afleiden.
Hier volgt een voorbeeld:
using System;
namespace Chap1 {
class P00 {
static void Main(string[] args) {
int i=2;
Console.WriteLine("Type de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
var j = 3;
Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
var aujourdhui = DateTime.Now;
Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
}
}
}
- regel 6: een expliciet getypeerde waarde
- regel 7: (data).GetType().Name is de korte naam van (data), (data).GetType().FullName is de volledige naam van (data)
- regel 8: een impliciet getypeerde waarde. Omdat 3 van het type int is, zal j van het type int zijn.
- regel 10: een impliciet getypeerde waarde. Omdat DateTime.Now van het type DateTime is, zal aujourdhui van het type DateTime zijn.
Bij uitvoering krijgt men het volgende resultaat:
Een variabele die impliciet het type var heeft gekregen, kan daarna niet van type veranderen. Het is dus niet mogelijk om na regel 10 van de code de volgende regel te schrijven:
var aujourdhui = "aujourd'hui";
We zullen later zien dat het mogelijk is om een type „on-the-fly” in een uitdrukking te declareren. Dit is dan een anoniem type, een type waaraan de gebruiker geen naam heeft gegeven. Het is de compiler die dit nieuwe type een naam zal geven. Als een waarde van een anoniem type aan een variabele moet worden toegewezen, is de enige manier om deze te declareren het gebruik van het sleutelwoord var.
3.2.4. Conversies tussen getallen en tekenreeksen
nombre.ToString() | |
int.Parse(string) of System.Int32.Parse | |
long.Parse(string) of System.Int64.Parse | |
double.Parse (reeks) of System.Double.Parse (reeks) | |
float.Parse (tekenreeks) of System.Float.Parse (tekenreeks) |
De conversie van een tekenreeks naar een getal kan mislukken als de tekenreeks geen geldig getal vertegenwoordigt. Er wordt dan een fatale fout gegenereerd, een zogenaamde uitzondering. Deze fout kan worden afgehandeld met de volgende clausule try/catch:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Als de functie geen uitzondering genereert, gaan we door naar de volgende instructie; anders gaan we naar de hoofdtekst van de clausule catch en vervolgens naar de volgende instructie. We zullen later terugkomen op het beheer van uitzonderingen. Hier volgt een programma waarin enkele technieken voor het omzetten tussen getallen en tekenreeksen worden getoond. In dit voorbeeld geeft de functie affiche de waarde van haar parameter weer op het scherm. Zo geeft affiche(S) de waarde van S weer op het scherm, waarbij S van het type string is.
using System;
namespace Chap1 {
class P01 {
static void Main(string[] args) {
// gegevens
const int i = 10;
const long l = 100000;
const float f = 45.78F;
double d = -14.98;
// getal --> tekenreeks
affiche(i.ToString());
affiche(l.ToString());
affiche(f.ToString());
affiche(d.ToString());
//booleaanse waarde --> tekenreeks
const bool b = false;
affiche(b.ToString());
// tekenreeks --> int
int i1;
i1 = int.Parse("10");
affiche(i1.ToString());
try {
i1 = int.Parse("10.67");
affiche(i1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// tekenreeks --> long
long l1;
l1 = long.Parse("100");
affiche(l1.ToString());
try {
l1 = long.Parse("10.675");
affiche(l1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// tekenreeks --> double
double d1;
d1 = double.Parse("100,87");
affiche(d1.ToString());
try {
d1 = double.Parse("abcd");
affiche(d1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
// tekenreeks --> float
float f1;
f1 = float.Parse("100,87");
affiche(f1.ToString());
try {
d1 = float.Parse("abcd");
affiche(f1.ToString());
} catch (Exception e) {
affiche("Erreur : " + e.Message);
}
}// einde main
public static void affiche(string S) {
Console.Out.WriteLine("S={0}",S);
}
}// einde klasse
}
In de regels 30-32 wordt de eventuele uitzondering afgehandeld die kan optreden. e.Message is de foutmelding die verband houdt met de uitzondering e.
De verkregen resultaten zijn als volgt:
Let op: bij reële getallen in de vorm van een tekenreeks moet een komma worden gebruikt in plaats van een decimaalteken. We schrijven dus
maar
3.2.5. Gegevenstabellen
Een C#-array is een object waarmee gegevens van hetzelfde type onder één identificatie kunnen worden gebundeld. De declaratie ervan is als volgt:
n is het aantal gegevens dat de array kan bevatten. De syntaxis Array[i] verwijst naar het gegeven met nummer i, waarbij i tot het interval [0,n-1] behoort. Elke verwijzing naar het gegevenselement Tableau[i] waarbij i niet tot het interval [0,n-1] behoort, zal een uitzondering veroorzaken. Een array kan tegelijk met de declaratie worden geïnitialiseerd:
of eenvoudiger:
Arrays hebben een eigenschap Length, die het aantal elementen van de array aangeeft.
Een tweedimensionale array kan als volgt worden gedeclareerd:
Type[,] array = new Type[n,m];
waarbij n het aantal rijen is en m het aantal kolommen. De syntaxis Tableau[i,j] verwijst naar element j in rij i van de array. De tweedimensionale array kan ook tegelijk met de declaratie worden geïnitialiseerd:
of eenvoudiger:
Het aantal elementen in elk van de dimensies kan worden verkregen met de methode GetLength(i), waarbij i=0 staat voor de dimensie die overeenkomt met de eerste index, i=1 voor de dimensie die overeenkomt met de tweede index, …
Het totale aantal dimensies wordt verkregen met de eigenschap Rank, het totale aantal elementen met de eigenschap Length.
Een array van arrays wordt als volgt gedeclareerd:
Type[][] array = new Type[n][];
De bovenstaande declaratie creëert een array met n rijen. Elk element array[i] is een eendimensionale array-referentie. Deze array-referenties array[i] worden bij de bovenstaande declaratie niet geïnitialiseerd. Ze hebben de waarde null.
Het onderstaande voorbeeld illustreert het aanmaken van een array van arrays:
// een array van arrays
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//for
// initialisatie
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//for j
}//for i
- regel 2: een array noms met 3 elementen van het type string[][]. Elk element is een array-pointer (een objectreferentie) waarvan de elementen van het type string[] zijn.
- regels 3-5: de 3 elementen van de array noms worden geïnitialiseerd. Elk element „verwijst“ nu naar een array met elementen van het type string[]. noms[i][j] is het element j van de array van het type string [] waarnaar wordt verwezen door noms[i].
- regel 9: initialisatie van het element namen[i][j] binnen een dubbele lus. Hier is `noms[i]` een array met `i+1` elementen. Aangezien `noms[i]` een array is, is `noms[i].Length` het aantal elementen ervan.
Hier volgt een voorbeeld waarin de drie soorten arrays die we zojuist hebben besproken, worden gecombineerd:
using System;
namespace Chap1 {
// arrays
using System;
// testklasse
public class P02 {
public static void Main() {
// een geïnitialiseerde eendimensionale array
int[] entiers = new int[] { 0, 10, 20, 30 };
for (int i = 0; i < entiers.Length; i++) {
Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
}//voor
// een geïnitialiseerde tweedimensionale array
double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
for (int i = 0; i < réels.GetLength(0); i++) {
for (int j = 0; j < réels.GetLength(1); j++) {
Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
}//for j
}//for i
// een array van arrays
string[][] noms = new string[3][];
for (int i = 0; i < noms.Length; i++) {
noms[i] = new string[i + 1];
}//voor
// initialisatie
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
noms[i][j] = "nom" + i + j;
}//voor j
}//voor i
// weergave
for (int i = 0; i < noms.Length; i++) {
for (int j = 0; j < noms[i].Length; j++) {
Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
}//voor j
}//voor i
}//Hoofdpagina
}//klasse
}//naamruimte
Bij uitvoering krijgen we de volgende resultaten:
3.3. De basisinstructies van C#
We onderscheiden
1 de basisinstructies die door de computer worden uitgevoerd.
2 de instructies die de uitvoering van het programma sturen.
De basisinstructies worden duidelijk wanneer men de structuur van een microcomputer en zijn randapparatuur bekijkt.
![]() |
-
het inlezen van informatie via het toetsenbord
-
verwerking van informatie
-
informatie naar het scherm schrijven
3.3.1. Schrijven naar het scherm
Er zijn verschillende instructies om informatie op het scherm weer te geven:
Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine (expression)
waarbij expression elk gegevenstype is dat kan worden omgezet in een tekenreeks om op het scherm te worden weergegeven. Alle objecten in C# of .NET beschikken over een methode ToString() die wordt gebruikt om deze conversie uit te voeren.
De klasse System.Console biedt toegang tot schermschrijfbewerkingen (Write, WriteLine). De klasse Console heeft twee eigenschappen, Out en Error, die schrijfstromen van het type TextWriter zijn:
- Console.WriteLine() is gelijk aan Console.Out.WriteLine() en schrijft naar de Out-stream die doorgaans aan het scherm is gekoppeld.
- Console.Error.WriteLine() schrijft naar de Error-stream, die doorgaans ook aan het scherm is gekoppeld.
De streams Out en Error kunnen tijdens de uitvoering van het programma naar tekstbestanden worden omgeleid, zoals we straks zullen zien.
3.3.2. Gegevens van het toetsenbord lezen
De gegevensstroom afkomstig van het toetsenbord wordt aangeduid door het object Console.In van het type TextReader. Met dit type objecten kan een tekstregel worden gelezen met de methode ReadLine:
De klasse Console biedt een methode ReadLine die standaard is gekoppeld aan de stroom In. We kunnen dus het volgende schrijven:
De via het toetsenbord ingevoerde regel wordt opgeslagen in de variabele ligne en kan vervolgens door het programma worden verwerkt. De In-stream kan, net als de Out- en Error-streams, naar een bestand worden omgeleid.
3.3.3. Voorbeeld van invoer en uitvoer
Hier volgt een kort programma ter illustratie van invoer- en uitvoerbewerkingen via toetsenbord en scherm:
using System;
namespace Chap1 {
// testklasse
public class P03 {
public static void Main() {
// schrijven naar de Out-stream
object obj = new object();
Console.Out.WriteLine(obj);
// schrijven naar de Error-stream
int i = 10;
Console.Error.WriteLine("i=" + i);
// een via het toetsenbord ingevoerde regel lezen
Console.Write("Tapez une ligne : ");
string ligne = Console.ReadLine();
Console.WriteLine("ligne={0}", ligne);
}//einde main
}//einde klasse
}
- regel 9: obj is een objectreferentie
- regel 10: obj wordt op het scherm weergegeven. Standaard wordt de methode obj.ToString() aangeroepen.
- regel 14: men kan ook schrijven:
Console.Error.WriteLine("i={0}",i);
De eerste parameter "i={0}" is het weergaveformaat, de overige parameters zijn de uit te geven uitdrukkingen. De elementen {n} zijn "positionele" parameters. Bij uitvoering wordt de parameter {n} vervangen door de waarde van uitdrukking nr. n.
Het resultaat van de uitvoering is als volgt:
- regel 1: de weergave die wordt gegenereerd door regel 10 van de code. De methode obj.ToString() heeft de typenaam van de variabele obj weergegeven: System.Object. Het type object is een C#-alias voor het type .NET System.Object.
3.3.4. Omleiding van I/O
Onder DOS en UNIX bestaan drie standaardapparaten met de namen:
- standaard invoerapparaat – verwijst standaard naar het toetsenbord en heeft nummer 0
- standaarduitvoerapparaat – verwijst standaard naar het scherm en heeft nummer 1
- standaardfoutapparaat – verwijst standaard naar het scherm en heeft nummer 2
In C# schrijft de schrijfstream Console.Out naar apparaat 1, schrijft de schrijfstream Console.Error naar apparaat 2 en leest de leesstream Console.In gegevens van apparaat 0.
Wanneer je een programma onder DOS of Unix start, kun je instellen welke apparaten 0, 1 en 2 zijn voor het uitgevoerde programma. Laten we de volgende opdrachtregel eens bekijken:
Achter de argumenten argi van het programma pg kunnen de standaard I/O-apparaten naar bestanden worden omgeleid:
de standaardinvoerstroom nr. 0 wordt omgeleid naar het bestand in.txt. In het programma haalt de stroom Console.In zijn gegevens dus uit het bestand in.txt. | |
leidt uitvoer nr. 1 om naar het bestand out.txt. Dit heeft tot gevolg dat in het programma de stroom Console.Out zijn gegevens naar het bestand out.txt schrijft | |
idem, maar de geschreven gegevens worden toegevoegd aan de huidige inhoud van het bestand out.txt. | |
leidt uitgang nr. 2 om naar het bestand error.txt. Dit heeft tot gevolg dat de stroom Console.Error in het programma zijn gegevens naar het bestand error.txt zal schrijven | |
Hetzelfde, maar de geschreven gegevens worden toegevoegd aan de huidige inhoud van het bestand error.txt. | |
De apparaten 1 en 2 worden beide omgeleid naar bestanden |
Merk op dat het programma pg de I/O-stromen naar bestanden kan omleiden zonder dat het programma pg hoeft te worden aangepast. Het besturingssysteem bepaalt de aard van de apparaten 0, 1 en 2. Laten we het volgende programma eens bekijken:
using System;
namespace Chap1 {
// omleidingen
public class P04 {
public static void Main(string[] args) {
// In-stream lezen
string data = Console.In.ReadLine();
// Out-stream schrijven
Console.Out.WriteLine("écriture dans flux Out : " + data);
// schrijven van foutstroom
Console.Error.WriteLine("écriture dans flux Error : " + data);
}//Main
}//klasse
}
Laten we het uitvoerbare bestand van deze broncode genereren:
![]() |
- in [1]: het uitvoerbare bestand wordt aangemaakt door met de rechtermuisknop op het project te klikken / Build
- in [2]: in een DOS-venster is het uitvoerbare bestand 04.exe aangemaakt in de map bin/Release van het project.
Voer de volgende opdrachten uit in het DOS-venster [2]:
- regel 1: we plaatsen de tekenreeks test in het bestand in.txt
- regels 2-3: we geven de inhoud van het bestand in.txt weer ter controle
- regel 4: het programma 04.exe wordt uitgevoerd. De stream In wordt omgeleid naar het bestand in.txt, de stroom Out naar het bestand out.txt, de stroom Error naar het bestand err.txt. De uitvoering leidt niet tot een weergave.
- regels 5-6: inhoud van het bestand out.txt. Uit deze inhoud blijkt dat:
- het bestand in.txt is gelezen
- de schermuitvoer is omgeleid naar out.txt
- regels 7-8: soortgelijke controle voor het bestand err.txt
Het is duidelijk te zien dat de stromen Out en In niet naar dezelfde apparaten schrijven, aangezien we ze afzonderlijk konden omleiden.
3.3.5. Toewijzing van de waarde van een uitdrukking aan een variabele
We richten ons hier op de bewerking variable=expression;
De uitdrukking kan van het volgende type zijn: rekenkundig, relationeel, booleaans, tekenreeks
3.3.5.1. Interpretatie van de toewijzingsbewerking
De bewerking variable=expression;
is zelf een uitdrukking waarvan de evaluatie als volgt verloopt:
- de rechterkant van de toewijzing wordt geëvalueerd: het resultaat is een waarde V.
- de waarde V wordt toegewezen aan de variabele
- de waarde V is tevens de waarde van de toewijzing, ditmaal beschouwd als een uitdrukking.
Zo wordt de bewerking
geldig is. Vanwege de prioriteit wordt de operator = die het meest rechts staat, geëvalueerd. We hebben dus
De uitdrukking V2=uitdrukking wordt geëvalueerd en heeft als waarde V. De evaluatie van deze uitdrukking heeft ertoe geleid dat V is toegewezen aan V2. Het volgende =-teken wordt vervolgens geëvalueerd in de vorm:
De waarde van deze uitdrukking is nog steeds V. De evaluatie ervan leidt tot de toewijzing van V aan V1.
De bewerking V1=V2=uitdrukking
is dus een uitdrukking waarvan de evaluatie
- er voor zorgt dat de waarde van expression wordt toegewezen aan de variabelen V1 en V2
- geeft als resultaat de waarde expression.
Dit kan worden veralgemeend tot een uitdrukking van het type:
3.3.5.2. Rekenkundige uitdrukking
De operatoren voor rekenuitdrukkingen zijn de volgende:
-
optelling
-
aftrekking
* vermenigvuldiging
/ deling: het resultaat is het exacte quotiënt als ten minste één van de operanden reëel is. Als beide operanden gehele getallen zijn, is het resultaat het gehele quotiënt. Dus 5/2 -> 2 en 5,0/2 -> 2,5.
% deling: het resultaat is de rest, ongeacht de aard van de operanden, waarbij het quotiënt een geheel getal is. Dit is dus de modulo-bewerking.
Er bestaan diverse wiskundige functies. Hier volgen er enkele:
vierkantswortel | |
Cosinus | |
Sinus | |
Tangens | |
x tot de macht y (x>0) | |
Exponentieel | |
Natuurlogaritme | |
absolute waarde |
enz...
Al deze functies zijn gedefinieerd in een C#-klasse met de naam Math. Wanneer je ze gebruikt, moet je ze vooraf laten gaan door de naam van de klasse waarin ze zijn gedefinieerd. Je schrijft dus:
De volledige definitie van de klasse Math is als volgt:





3.3.5.3. Prioriteiten bij het evalueren van rekenkundige uitdrukkingen
De prioriteit van de operatoren bij het berekenen van een rekenuitdrukking is als volgt (van hoogste naar laagste prioriteit):
Operatoren binnen hetzelfde blok [ ] hebben dezelfde prioriteit.
3.3.5.4. Relationele uitdrukkingen
De operatoren zijn als volgt:
Prioriteiten van de operatoren
Het resultaat van een relationele uitdrukking is de booleaanse waarde false als de uitdrukking onwaar is, en true in het tegenovergestelde geval.
Vergelijking van twee tekens
Stel dat we twee tekens hebben: C1 en C2. Deze kunnen worden vergeleken met de operatoren
Het zijn dan hun Unicode-codes, die uit getallen bestaan, die met elkaar worden vergeleken. Volgens de Unicode-volgorde gelden de volgende relaties:
Vergelijking van twee tekenreeksen
Ze worden teken voor teken vergeleken. De eerste ongelijkheid die tussen twee tekens wordt aangetroffen, leidt tot een ongelijkheid in dezelfde richting voor de tekenreeksen.
Voorbeelden:
Stel dat we de tekenreeksen "Kat" en "Hond" vergelijken
![]() |
Op basis van dit laatste verschil kunnen we stellen dat "Chat" < "Chien".
Stel dat we de tekenreeksen "Chat" en "Chaton" vergelijken. Er is telkens gelijkheid totdat de tekenreeks "Chat" is uitgeput. In dit geval wordt de uitgeputte tekenreeks als de "kleinste" aangemerkt. We hebben dus de relatie
Vergelijkingsfuncties voor twee tekenreeksen
Je kunt de relationele operatoren == en != gebruiken om te controleren of twee tekenreeksen gelijk zijn of niet, of de methode Equals van de klasse System.String. Voor de relaties < <= > >= moet de methode CompareTo van de klasse System.String worden gebruikt:
using System;
namespace Chap1 {
class P05 {
static void Main(string[] args) {
string chaine1="chat", chaine2="chien";
int n = chaine1.CompareTo(chaine2);
bool egal = chaine1.Equals(chaine2);
Console.WriteLine("i={0}, egal={1}", n, egal);
Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
}
}
}
Op regel 7 krijgt de variabele i de waarde:
0 als de twee tekenreeksen gelijk zijn
1 als tekenreeks nr. 1 > tekenreeks nr. 2
-1 als tekenreeks nr. 1 < tekenreeks nr. 2
Regel 8: de variabele egal krijgt de waarde true als de twee tekenreeksen gelijk zijn, en false in het tegenovergestelde geval. Regel 10: we gebruiken de operatoren == en != om te controleren of twee tekenreeksen gelijk zijn of niet.
De resultaten van de uitvoering:
3.3.5.5. Booleaanse uitdrukkingen
De beschikbare operatoren zijn AND (&&) OR (||) NOT (!). Het resultaat van een booleaanse uitdrukking is een booleaanse waarde.
Prioriteiten van operatoren:
- !
- &&
- ||
double x = 3.5;
bool valide = x > 2 && x < 4;
Relationele operatoren hebben voorrang op de operatoren && en ||.
3.3.5.6. Bitverwerking
De operatoren
Stel dat i en j twee gehele getallen zijn.
verschuift i n bits naar links. De binnkomende bits zijn nullen. | |
verschuift i n bits naar rechts. Als i een geheel getal met teken is (signed char, int, long), blijft het tekenbit behouden. | |
voert de bit-voor-bit logische OR-bewerking uit tussen i en j (ET). | |
voert de bit-voor-bit logische OR-bewerking uit tussen i en j (OU). | |
maakt i complementair tot 1 | |
voert de OU EXCLUSIF uit op i en j |
Stel dat de volgende code geldt:
short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i,j,k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4},k>>4=0x{2:x4},i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
- het formaat {0:x4} geeft parameter nr. 0 weer in hexadecimale notatie (x) met 4 tekens (4).
De resultaten van de uitvoering zijn als volgt:
3.3.5.7. Combinatie van operatoren
a=a+b kan worden geschreven als a+=b
a=a-b kan worden geschreven als a-=b
Hetzelfde geldt voor de operatoren /, %, *, <<, >>, &, |, ^. Zo kan a=a/2; worden geschreven als a/=2;
3.3.5.8. Inkrementatie- en dekrementatieoperatoren
De notatie variable++ betekent variable=variable+1 of ook variable+=1
De notatie variable-- betekent variable=variable-1 of ook variable-=1.
3.3.5.9. De ternaire operator ?
De uitdrukking
wordt als volgt berekend:
1 de uitdrukking expr_cond wordt geëvalueerd. Dit is een voorwaardelijke uitdrukking met de waarde vrai of faux
2 Als deze waar is, is de waarde van de uitdrukking die van expr1 en wordt expr2 niet geëvalueerd.
3 Als de voorwaarde onwaar is, gebeurt het omgekeerde: de waarde van de uitdrukking is die van expr2 en expr1 wordt niet berekend.
De bewerking i=(j>4 ? j+1:j-1); wijst aan de variabele i de waarde j+1 toe als j>4, en anders j-1. Dit is hetzelfde als if(j>4) i=j+1; else i=j-1; schrijven, maar het is beknopter.
3.3.5.10. Algemene prioriteit van operatoren
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
dg |
gd geeft aan dat bij gelijke prioriteit de volgorde van links naar rechts wordt gevolgd. Dit betekent dat wanneer er in een uitdrukking operatoren met dezelfde prioriteit voorkomen, de operator die zich het meest links in de uitdrukking bevindt, als eerste wordt geëvalueerd. dg geeft een volgorde van rechts naar links aan.
3.3.5.11. Typeomzettingen
Het is mogelijk om in een uitdrukking de codering van een waarde tijdelijk te wijzigen. Dit wordt het wijzigen van het type van een waarde genoemd, of in het Engels ‘type casting’. De syntaxis voor het wijzigen van het type van een waarde in een uitdrukking is als volgt:
De waarde krijgt dan het aangegeven type. Dit leidt tot een wijziging in de codering van de waarde.
using System;
namespace Chap1 {
class P06 {
static void Main(string[] args) {
int i = 3, j = 4;
float f1=i/j;
float f2=(float)i/j;
Console.WriteLine("f1={0}, f2={1}",f1,f2);
}
}
}
- regel 7: f1 krijgt de waarde 0,0. De deling 3/4 is een gehele deling, aangezien beide operanden van het type int zijn.
- regel 8: (float)i is de waarde van i, omgezet naar float. Nu hebben we een deling tussen een reëel getal van het type float en een geheel getal van het type int. Vervolgens wordt de deling tussen reële getallen uitgevoerd. De waarde van j wordt eveneens omgezet naar het type float, waarna de deling van de twee reële getallen wordt uitgevoerd. f2 krijgt dan de waarde 0,75.
Dit zijn de resultaten van de uitvoering:
In de bewerking (float)i:
- i is een exact gecodeerde waarde van 2 bytes
- (float) i is dezelfde waarde, bij benadering gecodeerd als een reëel getal over 4 bytes
Er vindt dus een transcodering plaats van de waarde van i. Deze transcodering vindt slechts plaats tijdens één berekening; de variabele i behoudt altijd het type int.
3.4. De instructies voor de controle van het programmaverloop
3.4.1. Stoppen
Met de methode Exit, gedefinieerd in de klasse Environment, kan de uitvoering van een programma worden gestopt.
syntaxe void Exit(int status)
action arrête le processus en cours et rend la valeur status au processus père
Exit beëindigt het lopende proces en geeft de controle terug aan het aanroepende proces. De waarde van status kan door dit proces worden gebruikt. Onder DOS wordt deze statusvariabele teruggegeven in de systeemvariabele ERRORLEVEL, waarvan de waarde in een batchbestand kan worden getest. Onder Unix, met de Bourne-shell, is het de variabele $? die de waarde van status ophaalt.
stopt de uitvoering van het programma met een statuswaarde van 0.
3.4.2. Eenvoudige keuzestructuur
opmerkingen:
- de voorwaarde staat tussen haakjes.
- Elke actie wordt afgesloten met een puntkomma.
- De accolades worden niet afgesloten met een puntkomma.
- accolades zijn alleen nodig als er meer dan één actie is.
- de clausule else mag ontbreken.
- Er is geen clausule then.
Het algoritmische equivalent van deze structuur is de if-then-else-structuur:
![]() |
Voorbeeld
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
Keuzestructuren kunnen in elkaar worden genest:
Soms doet zich het volgende probleem voor:
using System;
namespace Chap1 {
class P07 {
static void Main(string[] args) {
int n = 5;
if (n > 1)
if (n > 6)
Console.Out.WriteLine(">6");
else Console.Out.WriteLine("<=6");
}
}
}
In het vorige voorbeeld: naar welke if verwijst de else in regel 10? De regel is dat een else altijd verwijst naar de dichtstbijzijnde if: in dit voorbeeld is dat if(n>6), op regel 8. Laten we nog een voorbeeld bekijken:
if (n2 > 1) {
if (n2 > 6) Console.Out.WriteLine(">6");
} else Console.Out.WriteLine("<=1");
Hier wilden we een else koppelen aan if(n2>1) en geen else aan if(n2>6). Vanwege de vorige opmerking zijn we genoodzaakt accolades te gebruiken: if(n2>1) {...} else ...
3.4.3. Casestructuur
De syntaxis is als volgt:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default:
actions_sinon;
break;
}
opmerkingen
- de waarde van de controle-uitdrukking van switch kan een geheel getal, een teken of een tekenreeks zijn
- de controle-uitdrukking staat tussen haakjes.
- De clausule default mag ontbreken.
- De waarden vi zijn mogelijke waarden van de uitdrukking. Als de uitdrukking de waarde vi heeft, worden de acties achter de case-clausule vi uitgevoerd.
- De instructie break verlaat de case-structuur.
- elk instructieblok dat gekoppeld is aan een waarde vi moet eindigen met een spronginstructie (break, goto, return, ...), anders meldt de compiler een fout.
Voorbeeld
In de algoritmiek
selon la valeur de choix
cas 0
fin du module
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
In C#
3.4.4. Herhalingsstructuren
3.4.4.1. Aantal herhalingen bekend
For-structuur
De syntaxis is als volgt:
for (i=id;i<=if;i=i+ip){
actions;
}
Opmerkingen
- De 3 argumenten van for staan tussen haakjes en worden gescheiden door puntkomma's.
- Elke actie van de for wordt afgesloten met een puntkomma.
- De accolades zijn alleen nodig als er meer dan één actie is.
- De accolades worden niet gevolgd door een puntkomma.
Het algoritmische equivalent is de structuur pour:
wat kan worden vertaald naar een structuur tantque:
foreach-structuur
De syntaxis is als volgt:
foreach (Type variable in collection)
instructions;
}
Opmerkingen
- collection is een doorloopbare verzameling van objecten. De doorloopbare verzameling van objecten die we al kennen, is de array
- Type is het type van de objecten in de verzameling. Bij een array zou dit het type van de array-elementen zijn
- variable is een lokale variabele binnen de lus die achtereenvolgens alle waarden uit de verzameling als waarde zal aannemen.
De volgende code:
string[] amis = { "paul", "hélène", "jacques", "sylvie" };
foreach (string nom in amis) {
Console.WriteLine(nom);
}
zou het volgende weergeven:
3.4.4.2. Aantal herhalingen onbekend
Er bestaan talrijke structuren in C# voor dit geval.
While-structuur
while(condition){
actions;
}
De lus wordt herhaald zolang aan de voorwaarde wordt voldaan. Het kan zijn dat de lus nooit wordt uitgevoerd.
opmerkingen:
- de voorwaarde staat tussen haakjes.
- Elke actie wordt afgesloten met een puntkomma.
- De accolades zijn alleen nodig als er meer dan één actie is.
- De accolades worden niet gevolgd door een puntkomma.
De bijbehorende algoritmische structuur is de 'zolang'-structuur:
Herhaalstructuur tot (do while)
De syntaxis is als volgt:
do{
instructions;
}while(condition);
De lus wordt herhaald totdat de voorwaarde onwaar wordt. In dit geval wordt de lus minstens één keer doorlopen.
opmerkingen
- De voorwaarde staat tussen haakjes.
- Elke actie wordt afgesloten met een puntkomma.
- De accolades zijn alleen nodig als er meer dan één actie is.
- De accolades worden niet gevolgd door een puntkomma.
De bijbehorende algoritmische structuur is de structuur ‘herhaal … tot’:
Structuur voor 'for'
De syntaxis is als volgt:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
De lus wordt herhaald zolang de voorwaarde waar is (deze wordt voor elke lusronde geëvalueerd). Instructions_départ worden uitgevoerd voordat de lus voor de eerste keer wordt gestart. Instructions_fin_boucle worden na elke lusronde uitgevoerd.
opmerkingen
- De verschillende instructies in instructions_depart en instructions_fin_boucle worden gescheiden door komma's.
De bijbehorende algoritmische structuur is als volgt:
Voorbeelden
De volgende codefragmenten berekenen allemaal de som van de eerste 10 gehele getallen.
int i, somme, n=10;
for (i = 1, somme = 0; i <= n; i = i + 1)
somme = somme + i;
for (i = 1, somme = 0; i <= n; somme = somme + i, i = i + 1) ;
i = 1; somme = 0;
while (i <= n) { somme += i; i++; }
i = 1; somme = 0;
do somme += i++;
while (i <= n);
3.4.4.3. Instructies voor het beheer van de lus
verlaat de for-, while- of do...while-lus. | |
gaat door naar de volgende iteratie van de for-, while- en do ... while-lussen |
3.5. Uitzonderingsafhandeling
Veel C#-functies kunnen uitzonderingen, oftewel fouten, genereren. Wanneer een functie een uitzondering kan genereren, moet de programmeur deze afhandelen om programma's te verkrijgen die beter bestand zijn tegen fouten: het oncontroleerbaar "crashen" van een applicatie moet altijd worden vermeden.
Het afhandelen van een uitzondering verloopt volgens het volgende schema:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Als de functie geen uitzondering genereert, gaat men door naar de volgende instructie; anders gaat men naar de hoofdtekst van de clausule catch en vervolgens naar de volgende instructie. Let op de volgende punten:
- e is een object van het type Exception of een afgeleid type. We kunnen specifieker zijn door typen te gebruiken zoals IndexOutOfRangeException, FormatException, SystemException, enzovoort: er bestaan verschillende soorten uitzonderingen. Door catch (Exception e) te schrijven, geven we aan dat we alle soorten uitzonderingen willen afhandelen. Als de code van de clausule try meerdere soorten uitzonderingen kan genereren, willen we misschien specifieker zijn door de uitzondering af te handelen met meerdere clausules catch:
try{
code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
traiter l'exception e1
}
} catch ( FormatException e2){
traiter l'exception e2
}
instruction suivante
- Aan de clausules try/catch kan een finally-clausule worden toegevoegd:
try{
code susceptible de générer une exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
Of er nu een uitzondering is of niet, de code van de clausule finally wordt altijd uitgevoerd.
- In de clausule catch wil men het beschikbare object Exception misschien niet gebruiken. In plaats van catch (Exception e){..} te schrijven, schrijft men dan catch(Exception){...} of, eenvoudiger nog, catch {...}.
- De klasse Exception heeft een eigenschap Message die een bericht bevat met details over de fout die is opgetreden. Als men dit bericht wil weergeven, schrijft men dus:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
...
}//catch
- De klasse Exception heeft een methode ToString die een tekenreeks retourneert met het type van de uitzondering en de waarde van de eigenschap Message. We kunnen dus het volgende schrijven:
catch (Exception ex){
Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
...
}//catch
Men kan ook schrijven:
De compiler wijst aan de parameter {0} de waarde ex.ToString() toe.
Het volgende voorbeeld toont een uitzondering die wordt gegenereerd door het gebruik van een niet-bestaand array-element:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// definitie en initialisatie van een array
int[] tab = { 0, 1, 2, 3 };
int i;
// weergave van een array met een for-lus
for (i = 0; i < tab.Length; i++)
Console.WriteLine("tab[{0}]={1}", i, tab[i]);
// array weergeven met een for each-lus
foreach (int élmt in tab) {
Console.WriteLine(élmt);
}
// een uitzondering genereren
try {
tab[100] = 6;
} catch (Exception e) {
Console.Error.WriteLine("L'erreur suivante s'est produite : " + e);
return;
}//try-catch
finally {
Console.WriteLine("finally ...");
}
}
}
}
Hierboven zal regel 18 een uitzondering genereren omdat de array tab geen element nr. 100 heeft. De uitvoering van het programma levert de volgende resultaten op:
- regel 9: de uitzondering [System.IndexOutOfRangeException] is opgetreden
- regel 11: de clausule finally (regels 23-25) van de code is uitgevoerd, terwijl er op regel 21 een instructie return stond om de methode te verlaten. Merk op dat de clausule finally altijd wordt uitgevoerd.
Hier volgt nog een voorbeeld waarin de uitzondering wordt afgehandeld die wordt veroorzaakt door het toewijzen van een tekenreeks aan een variabele van het type integer, terwijl de tekenreeks geen geheel getal vertegenwoordigt:
using System;
namespace Chap1 {
class P08 {
static void Main(string[] args) {
// voorbeeld 2
// Er wordt om de naam gevraagd
Console.Write("Nom : ");
// antwoord voorlezen
string nom = Console.ReadLine();
// er wordt naar de leeftijd gevraagd
int age = 0;
bool ageOK = false;
while (!ageOK) {
// vraag
Console.Write("âge : ");
// antwoord lezen en controleren
try {
age = int.Parse(Console.ReadLine());
ageOK = age>=1;
} catch {
}//try-catch
if (!ageOK) {
Console.WriteLine("Age incorrect, recommencez...");
}
}//while
// eindweergave
Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
}
}
}
- regels 15-27: de lus voor het invoeren van de leeftijd van een persoon
- regel 20: de via het toetsenbord ingevoerde regel wordt door de methode int.Parse omgezet in een geheel getal. Deze methode genereert een uitzondering als de conversie niet mogelijk is. Daarom is de bewerking in een try/catch-blok geplaatst.
- regels 22-23: als er een uitzondering wordt gegenereerd, gaat men naar de catch-blok waar niets gebeurt. Daardoor blijft de booleaanse variabele ageOK, die in regel 14 op false is gezet, op false staan.
- regel 21: als deze regel wordt bereikt, is de conversie van string naar int geslaagd. Er wordt echter gecontroleerd of het verkregen gehele getal groter is dan of gelijk is aan 1.
- regels 24-26: er wordt een foutmelding weergegeven als de leeftijd onjuist is.
Enkele uitvoerresultaten:
3.6. Voorbeeldtoepassing - V1
We willen een programma schrijven waarmee de belasting van een belastingplichtige kan worden berekend. We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven (cijfers uit 2004 voor inkomsten uit 2003):
- we berekenen het aantal aandelen van de werknemer nbParts=nbEnfants/2 +1 als hij ongehuwd is, nbEnfants/2 + 2 als hij gehuwd is, waarbij nbEnfants het aantal kinderen is.
- als hij ten minste drie kinderen heeft, krijgt hij een half deel extra
- zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
- zijn gezinscoëfficiënt wordt berekend als QF = R / nbParts
- zijn belasting I wordt berekend. Laten we de volgende tabel eens bekijken:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23888 | 0,2826 | 2668,39 |
38868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als bijvoorbeeld QF = 5000 is, vinden we de regel
Belasting I is dan gelijk aan 0,0683*R - 291,09*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:
0 0,4809 9505,54
wat resulteert in de belasting I = 0,4809*R - 9505,54*nbParts.
Het bijbehorende C#-programma is als volgt:
using System;
namespace Chap1 {
class Impots {
static void Main(string[] args) {
// tabel met gegevens die nodig zijn voor de berekening van de belasting
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
// de burgerlijke staat ophalen
bool OK = false;
string reponse = null;
while (!OK) {
Console.Write("Etes-vous marié(e) (O/N) ? ");
reponse = Console.ReadLine().Trim().ToLower();
if (reponse != "o" && reponse != "n")
Console.Error.WriteLine("Réponse incorrecte. Recommencez");
else OK = true;
}//while
bool marie = reponse == "o";
// aantal kinderen
OK = false;
int nbEnfants = 0;
while (!OK) {
Console.Write("Nombre d'enfants : ");
try {
nbEnfants = int.Parse(Console.ReadLine());
OK = nbEnfants >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// salaris
OK = false;
int salaire = 0;
while (!OK) {
Console.Write("Salaire annuel : ");
try {
salaire = int.Parse(Console.ReadLine());
OK = salaire >= 0;
} catch {
}// try
if (!OK) {
Console.WriteLine("Réponse incorrecte. Recommencez");
}
}// while
// berekening van het aantal aandelen
decimal nbParts;
if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// belastbaar inkomen
decimal revenu = 0.72M * salaire;
// gezinsquotiënt
decimal QF = revenu / nbParts;
// opzoeken van de belastingschijf die overeenkomt met QF
int i;
int nbTranches = limites.Length;
limites[nbTranches - 1] = QF;
i = 0;
while (QF > limites[i]) i++;
// de belasting
int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
// het resultaat wordt weergegeven
Console.WriteLine("Impôt à payer : {0} euros", impots);
}
}
}
- regels 7-9: de numerieke waarden krijgen de achtervoegsel M (Money) zodat ze van het type decimal zijn.
- regel 16:
- Console.ReadLine() geeft de via het toetsenbord ingevoerde tekenreeks C1 terug
- C1.Trim() verwijdert de spaties aan het begin en einde van C1 – geeft de tekenreeks C2 terug
- C2.ToLower() levert de tekenreeks C3 op, wat de tekenreeks C2 is, omgezet naar kleine letters.
- regel 21: de booleaanse waarde marie krijgt de waarde true of false uit de relatie reponse=="o"
- regel 29: de via het toetsenbord ingevoerde tekenreeks wordt omgezet naar het type int. Als de omzetting mislukt, wordt er een uitzondering gegenereerd.
- regel 30: de booleaanse waarde OK krijgt de waarde true of false uit de relatie nbEnfants>=0
- regels 55-56: men kan niet simpelweg nbEnfants/2 schrijven. Als nbEnfants gelijk was aan 3, zouden we 3/2 hebben, een gehele deling die 1 zou opleveren en niet 1,5. Daarom schrijven we (decimal)nbEnfants om een van de operanden van de deling reëel te maken en zo een deling tussen reële getallen te verkrijgen.
Hier volgen enkele uitvoervoorbeelden:
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 euros
3.7. Argumenten van het hoofdprogramma
De hoofdfunctie Main kan als parameter een array van strings accepteren: String[] (of string[]). Deze array bevat de argumenten van de opdrachtregel die wordt gebruikt om de toepassing te starten. Als we het programma P dus starten met de volgende (DOS-)opdracht:
P arg0 arg1 … argn
en als de functie Main als volgt is gedeclareerd:
dan is args[0]="arg0", args[1]="arg1" … Hier volgt een voorbeeld:
using System;
namespace Chap1 {
class P10 {
static void Main(string[] args) {
// de ontvangen parameters worden weergegeven
Console.WriteLine("Il y a " + args.Length + " arguments");
for (int i = 0; i < args.Length; i++) {
Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
}
}
}
}
Om argumenten door te geven aan de uitgevoerde code, gaat men als volgt te werk:
![]() |
- in [1]: klik met de rechtermuisknop op het project / Properties
- in [2]: tabblad [Debug]
- in [3]: voer de argumenten in
De uitvoering levert de volgende resultaten op:
Merk op dat de handtekening
geldig is als de functie Main geen parameters verwacht.
3.8. Enumeraties
Een opsomming is een gegevenstype waarvan het waardenbereik bestaat uit een verzameling gehele constanten. Laten we eens kijken naar een programma dat vermeldingen bij een examen moet verwerken. Er zouden er vijf zijn: Passable, AssezBien, Bien, TrèsBien, Excellent.
We zouden dan een opsomming kunnen definiëren voor deze vijf constanten:
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
Intern worden deze vijf constanten gecodeerd door opeenvolgende gehele getallen, beginnend met 0 voor de eerste constante, 1 voor de volgende, enzovoort. Een variabele kan worden gedeclareerd om deze waarden uit de opsomming aan te nemen:
// een variabele die haar waarden ontleent aan de opsomming ‘Vermeldingen’
Mentions maMention = Mentions.Passable;
Een variabele kan worden vergeleken met de verschillende mogelijke waarden van de opsomming:
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
Men kan alle waarden van de opsomming opvragen:
// lijst met vermeldingen in de vorm van tekenreeksen
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
Net zoals het eenvoudige type int gelijkwaardig is aan de structuur System.Int32, is het eenvoudige type enum gelijkwaardig aan de structuur System.Enum. Deze structuur heeft een statische methode GetValues waarmee alle waarden van een opgesomd type kunnen worden opgehaald dat als parameter wordt doorgegeven. Dit moet een object van het type Type zijn, een klasse die informatie bevat over het type van een gegeven. Het type van een variabele v wordt verkregen via v.GetType(). Het type van een type T wordt verkregen via typeof(T). Dus hier levert maMention.GetType() het object Type uit de opsomming Mentions en Enum.GetValues(maMention.GetType()) de lijst met waarden van de opsomming Mentions.
Als we nu schrijven
//lijst met vermeldingen in de vorm van gehele getallen
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
Regel 2: de lusvariabele is van het type geheel getal. We krijgen dan de lijst met waarden van de opsomming in de vorm van gehele getallen. Het object van het type System.Type, dat overeenkomt met het gegevenstype Mentions, wordt verkregen via typeof(Mentions). We hadden ook, net als eerder, maMention.GetType() kunnen schrijven.
Het volgende programma illustreert wat zojuist is beschreven:
using System;
namespace Chap1 {
class P11 {
enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
static void Main(string[] args) {
// een variabele die zijn waarden ontleent aan de opsomming ‘Vermeldingen’
Mentions maMention = Mentions.Passable;
// weergave van de waarde van de variabele
Console.WriteLine("mention=" + maMention);
// test met waarde uit de opsomming
if (maMention == Mentions.Passable) {
Console.WriteLine("Peut mieux faire");
}
// lijst met vermeldingen in de vorm van tekenreeksen
foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
Console.WriteLine(m);
}
//lijst met vermeldingen in de vorm van gehele getallen
foreach (int m in Enum.GetValues(typeof(Mentions))) {
Console.WriteLine(m);
}
}
}
}
De uitvoerresultaten zijn als volgt:
3.9. Parameters doorgeven aan een functie
We kijken hier naar de manier waarop parameters aan een functie worden doorgegeven. Laten we de volgende statische functie eens bekijken:
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
In de definitie van de functie, regel 1, wordt a een formele parameter genoemd. Deze parameter is uitsluitend bedoeld voor de definitie van de functie changeInt. Hij had net zo goed b kunnen heten. Laten we nu eens kijken naar een toepassing van deze functie:
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
Hier in de instructie op regel 3, ChangeInt(age), is age de effectieve parameter die zijn waarde doorgeeft aan de formele parameter a. We zijn geïnteresseerd in de manier waarop een formele parameter de waarde van een effectieve parameter ophaalt.
3.9.1. Doorgave per waarde
Het volgende voorbeeld laat zien dat de parameters van een functie standaard per waarde worden doorgegeven, dat wil zeggen dat de waarde van de effectieve parameter wordt gekopieerd naar de bijbehorende formele parameter. Het gaat hier om twee afzonderlijke entiteiten. Als de functie de formele parameter wijzigt, blijft de effectieve parameter ongewijzigd.
using System;
namespace Chap1 {
class P12 {
public static void Main() {
int age = 20;
ChangeInt(age);
Console.WriteLine("Paramètre effectif age=" + age);
}
private static void ChangeInt(int a) {
a = 30;
Console.WriteLine("Paramètre formel a=" + a);
}
}
}
De verkregen resultaten zijn als volgt:
De waarde 20 van de effectieve parameter age is overgenomen in de formele parameter a (regel 10). Deze is vervolgens gewijzigd (regel 11). De effectieve parameter is ongewijzigd gebleven. Deze overdrachtswijze is geschikt voor invoerparameters van een functie.
3.9.2. Doorgeven via verwijzing
Bij doorgave via verwijzing zijn de werkelijke parameter en de formele parameter één en dezelfde entiteit. Als de functie de formele parameter wijzigt, wordt ook de werkelijke parameter gewijzigd. In C# moeten beide worden voorafgegaan door het sleutelwoord ref:
Hier volgt een voorbeeld:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// voorbeeld 2
int age2 = 20;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
en de uitvoerresultaten:
De werkelijke parameter heeft de wijziging van de formele parameter gevolgd. Deze manier van doorgeven is geschikt voor uitvoerparameters van een functie.
3.9.3. Doorgave via verwijzing met het sleutelwoord out
Laten we het vorige voorbeeld eens bekijken, waarin de variabele age2 niet zou worden geïnitialiseerd vóór de aanroep van de functie changeInt:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// voorbeeld 2
int age2;
ChangeInt2(ref age2);
Console.WriteLine("Paramètre effectif age2=" + age2);
}
private static void ChangeInt2(ref int a2) {
a2 = 30;
Console.WriteLine("Paramètre formel a2=" + a2);
}
}
}
Bij het compileren van dit programma treedt er een fout op:
Dit probleem kan worden omzeild door een beginwaarde toe te wijzen aan age2. Ook kan het sleutelwoord ref worden vervangen door het sleutelwoord out. Hiermee wordt aangegeven dat de parameter uitsluitend een uitgangsparameter is en dus geen beginwaarde nodig heeft:
using System;
namespace Chap1 {
class P12 {
public static void Main() {
// voorbeeld 3
int age3;
ChangeInt3(out age3);
Console.WriteLine("Paramètre effectif age3=" + age3);
}
private static void ChangeInt3(out int a3) {
a3 = 30;
Console.WriteLine("Paramètre formel a3=" + a3);
}
}
}
De resultaten van de uitvoering zijn als volgt:





