Programming
Cover
H1_BasisconceptenC++.pdf
Summary
# Basisconcepten C++
Dit hoofdstuk introduceert de belangrijkste uitbreidingen van C++ ten opzichte van C, waaronder nieuwe datatypes zoals `bool`, de standaard `string` klasse, namespaces, en het gebruik van functies als parameters [1](#page=1) [2](#page=2).
### 1.1 Nieuwe datatypes
C++ introduceert naast de basistypes uit C ook het datatype `bool`. Dit datatype kan de waarden `true` of `false` aannemen. Er is een automatische conversie tussen `bool` en `int`, waarbij `true` wordt omgezet naar `1` en `false` naar `0`. Omgekeerd wordt `0` omgezet naar `false` en elk niet-nul getal naar `true`. Dit elimineert de noodzaak van `#include ` zoals in C [4](#page=4).
#### 1.1.1 Conversies tussen datatypes
Net als in C vinden in C++ impliciete (automatische) conversies plaats tussen basistypes tijdens toekenning en rekenkundige bewerkingen, zelfs als dit informatieverlies kan veroorzaken. Expliciete conversies (casting) kunnen worden uitgevoerd met behulp van de C-stijl operator syntax `(type) uitdrukking` of de C++ functie-oproep syntax `type(uitdrukking)` [5](#page=5).
> **Tip:** C++ vereist minder expliciete conversies dankzij het verminderde gebruik van 'raw pointers' [5](#page=5).
#### 1.1.2 De `std::string` klasse
De `std::string` klasse uit de `` bibliotheek biedt een krachtigere en flexibelere manier om met tekst om te gaan dan C-strings [6](#page=6).
**Declaratie en initialisatie:**
Een `std::string` kan worden gedeclareerd en geïnitialiseerd op verschillende manieren:
- `std::string s1;` (initialiseert met een lege string `""`) [6](#page=6).
- `s1 = "Hij zei: \"Hallo\"\n";`
- `std::string s2("test");`
- `std::string s3 = s2;` (kopie van `s2`) [6](#page=6).
- `std::string s4(s3);` (gebruikt de copyconstructor) [6](#page=6).
- `std::string s5(10, 'c');` (string van 10 karakters 'c') [6](#page=6).
> **Merk op:** Bij het aanroepen van constructors voor `std::string` wordt geen `new` gebruikt. Function/constructor overloading is toegestaan in C++ [6](#page=6).
**Concatenatie:**
Strings kunnen worden samengevoegd met de `+` en `+=` operatoren. Minimaal één van de operanden van de `+` operator moet een standaardstring zijn [7](#page=7).
- `s = s + " Jan";`
- `s += '!';` (resulteert in `s = "dag Jan!"`)
- `std::string t = "Hoi," + s + " Alles goed?";` (geldige concatenatie) [7](#page=7).
- `t = "Dag" + " An!";` (ongeldige concatenatie) [7](#page=7).
**Vergelijken:**
Strings kunnen alfabetisch worden vergeleken met behulp van vergelijkingsoperatoren (`<`, `>=`, `==`, etc.) [7](#page=7).
- `if (s < "tomaat") …` (controleert of `s` alfabetisch vóór "tomaat" komt) [7](#page=7).
- `if (s >= t) …` (controleert of `s` alfabetisch na of gelijk aan `t` is) [7](#page=7).
- `if (s == "stop") …` (controleert of `s` de tekst "stop" bevat) [7](#page=7).
**Lengte en ontleden:**
De lengte van een string kan worden bepaald met de `size()` of `length()` methoden [8](#page=8).
- `std::string s = "dag An";`
- `int l = s.size();` (resultaat is 6) [8](#page=8).
- `l = s.length();` (resultaat is 6) [8](#page=8).
Strings zijn mutable, wat betekent dat individuele karakters kunnen worden aangepast [8](#page=8).
- `s = 'e';` [1](#page=1).
Iteratie over een string kan met een traditionele `for`-lus of met een range-based `for`-lus [8](#page=8).
```cpp
std::string s = "hallo";
for (int i = 0; i < s.size(); i++) {
printf("%c", s[i]); // Print h a l l o
}
s = 'e'; // s wordt "hello" [1](#page=1).
for (char c : s) {
printf("%c", c); // Print h e l l o
}
```
**Lidfuncties van `std::string`:**
De `std::string` klasse biedt diverse handige lidfuncties:
- `size_t find(string/[const char [*] s, size_t pos=0)`: Zoekt naar de eerste_instantie_van `s` vanaf positie `pos` en retourneert de index, of `string::npos` indien niet gevonden [9](#page=9).
> **Merk op:** De laatste parameter is een default parameter, wat betekent dat het argument optioneel is bij de aanroep [9](#page=9).
- `void insert(size_t pos, string/const char* s)`: Voegt string `s` in op positie `pos` [11](#page=11).
- `string substr(size_t pos=0, size_t len=string::npos)`: Retourneert een deelstring vanaf positie `pos` met lengte `len` [11](#page=11).
- `void erase(size_t pos=0, size_t len=string::npos)`: Verwijdert `len` karakters vanaf positie `pos` [11](#page=11).
- `void replace(size_t pos, size_t len, string/const char* s)`: Vervangt `len` karakters vanaf positie `pos` door string `s` [11](#page=11).
Meer informatie is te vinden op http://www.cplusplus.com/reference/string/string/ [11](#page=11).
> **Voorbeeld:** Het verwijderen van dubbele spaties en een leidende spatie uit een string.
```cpp
std::string s = " Een voorbeeld ";
int pos = s.find(" "); // zoekt naar twee spaties
while (pos != -1) {
s.replace(pos, 2, " "); // vervangt twee spaties door één
pos = s.find(" ");
}
if (s == ' ') { .
s = s.substr(1, s.size() - 1); // verwijdert leidende spatie
}
// Na de operaties kan s "Een voorbeeld" zijn.
```
### 1.2 Namespaces
Namespaces worden gebruikt om naamconflicten (collisions) te voorkomen in grote projecten of bij het gebruik van externe bibliotheken. Ze groeperen identifiers (zoals functies, klassen, variabelen) onder een specifieke naam. De standaardbibliotheek van C++ maakt hier uitgebreid gebruik van, bijvoorbeeld de `string` klasse bevindt zich in de `std` namespace [16](#page=16).
**Gebruik van `using`:**
- `using XXX::identifier;`: Hiermee kan de namespace-prefix `XXX::` worden weggelaten voor een specifieke identifier. Dit kan lokaal (bv. binnen `main`) of globaal na een include worden gebruikt [17](#page=17).
> **Voorbeeld:** `using A::f;` en `using std::string;` [17](#page=17).
- `using namespace XXX;`: Hiermee kunnen alle identifiers uit de namespace `XXX` zonder prefix worden gebruikt [17](#page=17).
> **Voorbeeld:** `using namespace std;` maakt het mogelijk om `string s;` en `cin >> s;` direct te gebruiken [17](#page=17).
> **Voorbeeld bestanden:** `nmspc.cpp` en `namespace_vb.cpp` demonstreren het gebruik van namespaces [17](#page=17).
### 1.3 References en call by reference
(Niet direct behandeld in de verstrekte paginering, maar een basisconcept in C++ gerelateerd aan het efficiënter doorgeven van parameters ) [3](#page=3).
### 1.4 Console invoer en uitvoer
(Niet direct behandeld in de verstrekte paginering, maar een basisconcept in C++ ) [3](#page=3).
### 1.5 Werken met bestanden
(Niet direct behandeld in de verstrekte paginering, maar een basisconcept in C++ ) [3](#page=3).
### 1.6 Functie-templates
Functie-templates maken het mogelijk om generieke functies te schrijven die met verschillende datatypes kunnen werken zonder de functie voor elk type apart te hoeven definiëren [3](#page=3).
### 1.7 Functies als parameter/argument
C++ staat toe dat functies als parameters of argumenten worden doorgegeven aan andere functies. Dit is een krachtig concept voor flexibele en modulaire programmacreatie [3](#page=3).
### 1.8 Dynamisch geheugenbeheer
Dit omvat het beheren van geheugen tijdens de uitvoering van het programma, typisch met `new` en `delete`, en wordt in C++ vaak efficiënter afgehandeld door bijvoorbeeld slimme pointers [3](#page=3).
---
# References en call by reference
References in C++ bieden een manier om variabelen efficiënter door te geven aan functies en onnodige kopieën van gegevens te vermijden. Dit wordt met name bereikt door het gebruik van "call by reference" [18](#page=18) [20](#page=20).
### 2.1 Declaratie en gebruik van references
Een reference is een alias voor een bestaande variabele [19](#page=19).
* **Declaratie:** Een reference wordt gedeclareerd met het `&` symbool na het datatype [19](#page=19).
* **Verplichte initialisatie:** References moeten direct bij declaratie worden geïnitialiseerd met een bestaande variabele. Dit is vergelijkbaar met een constante pointer [19](#page=19).
* **Gedrag:** Een reference bevat automatisch het adres van een andere variabele. Er moet **géén** `&` gebruikt worden vóór de variabele waarnaar de reference verwijst bij initialisatie. Bovendien wordt een reference automatisch gederefereerd, wat betekent dat er **géén** `*` gebruikt hoeft te worden bij het benaderen van de onderliggende variabele [19](#page=19).
#### 2.1.1 Voorbeelden van reference declaratie en gebruik
```cpp
int a; // Declareer een integer variabele 'a'
int &x = a; // Declareer een reference 'x' die verwijst naar 'a'. Géén '&' vóór 'a'.
x = 5; // Wijzigt de waarde van 'a' naar 5
x++; // Verhoogt de waarde van 'a' met 1. Géén '*' vóór 'x'.
printf("%d %d", a, x); // Output: 6 6
```
Pogingen om een reference te declareren zonder deze te initialiseren leiden tot een fout:
```cpp
int &y; // Fout: initialisatie ontbreekt
```
### 2.2 Call by reference
References worden frequent gebruikt als formele parameters in functies, een techniek die bekend staat als "call by reference". Dit maakt het mogelijk om de oorspronkelijke argumenten van de functie aan te passen binnen de functie zelf, zonder dat hiervoor pointers gebruikt hoeven te worden [20](#page=20).
#### 2.2.1 Voorbeeld: Functie om waarden te wisselen
```cpp
void wissel(int &x, int &y) {
int h = x;
x = y;
y = h;
}
int main() {
int a = 5, b = 6;
wissel(a, b); // Nu zijn de waarden van a en b verwisseld
// a zal 6 zijn, b zal 5 zijn
return 0;
}
```
#### 2.2.2 Oefening: Nulpunten van een kwadratische vergelijking
Schrijf een procedure `nulptn(a,b,c,aantal,w1,w2)` die de nulpunten van de vergelijking $ax^2 + bx + c = 0$ bepaalt, waarbij $a$, $b$, en $c$ reële getallen zijn. Het aantal nulpunten wordt opgeslagen in `aantal`, en de eventuele nulpunten in `w1` en `w2` [21](#page=21).
De berekening van de nulpunten van een kwadratische vergelijking $ax^2 + bx + c$:
Discriminant ($D$) $= b^2 - 4ac$
Nulpunten ($w_{1,2}$) $= \frac{-b \pm \sqrt{D}}{2a}$ [21](#page=21).
### 2.3 Het vermijden van kopieën met references
References zijn ook uitermate geschikt om het kopiëren van grote datastructuren te vermijden, wat de prestaties aanzienlijk kan verbeteren [22](#page=22).
#### 2.3.1 Voorbeeld 1: Itereren over een array van strings
Bij het doorlopen van een array met behulp van een range-based for-loop, kan het direct gebruiken van de loopvariabele een kopie van elk element maken. Dit is inefficiënt, vooral bij grote of complexe objecten [22](#page=22).
```cpp
string t[] = {"dit", "is", "een", "voorbeeld"};
// Minder efficiënt (maakt kopieën van strings)
for (string s : t) {
cout << s;
}
// Véél beter (gebruikt references, voorkomt kopieën)
for (string &s : t) {
cout << s;
}
// Nog beter: met 'const' als de elementen niet aangepast worden
for (const string &s : t) {
cout << s;
}
```
#### 2.3.2 Voorbeeld 2: Functie met complexe objecten
Bij functies die objecten zoals `punt` (punt) als argument hebben, kan het doorgeven van een kopie van het object veel tijd en geheugen kosten. References bieden hier een elegant alternatief [23](#page=23).
```cpp
struct punt {
double x, y;
};
// Minder goed: maakt een kopie van het punt object
double afstand(punt p1, punt p2) {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
// C-stijl met pointers (ook in C++ mogelijk)
double afstand_c(const punt *p1, const punt *p2) {
return sqrt((p1->x - p2->x) * (p1->x - p2->x) + (p1->y - p2->y) * (p1->y - p2->y));
}
// C++-stijl met const references (meest efficiënt en idiomatisch)
double afstand_cpp(const punt &p1, const punt &p2) {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
```
### 2.4 Vergelijking met C-stijl pointers
In onderstaande tabel wordt de parallel getrokken tussen het gebruik van pointers in C en references in C++ voor verschillende scenario's [24](#page=24).
| C | C++ | Gebruiksscenario |
| :-------------------- | :---------------------------------- | :------------------------------------------------------ |
| `type *t` (array) | `[const type *t` (array) | Arrays |
| `type *p` | `type &p` (invoer-/uitvoerparameter) | Invoer- en uitvoerparameters |
| `const type *p` | `const type &p` (kopie vermijden) | Vermijden van kopieën (bv. voor structs, strings) |
> **Tip:** Het gebruik van `const` references (`const type &p`) is de meest efficiënte en veilige manier om grote objecten aan functies door te geven wanneer deze objecten binnen de functie niet gewijzigd hoeven te worden. Dit voorkomt kopieën en garandeert dat de oorspronkelijke data intact blijft [22](#page=22) [23](#page=23) [24](#page=24).
---
# Console invoer en uitvoer
Deze sectie behandelt de basisprincipes van het werken met de console voor invoer en uitvoer in C++ met behulp van de `` bibliotheek, en introduceert de objecten `cin` en `cout` als vervanging voor oudere functies zoals `scanf` en `printf` [25](#page=25).
### 3.1 Console uitvoer met `cout`
De `cout` (console output) object, samen met de `<<` operator, wordt gebruikt om gegevens naar de console te schrijven. Deze operator is concateneerbaar, wat betekent dat meerdere items achter elkaar kunnen worden afgedrukt [26](#page=26).
#### 3.1.1 `cout` operanden
De rechteroperand van de `<<` operator kan verschillende types zijn:
* Variabelen of literals van elk type [26](#page=26).
* De `endl` manipulator, die een nieuwe regel afdrukt en de uitvoerbuffer leegt [26](#page=26).
* Manipulatoren zoals `oct`, `dec`, of `hex` om volgende operanden respectievelijk in octaal, decimaal of hexadecimaal formaat af te drukken. Deze instelling blijft van kracht totdat een andere manipulator wordt gebruikt [26](#page=26).
> **Voorbeeld:**
> ```cpp
> #include
>
> using namespace std;
>
> int main() {
> string s = "wereld";
> cout << "dag" << s << endl << oct << 125 << endl;
> return 0;
> }
> ```
> Dit zal "dagwereld" afdrukken, gevolgd door een nieuwe regel, vervolgens de octale representatie van 125 (wat 175 is in octaal), en nogmaals een nieuwe regel [26](#page=26).
### 3.2 Console invoer met `cin`
De `cin` (console input) object, samen met de `>>` operator, wordt gebruikt om invoer van de console te lezen. Net als `<<` is ook deze operator concateneerbaar. De rechteroperand moet een variabele van een willekeurig type zijn waarin de gelezen waarde kan worden opgeslagen [27](#page=27).
> **Voorbeeld:**
> ```cpp
> #include
>
> using namespace std;
>
> int main() {
> cout << "Geef twee getallen in : ";
> int g1, g2;
> cin >> g1 >> g2;
> cout << "getal1 dec = " << g1 << endl
> << "getal2 hex = " << hex << g2;
> return 0;
> }
> ```
> Dit programma vraagt de gebruiker om twee getallen in te voeren, slaat deze op in `g1` en `g2`, en toont `g1` in decimaal formaat en `g2` in hexadecimaal formaat [27](#page=27).
### 3.3 Controle van console invoer
Bij het lezen van invoer met `cin`, kan de invoer mislukken. Dit gebeurt wanneer het type van de invoer niet overeenkomt met het type van de variabele waar het naar wordt gelezen, of wanneer het einde van het bestand wordt bereikt [28](#page=28).
* **`cin.fail()`**: Deze functie retourneert `true` als de invoer is mislukt, wat resulteert in het instellen van de `failbit` van `cin` [28](#page=28).
* **`cin.clear()`**: Deze functie reset de error-bits van `cin`, waaronder de `failbit`, zodat verdere invoer mogelijk is [28](#page=28).
* **Leegmaken van de inputbuffer**: Na een mislukte invoer blijven de ongeldige karakters in de inputbuffer staan. Om te voorkomen dat deze karakters de volgende invoerpoging verstoren, moet de buffer worden geleegd. Dit kan gedaan worden door alle karakters te lezen tot aan het newline-karakter met behulp van `getchar()!= '\n'` [28](#page=28).
> **Voorbeeld van invoercontrole:**
> ```cpp
> #include
>
> using namespace std;
>
> int main() {
> cout << "Geef een positief geheel getal in: ";
> int g;
> cin >> g;
>
> while (cin.fail() || g < 0) {
> while (getchar() != '\n'); // inputbuffer leegmaken
> cin.clear(); // zet de failbit terug op false
> cout << "Opnieuw!! Geef een nieuw getal in: ";
> cin >> g;
> }
>
> cout << "getal = " << g << endl;
> return 0;
> }
> ```
> Dit programma blijft de gebruiker vragen om invoer totdat een positief geheel getal is ingevoerd [28](#page=28).
### 3.4 Inlezen van strings
Er zijn twee gangbare manieren om strings in te lezen:
* **Met `cin >> string_variabele;`**: Deze methode leest slechts één woord (alle karakters tot de volgende whitespace) en slaat de whitespace karakters over [29](#page=29).
> **Voorbeeld:**
> ```cpp
> #include
> #include
>
> using namespace std;
>
> int main() {
> string s;
> cout << "Voer een woord in: ";
> cin >> s;
> cout << "Ingelezen woord: " << s << endl;
> return 0;
> }
> ```
> Als de gebruiker "Hallo wereld" invoert, zal alleen "Hallo" worden ingelezen [29](#page=29).
* **Met `getline(cin, string_variabele);`**: Deze methode leest de rest van de huidige regel, inclusief spaties, tot aan het newline-karakter. Het newline-karakter zelf wordt niet bewaard in de string [30](#page=30).
> **Voorbeeld:**
> ```cpp
> #include
> #include
>
> using namespace std;
>
> int main() {
> string s;
> cout << "Voer een zin in: ";
> getline(cin, s);
> cout << "Ingelezen zin: " << s << endl;
> return 0;
> }
> ```
> Als de gebruiker "Hallo wereld" invoert, zal de volledige string "Hallo wereld" worden ingelezen [30](#page=30).
**Let op bij `getline` direct na `>>`:**
Als je eerst een getal leest met `cin >> getal;` en direct daarna een string met `getline(cin, s);`, kan `getline` de resterende newline van de vorige invoerregel lezen, waardoor de string leeg blijft. Om dit te voorkomen, moet de inputbuffer worden geleegd na de `>>` bewerking voordat `getline` wordt aangeroepen [30](#page=30).
> **Voorbeeld van correct gebruik van `getline` na `>>`:**
> ```cpp
> #include
> #include
>
> using namespace std;
>
> int main() {
> int getal;
> string s;
>
> cout << "Voer een getal in: ";
> cin >> getal;
>
> // Leeg de inputbuffer na het lezen van het getal
> cin.ignore(numeric_limits::max(), '\n');
>
> cout << "Voer een zin in: ";
> getline(cin, s);
>
> cout << "Getal: " << getal << endl;
> cout << "Zin: " << s << endl;
> return 0;
> }
> ```
> Hierbij is `cin.ignore()` essentieel om de resterende newline na het invoeren van het getal te consumeren [30](#page=30).
### 3.5 Inlezen van karakters (`char`)
Voor het inlezen van individuele karakters zijn er twee hoofdmethoden:
* **Met `cin >> char_variabele;`**: Deze methode leest een enkel karakter en slaat whitespace karakters over. Het is dus niet geschikt om spaties of andere witruimtetekens als karakters in te lezen [31](#page=31).
> **Voorbeeld:**
> ```cpp
> #include
>
> using namespace std;
>
> int main() {
> char a;
> cout << "Voer een karakter in: ";
> cin >> a;
> cout << "Ingelezen karakter: " << a << endl;
> return 0;
> }
> ```
> Als de gebruiker " a" invoert, zal 'a' worden ingelezen, en de spatie wordt overgeslagen [31](#page=31).
* **Met `cin.get()` of `getchar()`**: Deze methoden lezen een enkel karakter, inclusief whitespace karakters. `cin.get()` is een methode van het `cin` object, terwijl `getchar()` een C-stijl functie is die ook werkt met `iostream` [31](#page=31).
> **Voorbeeld:**
> ```cpp
> #include
>
> using namespace std;
>
> int main() {
> char a;
> cout << "Voer een karakter in: ";
> a = cin.get(); // of a = getchar();
> cout << "Ingelezen karakter: " << a << endl;
> return 0;
> }
> ```
> Als de gebruiker " a" invoert, zal `cin.get()` de spatie als eerste karakter inlezen [31](#page=31).
> **Tip:** Wees je bewust van het verschil tussen `cin >> char` en `cin.get()`. Gebruik `cin.get()` wanneer je expliciet whitespace karakters wilt kunnen inlezen, zoals bij het verwerken van invoerregel voor invoerregel of karakter voor karakter zonder invoer te negeren [31](#page=31).
---
# Werken met bestanden
Dit hoofdstuk behandelt de basisprincipes van het lezen uit en schrijven naar sequentiële tekstbestanden met behulp van de `fstream` bibliotheek in C++ [33](#page=33).
### 4.1 Introductie tot sequentiële tekstbestanden
Sequentiële tekstbestanden worden gelezen en geschreven van begin tot eind. Voor willekeurige toegang tot bestanden kunnen functies zoals `seekg` en `seekp` worden gebruikt, maar deze vallen buiten het bestek van deze sectie [33](#page=33).
* **Invoerbestand**: Een bestand waaruit gegevens worden gelezen [33](#page=33).
* **Uitvoerbestand**: Een bestand waarnaar gegevens worden geschreven [33](#page=33).
### 4.2 Declaratie en opening van bestanden
Om met bestanden te werken, moet de `fstream` bibliotheek worden opgenomen en de `std` namespace worden gebruikt [34](#page=34).
#### 4.2.1 Declareren van bestandsstromen
* `ifstream inv;`: Declareert een invoerstroomobject voor invoerbestanden [34](#page=34).
* `ofstream uitv1, uitv2;`: Declareert uitvoerstroomobjecten voor uitvoerbestanden [34](#page=34).
#### 4.2.2 De `open` functie
De `open` functie wordt gebruikt om een bestand te koppelen aan een stroomobject [35](#page=35).
* **Syntax**:
* `void open(const char *fname [, openmode mode]);`
* `void open(const string &fname [, openmode mode]);`
* **`openmode` opties**:
* `ios::in`: Bestand openen voor invoer. Het bestand moet bestaan [35](#page=35).
* `ios::out`: Bestand openen voor uitvoer. Het bestand wordt aangemaakt of gewist [35](#page=35).
* `ios::app`: Bestand openen voor uitvoer en de schrijfpositie aan het einde plaatsen voor elke schrijfoperatie. Het bestand wordt aangemaakt of geopend [35](#page=35).
* **Default `openmode`**:
* Voor `ifstream`: `ios::in` [35](#page=35).
* Voor `ofstream`: `ios::out` [35](#page=35).
#### 4.2.3 Controle na openen
Het is cruciaal om te controleren of het openen van een invoerbestand succesvol was, aangezien het bestand mogelijk niet bestaat [36](#page=36).
* **Controlemethoden**:
* `if (inv.is_open()) {... }` [36](#page=36).
* `if (!inv.fail()) {... }` [36](#page=36).
* `if (inv) {... }` [36](#page=36).
#### 4.2.4 Voorbeelden van openen
```cpp
#include
#include
using namespace std;
int main() {
ifstream inv;
inv.open("b1.txt"); // Bestand openen voor invoer
if (inv.is_open()) {
cout << "openen gelukt";
}
ofstream uit1, uit2;
uit1.open("c:\\b2.txt"); // Bestand openen voor uitvoer, mogelijk gewist
string s = "b3.txt";
uit2.open(s, ios::app); // Bestand openen voor uitvoer, toevoegen aan het einde
// Vóór C++11 was dit nodig voor string objecten:
// uit2.open(s.c_str(), ios::app);
return 0;
}
```
#### 4.2.5 Initialisatie bij declaratie
Bestandsstromen kunnen ook direct bij declaratie worden geïnitialiseerd [38](#page=38).
```cpp
ifstream inv("b1.txt"); // Bestand direct openen voor invoer
if (inv) {
cout << "openen gelukt";
}
ofstream uit1("c:\\b2.txt"); // Bestand direct openen voor uitvoer
ofstream uit2(s, ios::app); // Bestand direct openen voor uitvoer met app mode
```
> **Tip:** De opdracht `ifstream inv.open("b1.txt");` is fout omdat `open` een methode is en niet gebruikt kan worden voor directe initialisatie op deze manier [38](#page=38).
### 4.3 Lezen en schrijven naar bestanden
Gegevens kunnen worden gelezen uit en geschreven naar bestanden met behulp van de invoeg- en extractieoperatoren, vergelijkbaar met console i/o [39](#page=39).
* **Lezen**:
* `inv >> variabele;`: Leest gestructureerde gegevens (bv. getallen, woorden) [39](#page=39).
* `ch = inv.get();`: Leest één enkel teken [39](#page=39).
* `getline(inv, lijn);`: Leest een hele regel tot een newline-teken [39](#page=39).
* **Schrijven**:
* `uitv1 << variabele << " " <<... << endl;`: Schrijft gestructureerde gegevens en voegt optioneel een nieuwe regel toe [39](#page=39).
### 4.4 Sluiten van bestanden
Het is belangrijk om bestanden na gebruik te sluiten om gegevensconsistentie te waarborgen en bronnen vrij te geven [40](#page=40).
* **De `close` methode**:
* `inv.close(); uitv1.close(); uitv2.close();` [40](#page=40).
* **Automatisch sluiten**: Wanneer een `fstream` object buiten scope gaat, wordt het bijbehorende bestand automatisch gesloten [40](#page=40).
* **Heropenen van bestanden**: Een reeds geopend `fstream` object kan niet opnieuw worden geopend totdat het eerst is gesloten, anders wordt de `failbit` ingesteld [40](#page=40).
```cpp
// Voorbeeld van meerdere keren openen en sluiten
ifstream inv;
for (const string &s : t) {
inv.open(s);
// Inlezen van gegevens...
inv.close(); // Bestand sluiten voor het openen van het volgende
}
```
### 4.5 Testen op het einde van een invoerbestand
Het correct verwerken van het einde van een bestand is essentieel om fouten of onvolledige gegevens te voorkomen [41](#page=41).
* **Testen met `fail()` of het stroomobject zelf**:
```cpp
// Methode 1: met expliciete leesoperaties binnen en buiten de loop
//... lees iets in uit het bestand [1](#page=1).
while (!inv.fail()) { // of: while (inv) {
// ... doe iets met wat je ingelezen hebt
//... lees iets in uit het bestand [2](#page=2).
}
```
Na de loop:
* `if (inv.eof()) {... }`: Controleert of het einde van het bestand is bereikt. Dit is de verwachte situatie bij succesvolle volledige inlezing [41](#page=41).
* `else {... }`: Geeft aan dat de loop is beëindigd door een fout in de gegevens, niet door het einde van het bestand [41](#page=41).
* **Vereenvoudigde loop**:
```cpp
// Methode 2: leesoperatie direct in de while-conditie
while (inv >> variabele || getline(inv, s)) {
// Verwerk ingelezen data
}
```
### 4.6 Voorbeeld: Tellen van positieve getallen in een bestand
Deze sectie introduceert een voorbeeld van een functie die een bestand met gehele getallen leest en het aantal strikt positieve getallen telt [42](#page=42).
* **Gevraagd**: Schrijf een functie `aantalpositief(s)` die een bestandsnaam `s` ontvangt, het aantal strikt positieve gehele getallen in dat bestand bepaalt, en -1 retourneert bij bestandsfouten of niet-bestaand bestand [42](#page=42).
### 4.7 Belangrijke opmerkingen over streams
* **Kopiëren van streams is niet toegestaan**: `fstream` objecten mogen niet direct worden gekopieerd. Dit kan leiden tot compileerfouten [43](#page=43).
```cpp
// Dit veroorzaakt een compileerfout:
// void lees_en_schrijf_getal(ifstream &inv) {
// int getal;
// inv >> getal;
// cout << getal;
// }
//
// int main() {
// ifstream inv("test.txt");
// lees_en_schrijf_getal(inv); // Poging tot kopiëren van stream
// lees_en_schrijf_getal(inv); // Nogmaals
// }
```
* **Gebruik van referenties**: Om streams door te geven aan functies zonder ze te kopiëren, moeten referenties (`&`) worden gebruikt [43](#page=43).
---
# Functie-templates en functies als parameter/argument
Dit onderdeel behandelt de mechanismen in C++ die generieke programmering mogelijk maken door middel van functie-templates, en hoe functies zelf als argumenten aan andere functies kunnen worden doorgegeven, met speciale aandacht voor lambda-functies.
### 5.1 Functie-templates
Functie-templates maken het mogelijk om generieke functies te schrijven die voor verschillende datatypes kunnen opereren zonder dat de code herhaald hoeft te worden. Dit is een krachtige techniek voor generieke programmering [45](#page=45).
#### 5.1.1 Declaratie en definitie
Een functie-template wordt gedefinieerd door een `template`-prefix te plaatsen gevolgd door ``, waarbij `T` een placeholder is voor een type. Deze `T` wordt vervolgens gebruikt in de implementatie van de functie [46](#page=46).
```cpp
template
void wissel( T & a, T & b) {
T hulp = a;
a = b;
b = hulp;
}
```
* **`template `**: Dit is de `template`-prefix [46](#page=46).
* **`T`**: De typeparameter die het specifieke datatype vertegenwoordigt. Er mogen ook meerdere typeparameters zijn, bijvoorbeeld `template ` [46](#page=46) [47](#page=47).
* **Gebruik van `T`**: De typeparameter `T` kan worden gebruikt voor variabelen, parameters en return-types binnen de template-functie [46](#page=46).
#### 5.1.2 Belangrijke opmerkingen over functie-templates
* **Prefix bij declaratie en definitie**: De `template`-prefix moet aanwezig zijn bij zowel de declaratie als de definitie van een template-functie [47](#page=47).
* **Type-substitutie beperkingen**: Alleen types die alle operatoren en functies ondersteunen die op `T` worden aangeroepen in de template-implementatie, mogen in `T` worden gesubstitueerd. Anders kan dit leiden tot crashes tijdens runtime [46](#page=46).
* **Naam van typeparameter**: Hoewel `T` vaak als naam wordt gebruikt, mag elke geldige naam worden gekozen voor een typeparameter [46](#page=46).
* **Combinatie met "echte" types**: Typeparameters zoals `T` mogen worden gecombineerd met reguliere, concrete types in functie-declaraties [47](#page=47).
```cpp
template
T1 functie_naam(int i, const T1 &t1, const T2 &t2);
```
* **Overloading**: Functies met templates kunnen worden geoverload door functies met alleen "echte" types [47](#page=47).
* **C++20 Concepts**: Vanaf C++20 kunnen *concepts* worden gebruikt om restricties op te leggen aan templates, wat de compiler helpt bij het controleren van de gebruikte types [47](#page=47).
### 5.2 Functies als parameter/argument
In C++ kunnen functies zelf als argumenten worden doorgegeven aan andere functies. Dit opent mogelijkheden voor flexibele en abstracte code [49](#page=49).
#### 5.2.1 Technieken voor het doorgeven van functies
* **In C**: Dit gebeurt met behulp van functie-pointers [49](#page=49).
* **Vóór C++11**: Gebruik van templates was mogelijk, maar wordt tegenwoordig afgeraden [49](#page=49).
* **Vanaf C++11**: De voorkeursmethode is het gebruik van `std::function` uit de `` header [49](#page=49).
#### 5.2.2 Gebruik van `std::function`
`std::function` is een type-veilige wrapper voor elk aanroepbaar object (functies, lambda-expressies, functie-objecten). De signatuur van de te verwachten functie wordt gespecificeerd bij het declareren van `std::function`.
```cpp
#include
using namespace std;
// Functie die een string array doorzoekt op basis van een predicate
bool zoek(const string *t, int n, function func);
```
* **`function func`**: Hier wordt een `std::function` met de naam `func` gedeclareerd. Deze functie verwacht een argument van type `const string &` en retourneert een `bool` [49](#page=49).
* **`const... &func`**: `std::function` kan ook by reference worden meegegeven, wat vaak efficiënter is. Het gebruik van `const` is aan te raden, zeker bij lambda-functies [49](#page=49).
#### 5.2.3 Template-gebaseerde aanpak (vóór C++11)
Hoewel minder gebruikelijk en afgeraden sinds C++11, kan een template ook worden gebruikt om een functie als argument te accepteren.
```cpp
template
bool zoek(const string *t, int n, Func func);
```
Deze aanpak is veelzijdiger omdat de compiler het type van `func` kan afleiden tijdens het aanroepen, maar de `std::function` methode biedt meer duidelijkheid over de verwachte functie-signatuur [50](#page=50).
#### 5.2.4 Het doorgeven van functies als argument in de praktijk
Een functie die een andere functie als argument aanneemt, kan deze interne functie aanroepen om specifieke logica uit te voeren.
```cpp
// Voorbeeld van de implementatie van zoek met std::function
bool zoek(const string *t, int n, function func ) {
for (int i = 0; i < n; i++) {
if (func(t[i])) { // De meegegeven functie wordt aangeroepen
return true;
}
}
return false;
}
```
#### 5.2.5 Lambda-functies
Lambda-functies zijn anonieme functies die lokaal kunnen worden gedefinieerd, direct op de plaats waar ze worden aangeroepen als argument. Dit maakt de code vaak beknopter en leesbaarder [52](#page=52).
##### 5.2.5.1 Syntaxis van lambda-functies
De algemene syntaxis is:
`[ captures (parameterlijst) -> returntype { statements }` [52](#page=52).
* **`[ captures ]`**: De capture-lijst bepaalt welke variabelen uit de omringende scope beschikbaar zijn binnen de lambda. Dit deel is verplicht, ook als het leeg is `[]` [52](#page=52).
* **`(parameterlijst)`**: De lijst met parameters die de lambda accepteert [52](#page=52).
* **`-> returntype`**: Het return-type. Dit kan vaak worden weggelaten omdat de compiler het kan afleiden uit de `statements` [52](#page=52).
* **`{ statements }`**: Het lichaam van de lambda-functie [52](#page=52).
**Voorbeeld van een lambda-functie:**
```cpp
if (zoek(tab, 10, [] (const string& s) { return s.find("x") != -1; })) {
// ...
}
```
In dit voorbeeld is `[] (const string& s) { return s.find("x")!= -1; }` een lambda-functie die geen variabelen capturet, een `const string&` als parameter accepteert, en `true` retourneert als de string de letter "x" bevat [52](#page=52).
##### 5.2.5.2 De capture-lijst
De capture-lijst bepaalt hoe variabelen van buiten de lambda-functie in de lambda beschikbaar worden gemaakt:
* **`[]`**: Capture nothing. De lambda kent alleen de meegegeven parameters [53](#page=53).
* **`[a, &b]`**: `a` wordt "by value" gecaptureerd (een kopie, alleen-lezen), `b` wordt "by reference" gecaptureerd (een referentie, kan worden gewijzigd) [53](#page=53).
* **`[&]`**: Capture all variables in the surrounding scope by reference [53](#page=53).
* **`[&, b]`**: Capture all variables by reference, except for `b` which is captured by value [53](#page=53).
* **`[=]`**: Capture all variables in the surrounding scope by value [53](#page=53).
* **`[=, &b]`**: Capture all variables by value, except for `b` which is captured by reference [53](#page=53).
**Voorbeeld met capture-lijst:**
```cpp
// vb_lambda2.cpp
int main() {
string mail[] = {"HN@ugent.be", "test@example.com"};
char letter = 's';
// Lambda captureert 'letter' by value en zoekt naar die letter
if (zoek(mail, 2, [letter](const string &s) {
return s.find(letter) != -1;
})) {
cout << "Adres gevonden met letter " << letter;
}
return 0;
}
```
Hier wordt de variabele `letter` van buiten de lambda `[letter]` doorgegeven als een kopie, en de lambda gebruikt deze kopie om te controleren of de string de letter bevat [54](#page=54).
---
# Dynamisch geheugenbeheer
Dit onderdeel behandelt de mechanismen voor dynamische geheugentoewijzing en -deallocatie in C++ met behulp van de `new` en `delete` operatoren [56](#page=56).
### 6.1 Introductie tot dynamisch geheugenbeheer
Dynamisch geheugenbeheer stelt programmeurs in staat om geheugen toe te wijzen en vrij te geven tijdens de uitvoering van een programma, in tegenstelling tot statisch geheugen dat bij compilatie wordt toegewezen. Dit is cruciaal voor situaties waarin de hoeveelheid benodigd geheugen niet van tevoren bekend is of kan variëren [56](#page=56).
### 6.2 Het `new` operator
De `new` operator wordt gebruikt om geheugen dynamisch toe te wijzen. Het retourneert een pointer naar het begin van het toegewezen geheugenblok [56](#page=56).
#### 6.2.1 Toewijzen van enkelvoudige variabelen
Voor het toewijzen van geheugen voor een enkelvoudige variabele van een bepaald type, wordt de volgende syntax gebruikt:
`new type;` [56](#page=56).
Een pointer van het overeenkomstige type moet deze toegewezen geheugenlocatie aanwijzen [56](#page=56).
**Voorbeeld:**
```cpp
int *a = new int; // Wijst geheugen toe voor een enkele integer
```
#### 6.2.2 Toewijzen van arrays
Voor het toewijzen van geheugen voor een array van elementen van een bepaald type, wordt de volgende syntax gebruikt:
`new type[grootte;` [56](#page=56).
Hierbij is `grootte` het aantal elementen dat in de array moet worden opgeslagen [56](#page=56).
**Voorbeeld:**
```cpp
int n = 10;
int *r = new int[n; // Wijst geheugen toe voor een array van 10 integers
```
### 6.3 De `delete` operator
De `delete` operator wordt gebruikt om dynamisch toegewezen geheugen weer vrij te geven. Het is essentieel om geheugen dat met `new` is toegewezen, ook met `delete` vrij te geven om geheugenlekken te voorkomen [56](#page=56).
#### 6.3.1 Vrijgeven van enkelvoudige variabelen
Om geheugen vrij te geven dat is toegewezen voor een enkelvoudige variabele, wordt de `delete` operator gevolgd door de pointer die naar dat geheugen wijst:
`delete naam;` [56](#page=56).
Hierbij is `naam` de pointer naar het te deallokeren geheugen [56](#page=56).
**Voorbeeld:**
```cpp
delete a; // Geeft het geheugen vrij waar 'a' naar wijst
```
#### 6.3.2 Vrijgeven van arrays
Wanneer geheugen is toegewezen voor een array met behulp van `new type[grootte]`, moet het worden vrijgegeven met de `delete[]` operator:
`delete[] naam;` [56](#page=56).
Dit zorgt ervoor dat alle elementen van de array correct worden gedeallokeerd [56](#page=56).
**Voorbeeld:**
```cpp
delete[] r; // Geeft het geheugen vrij waar 'r' naar wijst (array)
```
> **Tip:** Gebruik in C++ bij voorkeur alleen `new` en `delete` voor dynamisch geheugenbeheer en vermijd oudere C-functies zoals `malloc`, `calloc` en `free`. Dit bevordert typeveiligheid en zorgt voor een correcte afhandeling van constructors en destructors voor objecten die dynamisch worden toegewezen [56](#page=56).
### 6.4 Belangrijke overwegingen
Het correct balanceren van `new` en `delete` operaties is cruciaal. Elke `new` aanroep moet een corresponderende `delete` aanroep hebben, en elke `new[]` aanroep moet een corresponderende `delete[]` aanroep hebben. Het vrijgeven van geheugen dat al is vrijgegeven (double deletion) of het niet vrijgeven van toegewezen geheugen (memory leak) kan leiden tot programma-crashes of onvoorspelbaar gedrag [56](#page=56).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| datatype bool | Een fundamenteel datatype in C++ dat logische waarden kan aannemen, namelijk `true` (waar) of `false` (onwaar). Deze waarden kunnen automatisch worden geconverteerd naar integers, waarbij `true` 1 wordt en `false` 0, en omgekeerd waar 0 `false` wordt en elke andere waarde `true`. |
| standaardstring (klasse string) | Een klasse in C++ die wordt gebruikt voor het manipuleren van reeksen tekens. Het biedt functies voor concatenatie, vergelijking, het bepalen van de lengte en het ontleden van strings, en vereist de `#include ` header. |
| OGP | Dit staat voor Object Georiënteerd Programmeren, een programmeerparadigma dat gebruik maakt van objecten, die data en methoden combineren. C++ ondersteunt OGP met klassen en objecten. |
| overloading (functies en operatoren) | De mogelijkheid om meerdere functies of operatoren te definiëren met dezelfde naam maar met verschillende parameterlijsten (functies) of datatypen (operatoren). Dit zorgt voor flexibiliteit en leesbaarheid in de code. |
| default parameters | Een functieparameter waarbij een standaardwaarde wordt toegekend. Als de parameter niet expliciet wordt meegegeven tijdens de functieaanroep, wordt de standaardwaarde gebruikt. Default parameters moeten altijd aan het einde van de parameterlijst worden geplaatst. |
| templates | Een C++ feature die generieke programmering mogelijk maakt. Functie-templates en klasse-templates kunnen worden gedefinieerd om te werken met verschillende datatypes zonder dat de code herhaald hoeft te worden voor elk type. |
| exception handling | Een mechanisme in C++ om fouten of uitzonderlijke situaties die tijdens de uitvoering van een programma optreden, af te handelen. Het maakt gebruik van `try`, `catch` en `throw` om fouten te detecteren en erop te reageren. |
| Namespaces | Een mechanisme in C++ dat wordt gebruikt om naamconflicten (collisions) in de code te voorkomen, met name wanneer code uit verschillende bronnen wordt gecombineerd. De standaardbibliotheek bevindt zich bijvoorbeeld in de `std` namespace. |
| References (References) | Een alias voor een bestaande variabele. Een reference moet worden geïnitialiseerd bij declaratie en wordt gebruikt om direct toegang te krijgen tot de oorspronkelijke variabele, zonder de noodzaak van dereferencing zoals bij pointers. |
| call by reference | Een methode om parameters aan functies door te geven waarbij de functie werkt met een referentie naar de oorspronkelijke variabele in plaats van een kopie. Dit maakt het mogelijk om de waarden van de originele variabelen binnen de functie te wijzigen. |
| Console invoer en uitvoer | Het proces van het lezen van gegevens van de gebruiker via de console (invoer, typisch met `cin`) en het weergeven van resultaten of informatie op de console (uitvoer, typisch met `cout`). De `iostream` bibliotheek wordt hiervoor gebruikt. |
| Werken met bestanden | Het proces van het lezen van gegevens uit of schrijven van gegevens naar externe bestanden. In C++ wordt dit meestal gedaan met behulp van de `fstream` bibliotheek, die klassen biedt zoals `ifstream` (invoer) en `ofstream` (uitvoer). |
| Functie-templates | Een template die een blauwdruk definieert voor het genereren van functies. De compiler kan op basis van de gebruikte datatypes tijdens de aanroep van de functie, specifieke functies genereren die met die datatypes werken. |
| Functies als parameter/argument | De mogelijkheid om een functie door te geven als argument aan een andere functie. Dit wordt vaak gebruikt in C++ met behulp van `std::function` of templates, en maakt krachtige programmeerpatronen mogelijk zoals callbacks en hogere-orde functies. |
| Dynamisch geheugenbeheer | Het proces van het toewijzen en vrijgeven van geheugen tijdens de uitvoering van een programma. In C++ gebeurt dit met de `new` operator voor allocatie en de `delete` operator voor deallocatie van geheugen. |
| `std::string::npos` | Een speciale constante van het type `size_t` in C++, gedefinieerd binnen de `std::string` klasse. Het wordt geretourneerd door zoekfuncties zoals `find` wanneer het gezochte element niet in de string wordt gevonden. |
| `getline` | Een functie uit de `iostream` bibliotheek die wordt gebruikt om een volledige regel tekst, inclusief spaties, van een invoerstroom (zoals `cin` of een `ifstream`) te lezen tot aan het newline-karakter. |
| `ios::in` | Een openmode vlag die wordt gebruikt bij het openen van een bestand voor invoer met `ifstream`. Dit specificeert dat het bestand geopend wordt om uit te lezen. |
| `ios::out` | Een openmode vlag die wordt gebruikt bij het openen van een bestand voor uitvoer met `ofstream`. Dit specificeert dat het bestand geopend wordt om naar te schrijven. Indien het bestand reeds bestaat, wordt de inhoud ervan gewist. |
| `ios::app` | Een openmode vlag die wordt gebruikt bij het openen van een bestand voor uitvoer met `ofstream`. Data wordt aan het einde van het bestand toegevoegd, in plaats van dat het bestaande bestand wordt overschreven. |
| `ifstream` | Een klasse uit de `fstream` bibliotheek die wordt gebruikt om te lezen uit tekstbestanden. Het biedt methoden om gegevens uit een bestand te halen. |
| `ofstream` | Een klasse uit de `fstream` bibliotheek die wordt gebruikt om te schrijven naar tekstbestanden. Het biedt methoden om gegevens naar een bestand te schrijven. |
| `std::function` | Een klasse uit de `` header die wordt gebruikt om functies, lambda-expressies en andere aanroepbare objecten op te slaan. Het is een flexibele manier om functies als parameters door te geven. |
| Lambda-functies | Anonieme, lokale functies die direct op de plaats van aanroep kunnen worden gedefinieerd. Ze worden gekenmerkt door de `[]` syntax en kunnen variabelen uit hun omgeving "capteren". |
| `new` | Een C++ operator die wordt gebruikt voor dynamische geheugenallocatie op de heap. Het reserveert geheugen voor een object van een gespecificeerd type en retourneert een pointer naar dat geheugen. |
| `delete` | Een C++ operator die wordt gebruikt om geheugen vrij te geven dat eerder is toegewezen met de `new` operator. Het voorkomt geheugenlekken door ongebruikt geheugen terug te geven aan het systeem. |
Cover
H2_SmartPointers.pdf
Summary
# nullptr en het gebruik ervan
Dit gedeelte introduceert nullptr als een type-veilige vervanging voor NULL en 0 bij het representeren van een nullpointer.
### 1.1 Introductie van nullptr
`nullptr` is een sleutelwoord dat een nullpointer constante voorstelt. Het is ontworpen om `NULL` en `0` te vervangen en is sterk getypeerd, wat betekent dat het specifiek bedoeld is voor gebruik met pointers. Vanaf nu wordt het gebruik van `nullptr` bij pointers verplicht gesteld [3](#page=3).
### 1.2 Voordelen van nullptr ten opzichte van NULL en 0
Het belangrijkste voordeel van `nullptr` is dat het sterk getypeerd is. Dit voorkomt ambiguïteit in situaties waar overbelasting van functies (function overloading) wordt gebruikt [3](#page=3).
> **Voorbeeld:** Overweeg de volgende functie declaraties:
> ```cpp
> void proc(int); //A
> void proc(int *); //B
> ```
> Als je `proc ` aanroept, zal de compiler `proc(int)` (A) kiezen omdat 0 kan worden geïnterpreteerd als een integer. Echter, als je `proc(nullptr)` aanroept, zal de compiler correct de `proc(int *)` (B) functie kiezen, omdat `nullptr` expliciet een pointer aangeeft. Dit leidt tot een fout of waarschuwing bij het gebruik van `NULL` of `0` in dergelijke contexten, terwijl `nullptr` correct wordt afgehandeld [3](#page=3).
### 1.3 Verplicht gebruik van nullptr
Het gebruik van `nullptr` is verplicht bij pointers vanaf C++11. Dit zorgt voor duidelijkere en veiligere code door het vermijden van potentiële type-mismatches en ambigue aanroepen van functies [3](#page=3).
---
# Algemene introductie tot smart pointers
Smart pointers zijn pointer-wrapperklassen die, naast de functionaliteit van een traditionele pointer, extra eigenschappen bieden, met name op het gebied van automatisch geheugenbeheer. Ze lossen de noodzaak van expliciete `new` en `delete` operaties op, wat traditionele raw pointers vereist en vaak leidt tot geheugenlekken of crashes. Hoewel ze iets minder efficiënt kunnen zijn dan raw pointers, bieden ze aanzienlijke voordelen in termen van geheugenveiligheid en codeonderhoudbaarheid. De C++ standaardbibliotheek biedt drie primaire smart pointer-klassen: `unique_ptr`, `shared_ptr` en `weak_ptr` [5](#page=5).
### 2.1 Wat zijn smart pointers?
Een smart pointer is in essentie een klasse die fungeert als een wrapper rond een raw pointer. Het voornaamste doel van deze wrapper is om het beheer van het geheugen waar de pointer naar verwijst te automatiseren. Dit omvat het automatisch toewijzen (reserveren) en vrijgeven van geheugen, wat een cruciaal aspect is in talen zoals C++ waar geheugenbeheer handmatig gebeurt [5](#page=5).
### 2.2 Rol in geheugenbeheer
Het handmatig beheren van geheugen met `new` en `delete` is een veelvoorkomende bron van fouten. Als een ontwikkelaar vergeet om geheugen vrij te geven na gebruik, ontstaat er een geheugenlek, wat leidt tot een toenemend geheugengebruik en uiteindelijk kan resulteren in instabiliteit van het programma. Omgekeerd, als geheugen te vroeg wordt vrijgegeven terwijl er nog pointers naar verwijzen, kan dit leiden tot *dangling pointers* en *undefined behavior*, wat eveneens tot crashes kan leiden. Smart pointers automatiseren dit proces, waardoor de kans op dergelijke fouten drastisch vermindert [5](#page=5).
### 2.3 Vergelijking met traditionele raw pointers
Traditionele raw pointers vereisen dat de programmeur expliciet verantwoordelijkheid neemt voor het toewijzen en vrijgeven van geheugen. Dit gebeurt met de `new` operator voor toewijzing en de `delete` operator (of `delete[]` voor arrays) voor vrijgave. Hoewel dit maximale controle biedt, is het ook foutgevoelig [5](#page=5).
Smart pointers bieden een veiliger alternatief. De in de documentatie genoemde klassen zijn:
* `unique_ptr`: Biedt exclusief eigendom over het beheerde geheugen. Slechts één `unique_ptr` kan tegelijkertijd naar een specifiek object verwijzen [5](#page=5).
* `shared_ptr`: Maakt gedeeld eigendom mogelijk. Meerdere `shared_ptr`s kunnen naar hetzelfde object verwijzen, en het geheugen wordt vrijgegeven wanneer de laatste `shared_ptr` die ernaar verwijst wordt vernietigd [5](#page=5).
* `weak_ptr`: Een complementaire smart pointer die een niet-eigenaarschapreferentie naar een object beheerd door een `shared_ptr` biedt. Het voorkomt *cyclic references* (wederzijdse verwijzingen) die zouden kunnen leiden tot geheugenlekken bij het gebruik van `shared_ptr` [5](#page=5).
Het gebruik van smart pointers elimineert de noodzaak voor handmatige `new` en `delete` aanroepen, wat leidt tot robuustere en onderhoudsvriendelijkere code [5](#page=5).
> **Tip:** Hoewel smart pointers het geheugenbeheer automatiseren, is het nog steeds belangrijk om te begrijpen hoe ze werken om te voorkomen dat je ze op de verkeerde manier gebruikt, vooral in complexe scenario's met gedeeld eigendom [5](#page=5).
---
# De unique_ptr
Dit gedeelte behandelt de `unique_ptr`, die een exclusief eigenaarschap van een beheerd object garandeert.
### 3.1 Eigenschappen van uniek eigenaarschap
Een `unique_ptr` wijst naar een object en garandeert dat er slechts één `unique_ptr` op elk moment eigenaar is van dat object. Dit unieke eigenaarschap impliceert dat `unique_ptr`-objecten niet gekopieerd kunnen worden. Wel zijn operaties zoals *move* en *swap* mogelijk [7](#page=7).
### 3.2 Maken en initialiseren van unique_ptrs
* Een `unique_ptr` kan geïnitialiseerd worden als `nullptr` [7](#page=7).
* De functie `make_unique(args...)` wordt gebruikt om een nieuw object van type `T` te creëren en een `unique_ptr` te retourneren die eigenaar wordt van dit object. De constructorargumenten voor `T` worden meegegeven aan `make_unique` [7](#page=7) [8](#page=8).
> **Voorbeeld:** `unique_ptr p1 = make_unique();` initialiseert `p1` om te wijzen naar een integer met de defaultwaarde. `unique_ptr p2 = make_unique;` initialiseert `p2` om te wijzen naar een integer met de waarde 20 [7](#page=7).
* `unique_ptr` kan ook gebruikt worden om te wijzen naar objecten van zelfgemaakte klassen [8](#page=8).
> **Voorbeeld:** `unique_ptr p1 = make_unique(r);` creëert een `unique_ptr` die eigenaar wordt van een kopie van het `rechthoek`-object `r`. `unique_ptr p2 = make_unique(6,3);` creëert een `unique_ptr` die eigenaar wordt van een nieuw `rechthoek`-object met dimensies 6 en 3 [8](#page=8).
### 3.3 Gebruik van dereferentie en member access operatoren
* Met de dereferentie-operator `*` kan de waarde van het object waar de `unique_ptr` naar wijst, worden benaderd [7](#page=7).
* Met de pijloperator `->` kunnen leden (methoden of variabelen) van het object waar de `unique_ptr` naar wijst, worden aangeroepen of benaderd [8](#page=8).
> **Voorbeeld:** `*p1 = 101;` wijst de waarde 101 toe aan het integer-object waar `p1` naar verwijst. `p1->print();` roept de `print()`-methode aan op het `rechthoek`-object waar `p1` naar verwijst [7](#page=7) [8](#page=8).
### 3.4 Move-semantiek en eigendomsoverdracht
* De *move*-operatie transfereert het eigenaarschap van het geheugen van de ene `unique_ptr` naar de andere. Na een move-operatie wordt de oorspronkelijke `unique_ptr` `nullptr` [7](#page=7).
> **Voorbeeld:** `p2 = move(p1);` maakt `p2` de nieuwe eigenaar van het geheugenobject waar `p1` naar wees. `p1` wordt na deze operatie `nullptr` [7](#page=7).
### 3.5 Geheugen vrijgeven
* Het bewaakte geheugen wordt automatisch vrijgegeven wanneer de `unique_ptr` buiten scope gaat [7](#page=7).
* De `reset()`-methode kan expliciet worden aangeroepen om het geheugen onmiddellijk vrij te geven. Na het aanroepen van `reset()` wordt de `unique_ptr` `nullptr` [7](#page=7).
> **Voorbeeld:** `p2.reset();` geeft het geheugen vrij waar `p2` naar wees, en `p2` wordt `nullptr` [7](#page=7).
### 3.6 Swap-operatie
* De `swap()`-methode wisselt de pointers tussen twee `unique_ptr`-objecten. Beide pointers blijven geldig, maar wijzen nu naar elkaars oorspronkelijke object [7](#page=7).
> **Voorbeeld:** `p1.swap(p2);` zorgt ervoor dat `p1` nu wijst naar wat `p2` eerst wees, en vice versa [7](#page=7).
### 3.7 Oefening met unieke pointers
Gegeven twee `unique_ptr`s, `p1` en `p2`, die naar elementen van hetzelfde, ongespecificeerde type wijzen, met de aanname dat dit type de operator `<` ondersteunt. Schrijf een procedure `kleinste_eerst(p1, p2)` die ervoor zorgt dat `p1` naar het kleinste element wijst en `p2` naar het grootste element van de twee [9](#page=9).
> **Tip:** Gebruik de dereferentie-operator en de swap-operatie om de pointers correct te herordenen [7](#page=7) [9](#page=9).
---
# De shared_ptr
Dit deel behandelt de functionaliteit van `shared_ptr`, de mogelijkheden voor gedeeld eigenaarschap, en het beheer van het vrijgeven van geheugen wanneer er meerdere referenties zijn [11](#page=11).
### 4.1 Gedeeld eigenaarschap met shared_ptr
Een `shared_ptr` maakt gedeeld eigenaarschap van een dynamisch gealloceerd object mogelijk. Dit betekent dat meerdere `shared_ptr` instanties kunnen verwijzen naar hetzelfde object in het geheugen [11](#page=11).
#### 4.1.1 Kenmerken van shared_ptr
* **Kopieerbaarheid:** `shared_ptr` instanties kunnen gekopieerd worden, waardoor er meerdere eigenaars van het object kunnen ontstaan [11](#page=11).
* **Geheugenbeheer:** Het geheugen waarnaar verwezen wordt, wordt pas vrijgegeven wanneer *alle* instanties van de `shared_ptr` die eigenaar zijn van het object, vernietigd zijn. Dit is een cruciaal verschil met raw pointers of `unique_ptr` [11](#page=11).
* **Overhead:** Het gebruik van `shared_ptr` brengt een bepaalde overhead met zich mee omdat het aantal referenties naar het object bijgehouden moet worden. Daarom wordt aangeraden `shared_ptr` alleen te gebruiken wanneer gedeeld eigenaarschap echt noodzakelijk is [11](#page=11).
#### 4.1.2 Gebruik en reset van shared_ptr
Een `shared_ptr` wordt typisch geïnitialiseerd met een object gecreëerd via `make_shared`. Wanneer een `shared_ptr` wordt toegewezen aan een andere `shared_ptr`, delen beide nu het eigenaarschap. Het vrijgeven van het geheugen gebeurt automatisch wanneer de laatste `shared_ptr` die het object beheert, buiten scope gaat of gereset wordt [11](#page=11).
> **Tip:** Gebruik `make_shared` in plaats van `new` gevolgd door een `shared_ptr` constructor, omdat `make_shared` efficiënter is en het aantal geheugenallocaties kan reduceren [11](#page=11).
> **Voorbeeld:**
> ```cpp
> #include
>
> // Initialisatie van twee shared_ptr instanties die nullptr zijn.
> std::shared_ptr p1, p2;
>
> // p1 krijgt eigenaarschap van een integer met waarde 101.
> p1 = std::make_shared ;
>
> // p2 wordt nu ook eigenaar van hetzelfde object als p1.
> // Beide p1 en p2 wijzen naar dezelfde geheugenlocatie.
> p2 = p1;
>
> // p2.reset() maakt p2 los van het object, maar het geheugen wordt nog NIET vrijgegeven
> // omdat p1 nog steeds eigenaar is.
> p2.reset();
>
> // p1.reset() maakt p1 los van het object. Nu zijn er geen shared_ptr instanties meer
> // die het object beheren, dus het geheugen wordt automatisch vrijgegeven.
> // Dit gebeurt ook automatisch als p1 uit scope gaat.
> p1.reset();
> ```
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| nullptr | Een sleutelwoord dat een nullpointer constante voorstelt. Het vervangt de oudere NULL en 0 constanten en is sterk getypeerd, wat betekent dat het enkel te gebruiken is bij pointers. |
| Smart pointer | Een klasse die fungeert als een wrapper om een pointer. Naast het beheren van de pointer zelf, biedt het extra functionaliteiten zoals automatisch reserveren en vrijgeven van geheugen, wat de noodzaak voor handmatige `new` en `delete` operaties elimineert. |
| unique_ptr | Een type smart pointer dat een uniek eigenaarschap over het beheerde geheugen garandeert. Het kan niet gekopieerd worden, maar ondersteunt wel `move` en `swap` operaties. Geheugen wordt automatisch vrijgegeven wanneer de `unique_ptr` buiten scope gaat. |
| shared_ptr | Een type smart pointer dat gedeeld eigenaarschap toestaat. Meerdere `shared_ptr` instanties kunnen naar hetzelfde geheugen wijzen. Het geheugen wordt pas vrijgegeven wanneer de laatste `shared_ptr` die ernaar verwijst, wordt vernietigd. |
| Raw pointers | Traditionele pointers in C++ die geen automatisch geheugenbeheer bieden. Gebruikers zijn zelf verantwoordelijk voor het toewijzen en vrijgeven van geheugen met `new` en `delete`. |
| Geheugen lek | Een situatie waarbij geheugen dat dynamisch is toegewezen, niet correct wordt vrijgegeven nadat het niet langer nodig is. Dit kan leiden tot een geleidelijke afname van beschikbare systeembronnen en prestatieproblemen. |
| Overhead | Extra rekenkracht of geheugen die nodig is om een bepaalde functionaliteit te implementeren. Bij `shared_ptr` ontstaat overhead door het bijhouden van het aantal referenties naar het beheerde geheugen. |
| Constructeur | Een speciale methode binnen een klasse die automatisch wordt aangeroepen wanneer een object van die klasse wordt gecreëerd. Het initialiseert de gegevensleden van het object. |
Cover
H3_Collections.pdf
Summary
# Algemeen over collections
Dit document biedt een introductie tot het concept van collections in C++, waarbij de basisprincipes, het beheer van elementen en de navigatie met iteratoren worden behandeld [1](#page=1) [2](#page=2).
### 1.1 Basisprincipes van collections
Collections in C++ zijn klassen waarvan objecten worden aangemaakt om een verzameling van elementen te beheren. Elke collection biedt specifieke lidfuncties en operatoren om het gebruik te vereenvoudigen [3](#page=3).
* **Elementtype:** Alle elementen binnen een enkele collection moeten van hetzelfde type zijn. Dit type kan zowel een basistype als een samengesteld type (een klasse) zijn [3](#page=3).
* **Voorbeeld:** `set s;` declareert een collection genaamd `s` die alleen `string` elementen kan bevatten [3](#page=3).
* **Groottebeheer:** De collection beheert intern het aantal elementen dat hij bevat. De huidige grootte kan worden opgevraagd met de `size()` lidfunctie [3](#page=3).
* **Voorbeeld:** `cout << s.size();` drukt het aantal elementen in de set `s` af [3](#page=3).
* **Kenmerken:** Elke collection heeft specifieke voor- en nadelen en is ontworpen met bepaalde kenmerken in gedachten [3](#page=3).
### 1.2 Iteratoren
Iteratoren worden gebruikt om door de elementen van een collection te navigeren. Ze kunnen worden beschouwd als pointers naar individuele elementen binnen de collection [4](#page=4).
* **Declaratie:** Een iterator wordt gedeclareerd met het type van de collection en het woord `iterator` (of `const_iterator` voor een iterator die de elementen niet mag wijzigen) [4](#page=4).
* **Voorbeeld:**
```cpp
list l;
list::iterator it; // Iterator voor de list 'l'
typename set::const_iterator cis; // Constante iterator voor een set van type T
```
* **Initialisatie naar het begin:** De `begin()` lidfunctie geeft een iterator terug die wijst naar het eerste element van de collection [5](#page=5).
* **Voorbeeld:** `it = l.begin();` wijst de iterator `it` naar het eerste element van de list `l` [5](#page=5).
* **Verplaatsen van iteratoren:**
* `++it` of `it++`: Verplaatst de iterator naar het volgende element [5](#page=5).
* `--it` of `it--`: Verplaatst de iterator naar het vorige element [5](#page=5).
* `advance(it, n)`: Verplaatst de iterator `n` posities vooruit (of achteruit) [5](#page=5).
* **Toegang tot elementen:** Het dereferentieer-symbool (`*`) wordt gebruikt om toegang te krijgen tot het element waar de iterator momenteel naar wijst [5](#page=5).
* **Voorbeeld:**
```cpp
cout << *it << endl; // Drukt het element af waar 'it' naar wijst
*it = 10; // Wijzigt het element waar 'it' naar wijst naar 10
```
* Voor collections die key-value paren opslaan, zoals `map`: `cout << (*it).first << it->second;` [5](#page=5).
* **Het einde van de collection:** De `end()` lidfunctie retourneert een iterator die wijst naar de positie *na* het laatste element van de collection. Dit is nuttig om te controleren of alle elementen zijn doorlopen [6](#page=6).
* **Belangrijk:** `end()` wordt ook gebruikt door de `find()` lidfunctie om aan te geven dat een gezocht element niet is gevonden [6](#page=6).
### 1.3 Door collections lopen met iteratoren
Om alle elementen van een collection uit te schrijven met behulp van een iterator, wordt meestal een `while`-lus gebruikt in combinatie met `begin()` en `end()` [6](#page=6).
* **Typische `while`-lus:**
```cpp
list l;
// ... vul de list 'l' ...
list::iterator it = l.begin();
while (it != l.end()) { // Controleer of de iterator NIET gelijk is aan 'end()'
cout << *it << endl;
it++; // Verplaats naar het volgende element
}
```
* **Alternatieven:** Er kan ook gebruik gemaakt worden van een `for`-lus of een *for-each* lus voor meer compacte code [6](#page=6).
* **Opmerking over verplaatsing en dereferentie:** De uitdrukking `*it++` derefereert de iterator `it` (krijgt het element) en verhoogt vervolgens de iterator naar het volgende element. De volgorde is hier cruciaal [6](#page=6).
> **Tip:** Gebruik altijd `it!= l.end()` om te controleren of je het einde van de collection hebt bereikt, en nooit `it < l.end()`, omdat de vergelijking met `<` niet altijd gedefinieerd is voor alle iteratortypes [6](#page=6).
De inhoudsopgave van het document vermeldt de volgende secties na "Algemeen": Sequences, Container adapters, en Associatieve containers [2](#page=2) [7](#page=7).
---
# Sequentiële containers (sequences)
Sequentiële containers slaan elementen op in een lineaire volgorde [8](#page=8).
## 2. Sequentiële containers
Sequentiële containers zijn datastructuren waarbij elk element, met uitzondering van het eerste en laatste, een linker- en rechterbuur heeft. De elementen worden in een specifieke, lineaire volgorde opgeslagen, en wanneer men de container doorloopt met een iterator, gebeurt dit in deze gedefinieerde volgorde. Dit impliceert dat er een `i`-de element bestaat binnen de sequentie [8](#page=8).
### 2.1 Voorbeelden van sequentiële containers
De cursus bespreekt specifiek de groeitabel (ook wel bekend als `vector` in C++). Andere voorbeelden van sequentiële containers die niet verder worden uitgediept in deze cursus zijn de gelinkte lijst (`forward_list`) en de double-ended queue (`deque`), die als een vector met een efficiënte invoeging vooraan kan worden beschouwd [9](#page=9).
### 2.2 De groeitabel (vector)
#### 2.2.1 Principe van de groeitabel
Een groeitabel is een tabelgebaseerde container die dynamisch, dus tijdens de uitvoering van het programma, kan groeien. Bij de declaratie van een groeitabel wordt het type van de elementen vastgelegd en wordt een initiële capaciteit voorzien, wat het aantal beschikbare geheugenlocaties aangeeft [10](#page=10).
#### 2.2.2 Elementtoevoeging in de groeitabel
Er zijn twee scenario's mogelijk bij het toevoegen van een element aan een groeitabel [11](#page=11):
1. **Er is nog plaats beschikbaar:** Als het huidige aantal elementen (`n`) kleiner is dan de capaciteit van de tabel, wordt het nieuwe element op de eerstvolgende vrije index (`n`) geplaatst, en wordt `n` met één verhoogd [11](#page=11).
2. **Er is geen plaats meer beschikbaar:** Wanneer de capaciteit is bereikt, wordt er een nieuwe array met een grotere capaciteit gealloceerd. Alle elementen uit de oude array worden naar de nieuwe array gekopieerd, waarna de oude array wordt vrijgegeven. Het nieuwe element wordt vervolgens toegevoegd op index `n`, en `n` wordt verhoogd [11](#page=11).
#### 2.2.3 Uitbreiding van de groeitabel
Het proces van het alloceren van een nieuwe array en het kopiëren van elementen is een relatief kostbare operatie. Om de frequentie van deze dure herallocaties te minimaliseren, wordt de capaciteit van de array in de praktijk doorgaans verdubbeld bij elke herallocatie. Hoewel de gebruiker zich hier geen zorgen over hoeft te maken, is het in de praktijk wenselijk om deze verdubbelingen zoveel mogelijk te beperken om te voorkomen dat er te vaak opnieuw moet worden uitgebreid [12](#page=12).
#### 2.2.4 C++ implementaties van groeitabellen
In C++ zijn `string` en `vector` voorbeelden van groeitabellen [13](#page=13).
#### 2.2.5 Declaratie en initialisatie van `vector`
De declaratie van een `vector` vereist de `#include ` header en het gebruik van de `std` namespace [14](#page=14).
* **Standaard declaratie:** `vector v1;` initialiseert een lege vector met een capaciteit en grootte van nul [14](#page=14).
* **Declaratie met grootte:** `vector v2;` creëert een vector met een capaciteit en grootte van 6, waarbij de elementen worden geïnitialiseerd met de defaultwaarde (0 voor `int`) [15](#page=15) [6](#page=6).
* **Declaratie met grootte en initiële waarde:** `vector v3(4, "test");` maakt een vector met een capaciteit en grootte van 4, waarbij alle elementen worden geïnitialiseerd met de string "test" [15](#page=15).
* **Kopieerconstructor:** `vector v4(v2);` creëert een nieuwe vector `v4` die een kopie is van `v2` [15](#page=15).
> **Tip:** Let op het verschil met `vector v2;`, wat een array van 6 lege vectoren definieert, niet een vector met 6 elementen. Het gebruik van constructors voor vectoren vervangt de noodzaak van `new` [14](#page=14) [15](#page=15) [6](#page=6).
#### 2.2.6 Elementen toevoegen aan het einde
De methode `void push_back(type e)` voegt een element `e` toe aan het einde van de vector en verhoogt de grootte (`size`) met 1 [16](#page=16).
* **Voorbeeld:** Als `v4` initieel 3 elementen (met grootte 3 en capaciteit 3) bevat, zal `v4.push_back;` de vector uitbreiden met 8. De grootte wordt 4, en de capaciteit kan bijvoorbeeld 6 worden na een eventuele herallocatie. Vervolgens zal `v4.push_back;` het element 2 toevoegen [16](#page=16) [2](#page=2) [8](#page=8).
#### 2.2.7 Toegang en aanpassing van elementen
* **Indexering met `[]`:** Elementen kunnen worden benaderd en aangepast met de `[]` operator, bijvoorbeeld `v4 = -5;` [17](#page=17) [3](#page=3).
> **Tip:** Het gebruik van de `[]` operator resulteert in een crash als de index groter is dan of gelijk is aan de capaciteit. Het is ook belangrijk om te weten dat `[]` de `size` van de vector niet aanpast. De index moet idealiter kleiner zijn dan de huidige `size` [17](#page=17).
* **`size()` methode:** Geeft de huidige grootte (het aantal elementen) van de vector terug [17](#page=17).
* **`capacity()` methode:** Geeft de huidige capaciteit (het aantal beschikbare geheugenplaatsen) van de vector terug [17](#page=17).
#### 2.2.8 Andere nuttige methoden voor vectoren
* **`front()`:** Geeft een referentie terug naar het eerste element van de vector. Kan gebruikt worden om het element te lezen of aan te passen (bv. `v.front() *= 2;`) [18](#page=18).
* **`back()`:** Geeft een referentie terug naar het laatste element van de vector [18](#page=18).
* **`at(int i)`:** Geeft een referentie terug naar het element op index `i`. Deze methode werpt een exception als de index `i` groter is dan of gelijk is aan de `size` van de vector [18](#page=18).
* **`empty()`:** Controleert of de vector leeg is en retourneert `true` indien dit het geval is, anders `false` [18](#page=18).
* **`clear()`:** Zet de grootte (`size`) van de vector op 0, terwijl de capaciteit ongewijzigd blijft [18](#page=18).
* **`pop_back()`:** Verwijdert het laatste element uit de vector en vermindert de grootte (`size`) met 1 [19](#page=19).
* **`resize(int n)`:** Past de grootte (`size`) van de vector aan naar `n`. Nieuw toegevoegde elementen worden geïnitialiseerd [19](#page=19).
* **`reserve(int n)`:** Garandeert dat de capaciteit van de vector minimaal `n` is. De capaciteit wordt niet verkleind, en de inhoud wordt niet geïnitialiseerd; de grootte (`size`) blijft onveranderd [19](#page=19).
#### 2.2.9 Toepassingen van `vector`
* **Toepassing 1: Reeks gehele getallen lezen tot -1**
Om een reeks gehele getallen in te lezen en op te slaan totdat -1 wordt ingevoerd, kan een `vector` gebruikt worden met `push_back` [20](#page=20).
```cpp
vector v;
int g;
cin >> g;
while (g != -1) {
v.push_back(g);
cin >> g;
}
```
* **Toepassing 2: Een vooraf bepaald aantal gehele getallen lezen**
Er zijn verschillende manieren om `n` gehele getallen in te lezen en op te slaan:
* **Oplossing 1 (met C-style array):** Dit maakt gebruik van een C-style array van variabele grootte (`int t[n]`). Het nadeel is dat de grootte van een array achteraf niet meer kan worden aangepast [21](#page=21).
```cpp
int n;
cin >> n;
int t[n;
for (int i = 0; i < n; i++) {
cin >> t[i;
}
```
* **Oplossing 2 (met `vector::reserve` en `push_back`):** Hier wordt eerst de capaciteit gereserveerd met `v.reserve(n)`, waarna elementen worden toegevoegd met `push_back`. Dit is efficiënt omdat er minder herallocaties plaatsvinden.
```cpp
int n, g;
cin >> n;
vector v;
v.reserve(n); // Capaciteit wordt minstens n
for (int i = 0; i < n; i++) {
cin >> g;
v.push_back(g); // Voegt element toe, size neemt toe
}
// Na dit proces: size = n, capaciteit = n (of meer)
```
> **Tip:** Men kan hier ook `cin >> v[i;` gebruiken, maar dan past de `size` van de vector zich niet automatisch aan. `push_back` is daarom beter geschikt in combinatie met `reserve` wanneer de uiteindelijke `size` gelijk is aan de gereserveerde capaciteit [22](#page=22).
* **Oplossing 3 (met `vector` constructor en indexering):** De vector wordt direct geïnitialiseerd met de gewenste grootte `n`. Vervolgens worden de elementen direct op de juiste indexen ingelezen met `v[i]`. In dit geval is het gebruik van `push_back` niet nodig en zelfs inefficiënt, omdat de vector dan een onnodig grote interne structuur zou kunnen hebben [23](#page=23).
```cpp
int n, g;
cin >> n;
vector v(n); // Vector met size = n en capaciteit = n
for (int i = 0; i < n; i++) {
cin >> v[i; // Directe toewijzing aan bestaande elementen
}
// Na dit proces: size = n, capaciteit = n
```
---
# Container adapters
Container adapters zijn structuren die het gebruik van onderliggende collections beperken en een specifieke interface bieden, zoals stacks en queues. Ze worden geïmplementeerd met behulp van een van de reeds bestaande collections. Het primaire doel van een adapter is het aanpassen of beperken van de interface van een onderliggende datastructuur [25](#page=25).
### 3.1 Concept en functionaliteit
Container adapters beperken het aantal toegankelijke elementen binnen de collectie. Dit betekent dat indexeren van elementen niet mogelijk is en er geen iteratoren beschikbaar zijn om door de collectie te navigeren. Ze definiëren slechts een beperkt aantal operaties die op de data kunnen worden uitgevoerd [25](#page=25).
### 3.2 Voorbeelden van container adapters
Enkele veelvoorkomende voorbeelden van container adapters zijn:
* **Stapel (stack)**: Een LIFO (Last-In, First-Out) datastructuur [25](#page=25).
* **Wachtrij (queue)**: Een FIFO (First-In, First-Out) datastructuur [25](#page=25).
* **Prioriteitswachtrij (priority\_queue)**: Een wachtrij waarbij elementen worden gesorteerd op prioriteit [25](#page=25).
Deze specifieke adapters worden in de context van de cursus verder niet besproken [25](#page=25).
---
# Associatieve containers
Associatieve containers slaan gegevens op in de vorm van sleutel-waarde paren, waardoor efficiënte opslag en opvraging van informatie op basis van een sleutel mogelijk is [27](#page=27).
### 4.1 Verzamelingen (set)
Een verzameling (set) is een collection waarin elk element uniek is en geen bijhorende informatie heeft; het element zelf fungeert als de sleutel. De volgende operaties zijn mogelijk: elementen toevoegen (indien al aanwezig gebeurt er niets), elementen opzoeken en de elementen overlopen [29](#page=29).
#### 4.1.1 Families van verzamelingen
Er zijn twee hoofd families van verzamelingen:
1. **Gebaseerd op een binaire zoekboom**: Elementen worden in gesorteerde volgorde opgeslagen en kunnen relatief snel toegevoegd of gevonden worden. In C++ wordt dit geïmplementeerd door de klasse `set`. De template parameter moet de `<` operator ondersteunen [30](#page=30).
2. **Gebaseerd op een hashtabel**: Elementen kunnen zeer snel toegevoegd of gevonden worden, maar de opslag is niet gesorteerd. In C++ wordt dit geïmplementeerd door de klasse `unordered_set`. De template parameter moet een hash-functie voorzien [30](#page=30).
#### 4.1.2 Methoden voor `set` en `unordered_set`
Enkele veelgebruikte methoden zijn [31-33](#page=31,32,33):
* `pair insert(type x)`: Voegt element `x` toe als het nog niet aanwezig is. De methode retourneert een paar bestaande uit een iterator naar het element en een boolean die aangeeft of de toevoeging succesvol was [31](#page=31).
> **Voorbeeld:**
> ```cpp
> set s;
> s.insert [5](#page=5);
> auto [it, b = s.insert; // Gebruik van structured binding (C++17) [1](#page=1).
> if (b) {
> cout << "Toegevoegd" << endl;
> }
> cout << *it << endl; // Output: 1
> ```
> [31](#page=31).
* `int erase(type x)`: Verwijdert element `x` als het voorkomt. Retourneert het aantal keren dat `x` werd verwijderd (0 of 1) [32](#page=32).
* `int count(type x)`: Telt hoe vaak element `x` voorkomt (0 of 1) [32](#page=32).
* `void clear()`: Wist de volledige verzameling [32](#page=32).
* `bool empty()`: Controleert of de verzameling leeg is [32](#page=32).
* `int size()`: Geeft het aantal elementen in de verzameling terug [32](#page=32).
Om de verzameling te overlopen, worden iteratoren of een for-each-lus gebruikt. Het gebruik van gewone for-lussen met indices is niet mogelijk omdat deze niet bestaan voor deze containers [32](#page=32).
Iteratoren kunnen ook gebruikt worden voor het zoeken, toevoegen of verwijderen van elementen, hoewel dit zelden wordt gedaan [33](#page=33).
* `iterator find(type x)`: Geeft een iterator terug naar het gezochte element, of de `end`-iterator als `x` niet voorkomt [33](#page=33).
* `void erase(iterator it)`: Verwijdert het element waarnaar de iterator `it` verwijst [33](#page=33).
* `iterator insert(iterator it, type x)`: Voegt `x` toe na (C++98) of vóór (C++11) de iterator `it`. Dit is enkel efficiënt als de iterator op de juiste positie staat [33](#page=33).
### 4.2 Multi-verzamelingen (multiset)
Een multi-verzameling is een variant op de verzameling waarbij duplicaten wel worden bewaard. Een element kan dus meerdere keren voorkomen. In de C++ standard library kunnen de klassen `multiset` (gebaseerd op binaire zoekboom) en `unordered_multiset` (gebaseerd op hashtabel) gebruikt worden [34](#page=34).
### 4.3 Afbeeldingen (map)
Een afbeelding (map), ook wel dictionary genoemd, is een collection van sleutel-waarde paren (key/value pairs). Bij het toevoegen wordt een sleutel en bijhorende waarde opgegeven, en via de sleutel kan de waarde opgezocht of gewijzigd worden. Een (multi)map is conceptueel een (multi)set van paren, waarbij bij een `map` de sleutels uniek zijn en bij een `multimap` niet [36](#page=36).
#### 4.3.1 De (multi)map in C++
* **`map` en `multimap`**: Deze zijn gebaseerd op binaire zoekbomen. De paren worden opgeslagen in stijgende sleutelvolgorde, en de sleuteltypes moeten de `operator<` ondersteunen [37](#page=37).
* **`unordered_map` en `unordered_multimap`**: Deze zijn gebaseerd op hashtabellen, wat snellere toegang biedt [37](#page=37).
#### 4.3.2 Gebruik van `operator[]` in `map`
Voor een `map` (niet `multimap`) kan de `operator[]` gebruikt worden om de bijhorende waarde op te vragen, in te stellen, of om een sleutel toe te voegen met een defaultwaarde indien de sleutel nog niet bestaat [37](#page=37).
> **Nadeel:** Gebruik `operator[]` nooit bij een `const [unordered_]map&` omdat dit de const-correctheid schendt. Gebruik in dat geval `find` [38](#page=38).
>
> **Voorbeeld:**
> ```cpp
> map m;
> m['a'] = 1;
> m['b'] = 2;
> m['a'] = 3; // Waarde bij sleutel 'a' wordt aangepast
> cout << m['a'] << ' ' << m['c'; // Output: 3 0
> ```
> Hier wordt sleutel 'c' toegevoegd met de defaultwaarde 0 [38](#page=38).
>
> **Voordeel:** Kan de opbouw van een map erg compact maken.
> ```cpp
> map> m; // Sleutel = lengte string
> string s; // ... lees strings ...
> m[s.size().insert(s); // Voegt string s toe aan de set bij de key s.size()
> ```
> [38](#page=38).
#### 4.3.3 Methoden voor `map` en `unordered_map`
Enkele veelgebruikte methoden zijn [39-41](#page=39,40,41):
* `pair insert(const pair &p)`: Voegt paar `p` toe. Bij een `map` gebeurt dit enkel als de sleutel nog niet voorkomt [39](#page=39).
> **Voorbeeld:**
> ```cpp
> map m;
> m.insert(pair('b', 3));
> m.insert({'d', 9}); // Gebruik van initializer_list
> ```
> [39](#page=39).
* `int erase(type x)`: Verwijdert de sleutel `x` (en de bijhorende waarde) als deze voorkomt. Retourneert het aantal keren dat de sleutel werd verwijderd (0 of 1) [39](#page=39).
* `int count(type x)`: Telt hoe vaak de sleutel `x` voorkomt (0 of 1 voor `map`, kan meer zijn voor `multimap`) [39](#page=39).
* `void clear()`: Wist de volledige afbeelding [39](#page=39).
* `bool empty()`: Controleert of de afbeelding leeg is [39](#page=39).
* `int size()`: Geeft het aantal elementen (paren) in de afbeelding terug [39](#page=39).
#### 4.3.4 Overlopen van (multi)maps
Men kan een for-each-lus of iteratoren gebruiken om de (multi)map te overlopen [40](#page=40).
> **Voorbeeld:**
> ```cpp
> map m;
> // ... vul de map ...
>
> // Met for-each lus en structured binding (C++17)
> for (const auto &[key, value: m) {
> cout << key << "->" << value << endl;
> }
>
> // Met iteratoren
> for (auto it = m.begin(); it != m.end(); it++) {
> cout << it->first << "->" << it->second << endl;
> }
> ```
> [40](#page=40).
Net als bij verzamelingen, kunnen iteratoren gebruikt worden voor het zoeken, toevoegen of verwijderen van elementen, maar dit is enkel efficiënt als de iterator op de juiste positie staat [41](#page=41).
* `iterator find(keytype x)`: Geeft een iterator terug naar de sleutel `x`, of de `end`-iterator indien `x` niet voorkomt [41](#page=41).
* `void erase(iterator it)`: Verwijdert het element waarnaar de iterator `it` verwijst [41](#page=41).
* `iterator insert(iterator it, pair p)`: Voegt paar `p` toe na (C++98) of vóór (C++11) de iterator `it` [41](#page=41).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Collections | Een verzameling van elementen van hetzelfde type, die verschillende functionaliteiten biedt voor het opslaan en beheren van data. Ze hebben specifieke lidfuncties en operatoren om het gebruik te vereenvoudigen. |
| Lidfuncties | Functies die behoren tot een klasse en operaties uitvoeren op de objecten van die klasse, zoals het toevoegen of opvragen van elementen. |
| Iteratoren | Een pointer-achtig object dat gebruikt wordt om elementen binnen een collection te doorlopen. Ze bieden een uniforme manier om toegang te krijgen tot elementen, ongeacht het type collection. |
| begin() | Een lidfunctie van collections die een iterator retourneert die wijst naar het eerste element van de collection. |
| end() | Een lidfunctie van collections die een iterator retourneert die wijst naar de positie na het laatste element van de collection. Wordt gebruikt als eindconditie in lussen. |
| Sequences | Een type collection dat elementen in een lineaire, sequentiële volgorde opslaat. Elk element, behalve het eerste en laatste, heeft een linker- en rechterbuur. |
| Groeitabel (vector) | Een dynamische array-achtige collection die automatisch kan groeien wanneer er nieuwe elementen worden toegevoegd. De capaciteit kan worden beheerd om efficiëntie te optimaliseren. |
| Capaciteit | Het aantal elementen dat op dit moment in de onderliggende geheugenruimte van een collection kan worden opgeslagen zonder dat er een herallocatie nodig is. |
| Grootte (size) | Het werkelijke aantal elementen dat momenteel in een collection aanwezig is. |
| push_back() | Een functie die een element toevoegt aan het einde van een vector, waardoor de grootte van de vector met één toeneemt. |
| pop_back() | Een functie die het laatste element uit een vector verwijdert en de grootte van de vector met één vermindert. |
| reserve() | Een functie die de minimale capaciteit van een vector instelt. Dit kan vooraf geheugen toewijzen om frequente herallocaties te voorkomen. |
| Container adapters | Interfaces die bovenop bestaande collections worden gebouwd om hun functionaliteit te beperken of aan te passen, zoals een stack of een queue. Ze bieden een vereenvoudigde set operaties. |
| Stapel (stack) | Een LIFO (Last-In, First-Out) data-structuur die wordt geïmplementeerd met een container adapter. Elementen worden toegevoegd en verwijderd aan dezelfde kant (de top). |
| Wachtrij (queue) | Een FIFO (First-In, First-Out) data-structuur die wordt geïmplementeerd met een container adapter. Elementen worden toegevoegd aan de achterkant en verwijderd aan de voorkant. |
| Associatieve containers | Collections die gegevens opslaan als sleutel-waarde paren. Ze maken efficiënt zoeken, invoegen en verwijderen van gegevens mogelijk op basis van de sleutel. |
| Set | Een associatieve container waarin elk element uniek is en geen bijbehorende informatie heeft. Elementen worden vaak in gesorteerde volgorde opgeslagen. |
| Multiset | Een variant van de set waarin duplicaten van elementen zijn toegestaan. Een element kan dus meerdere keren voorkomen. |
| Afbeelding (map) | Een associatieve container die een verzameling van unieke sleutel-waarde paren opslaat. Via de sleutel kan de bijbehorende waarde efficiënt worden opgezocht. |
| Unordered_set | Een associatieve container die elementen opslaat op basis van een hash-tabel, wat resulteert in zeer snelle zoek- en invoegoperaties, maar de elementen worden niet in gesorteerde volgorde bewaard. |
| Unordered_map | Een associatieve container die sleutel-waarde paren opslaat op basis van een hash-tabel. Dit biedt snelle toegang tot waarden via hun sleutels, zonder garanties over de volgorde. |
| Hash tabel | Een datastructuur die sleutels omzet in indices in een array met behulp van een hash-functie, wat snelle gemiddelde tijdcomplexiteit voor zoek-, invoeg- en verwijderbewerkingen mogelijk maakt. |
| Binaire zoekboom | Een boomdatastructuur waarin elk knooppunt een sleutel heeft en de sleutels in de linker subboom kleiner zijn dan de sleutel van het knooppunt, en de sleutels in de rechter subboom groter zijn. |
Cover
H4_OGP_C++.pdf
Summary
# Algemeen over klassen in C++
Klassen in C++ vormen de basis voor objectgeoriënteerd programmeren, waarbij ze fungeren als blauwdrukken voor het creëren van objecten met specifieke data (attributen) en gedragingen (lidfuncties) [3](#page=3).
### 1.1 Declaratie en definitie van klassen
Een klasse in C++ wordt onderscheiden in een **declaratie** en een **definitie** [3](#page=3).
* **Declaratie:** De declaratie van een klasse omvat de structuur van de klasse, inclusief de attributen (dataleden) en de declaraties van de lidfuncties. Het is cruciaal om de afsluitende puntkomma (`;`) niet te vergeten na de afsluitende accolades (`}`) van de klassedeclaratie [3](#page=3).
* **Definitie:** De definitie van een lidfunctie vindt plaats buiten de klassedeclaratie. Hierbij wordt de scope-operator (`::`) gebruikt, direct voorafgaand aan de naam van de lidfunctie, om aan te geven tot welke klasse de functie behoort [3](#page=3).
> **Tip:** De standaardwaarde voor parameters van lidfuncties mag slechts één keer worden gespecificeerd, doorgaans in de klassedeclaratie. Bij het definiëren van de lidfunctie buiten de klasse wordt deze standaardwaarde weggelaten [3](#page=3).
### 1.2 Lidfuncties en de `this` pointer
Lidfuncties zijn de functies die tot een klasse behoren en opereren op de data van een object van die klasse.
* **De `this` pointer:** Binnen een lidfunctie verwijst de speciale pointer `this` naar het huidige object waarop de functie wordt aangeroepen. Dit wordt vaak gebruikt om een onderscheid te maken tussen een klasse-attribuut en een parameter met dezelfde naam [4](#page=4).
> **Voorbeeld:**
> ```cpp
> void voorbeeld::set_a(int a) {
> this->a = a; // 'this->a' verwijst naar het attribuut van het object,
> // 'a' verwijst naar de parameter.
> }
> ```
### 1.3 Private en public secties
Klassen maken gebruik van toegangscontrolemechanismen om de zichtbaarheid en toegankelijkheid van hun leden te beheren.
* **`private` sectie:** Leden die in de `private` sectie worden gedeclareerd, zijn alleen toegankelijk vanuit lidfuncties van dezelfde klasse. Dit bevordert inkapseling door interne implementatiedetails te verbergen [3](#page=3).
* **`public` sectie:** Leden in de `public` sectie zijn toegankelijk vanuit zowel lidfuncties van de klasse als van buiten de klasse. Dit definieert de interface van de klasse, wat gebruikers van de klasse kunnen gebruiken [3](#page=3).
* **Default toegankelijkheid:** Indien er geen expliciete `public:` of `private:` specificatie is, worden alle leden standaard als `private` beschouwd [3](#page=3).
* **Enkele specificatie:** De sleutelwoorden `private:` en `public:` mogen elk slechts één keer voorkomen binnen een klassedeclaratie [3](#page=3).
> **Voorbeeld van een klassedeclaratie:**
> ```cpp
> class voorbeeld {
> public: // public-blok
> void set_a(int = -1); // Lidfunctie declaratie
> private: // private-blok
> int a, b; // Primitieve attributen worden NIET automatisch geïnitialiseerd
> }; // Afsluitende puntkomma is vereist
> ```
### 1.4 Mutator en Accessor lidfuncties
Lidfuncties kunnen grofweg worden ingedeeld naar hun functie:
* **Mutator (Mutator):** Dit zijn lidfuncties die de attributen van het huidige object wijzigen. Voorbeelden hiervan zijn "setter"-functies die een waarde instellen voor een attribuut [5](#page=5).
* **Accessor (Accessor):** Dit zijn lidfuncties die de attributen van het huidige object niet wijzigen. "Getter"-functies, die de waarde van een attribuut ophalen, vallen hieronder [5](#page=5).
#### 1.4.1 De `const` specificatie voor accessors
Om expliciet te garanderen dat een lidfunctie een attribuut niet wijzigt, kan het sleutelwoord `const` na de parameterlijst worden toegevoegd. De compiler controleert vervolgens of de functie zich aan deze garantie houdt [5](#page=5).
> **Belangrijk:**
> * De `const` specificatie moet worden herhaald bij de definitie van de lidfunctie buiten de klasse, omdat het deel uitmaakt van de functie-signatuur [5](#page=5).
> * Het toekennen van `const` aan een "setter" lidfunctie zal resulteren in een compilerfout, aangezien setters per definitie de objecttoestand wijzigen [6](#page=6).
> **Voorbeeld met `const` accessor:**
> ```cpp
> class voorbeeld {
> public:
> void set_a(int = -1);
> int get_a() const; // Accessor gemarkeerd als const
> private:
> int a, b;
> };
>
> int voorbeeld::get_a() const { // const moet hier herhaald worden
> return a;
> }
> ```
---
# Constructoren en destructoren
Dit onderwerp behandelt de automatische aanroep van constructoren en destructoren bij het creëren en vernietigen van objecten, inclusief de rol van destructoren bij dynamisch geheugenbeheer [10](#page=10) [8](#page=8).
### 2.1 Constructoren
Een constructor is een speciale methode binnen een klasse die automatisch wordt aangeroepen wanneer een object van die klasse wordt gedeclareerd. Het primaire doel van een constructor is om een correct geïnitialiseerd object te creëren [8](#page=8).
#### 2.1.1 Kenmerken van constructoren
* Een constructor heeft geen return-type, zelfs geen `void` [8](#page=8).
* Constructor overloading is mogelijk, wat betekent dat er meerdere constructoren met verschillende parameterslijsten binnen dezelfde klasse kunnen bestaan [8](#page=8).
* Default-parameters zijn ook toegestaan in constructoren [8](#page=8).
* Als een klasse geen enkele constructor bevat, wordt er automatisch een default constructor voorzien. Deze default constructor initialiseert primitieve typen met willekeurige waarden ("rommel") en klasse-attributen met hun eigen default constructoren [8](#page=8).
* Zodra een klasse ook maar één eigen constructor definieert, wordt de automatische default constructor niet meer verstrekt [8](#page=8).
#### 2.1.2 Declaratie en aanroep van constructoren
De syntaxis voor een constructor is de naam van de klasse, gevolgd door een optionele parameterlijst: `klassenaam([parameterlijst])` [8](#page=8).
Constructoren worden aangeroepen bij de declaratie van een object, zonder het gebruik van het `new` sleutelwoord [9](#page=9).
> **Voorbeeld:**
> ```cpp
> student std1; // Aangeroepen met default constructor (indien gedefinieerd)
> student std2("Tom"); // Aangeroepen met constructor die een string accepteert
> // NIET OK: student std1();
> // WEL OK (C++11): student std1{};
> ```
> [9](#page=9).
Een object kan ook achteraf worden hergeïnitialiseerd door toewijzing met een ander object van dezelfde klasse, wat impliciet een constructor of kopieeroperator kan aanroepen: `std1 = student("Jan");` [9](#page=9).
#### 2.1.3 Delegerende constructoren
Vanaf C++11 is het mogelijk voor een constructor om een andere constructor van dezelfde klasse aan te roepen via de initializer list. Dit wordt "delegerende constructoren" genoemd [13](#page=13).
> **Voorbeeld:**
> ```cpp
> class voorbeeld {
> public:
> voorbeeld(int _a, int _b) {
> a = _a; b = _b;
> }
> voorbeeld() : voorbeeld(1,2) {} // Roept de andere constructor aan
> private:
> int a, b;
> };
> ```
> [13](#page=13).
### 2.2 Destructoren
Een destructor is een speciaal soort lidfunctie dat automatisch wordt aangeroepen wanneer een object zijn levensduur beëindigt [10](#page=10).
#### 2.2.1 Kenmerken van destructoren
* Een destructor heeft geen return-type (ook geen `void`) en mag geen parameters hebben. Hierdoor is destructor overloading niet mogelijk [10](#page=10).
* De destructor wordt automatisch opgeroepen wanneer een object "out of scope" gaat [10](#page=10).
* Een destructor is alleen strikt noodzakelijk wanneer een klasse dynamisch aangemaakte attributen beheert, zoals datagebieden die zijn aangemaakt met `new` en worden bijgehouden via raw pointers [10](#page=10).
#### 2.2.2 Declaratie van destructoren
De syntaxis voor een destructor is een tilde (`~`) gevolgd door de naam van de klasse: `~klassenaam()` [10](#page=10).
#### 2.2.3 Werking en geheugenbeheer
Bij het verlaten van de scope van objecten die zich op de runtime stack bevinden, wordt de destructor automatisch aangeroepen en het geheugen op de stack vrijgegeven [12](#page=12).
> **Voorbeeld van constructor- en destructoraanroep:**
> ```cpp
> #include
>
> class myclass {
> int i;
> public:
> myclass(int i);
> ~myclass() {
> std::cout << "Destructor object " << i << std::endl;
> }
> };
>
> myclass::myclass(int i) {
> std::cout << "Constructor object " << i << std::endl;
> this->i = i;
> }
>
> int main() {
> myclass a [1](#page=1);
> for (int i = 2; i < 4; i++)
> myclass b(i);
> return 0;
> }
> ```
>
> **Output:**
> ```
> Constructor object 1
> Constructor object 2
> Destructor object 2
> Constructor object 3
> Destructor object 3
> Destructor object 1
> ```
> [11](#page=11) [12](#page=12).
### 2.3 Kopieerconstructoren
De kopieerconstructor wordt in C++ standaard gebruikt in twee specifieke situaties [14](#page=14):
1. Bij het initialiseren van een nieuw object met een bestaand object van dezelfde klasse: `student std2(std1);` [14](#page=14).
2. Bij het doorgeven van objecten als value-parameters aan functies: `void proc(student st)` waarbij `st` een kopie is van het origineel [14](#page=14).
De standaard kopieerconstructor is aanwezig, zelfs als er andere constructoren zijn gedefinieerd, tenzij de klasse attributen zoals `unique_ptr` bevat. De standaard kopieerconstructor kopieert alle attributen (passieve kopie, of "shallow copy"). Dit kan leiden tot problemen bij raw pointers, aangezien meerdere objecten dan naar dezelfde dynamisch toegewezen geheugenlocatie kunnen verwijzen, wat resulteert in een gedeelde structuur [14](#page=14).
Het zelf definiëren van een kopieerconstructor is slechts uitzonderlijk noodzakelijk. Dit is voornamelijk het geval bij het beheer van raw pointers, waarbij een "deep copy" (een nieuwe kopie van de data op de heap) gemaakt moet worden. Voor moderne C++-idiomen, zoals `unique_ptr`, is het gebruik van `make_unique` een betere praktijk [14](#page=14).
De syntaxis voor een kopieerconstructor is `A(const A &a)`, waarbij `A` de klassenaam is. Het is belangrijk om een constante referentie (`const A &a`) te gebruiken en niet een waarde-parameter (`A a`), om onnodige kopieën en potentiële recursie te vermijden [14](#page=14).
---
# Objecten als attributen
Dit gedeelte behandelt het concept van objecten die worden gebruikt als attributen binnen andere klassen, en het belang van initializer lists voor een correcte initialisatie.
### 3.1 Associatie, aggregatie en compositie
Wanneer een klasse een object van een andere klasse bevat als een van haar attributen, wordt dit een associatie, aggregatie of compositie genoemd [16](#page=16).
### 3.2 Initialisatie van attributen in constructors
Wanneer het hoofdblok van een constructor wordt betreden, zijn de attributen van het object al aanwezig [16](#page=16).
* **Primitieve attributen** bevatten bij aanvang willekeurige waarden ('rommel') [16](#page=16).
* **Objectattributen** worden geïnstantieerd met hun default constructor. Als een default constructor niet bestaat voor een dergelijk attribuut, leidt dit tot een fout [16](#page=16).
### 3.3 Het belang van initializer lists
Een initializer list biedt de mogelijkheid om de constructie en initialisatie van attributen te laten plaatsvinden *voordat* het hoofdblok van de constructor wordt betreden. Dit is cruciaal voor een correcte en gecontroleerde initialisatie van zowel primitieve als objectattributen [16](#page=16).
#### 3.3.1 Syntax van initializer lists
De syntax voor een initializer list is als volgt [17](#page=17):
```cpp
class ClassNaam {
public:
ClassNaam(const ParameterType1 ¶m1, ParameterType2 param2) : attribuut1(param1), attribuut2(param2) {}
private:
Type1 attribuut1;
Type2 attribuut2;
};
```
#### 3.3.2 Voorbeeld van een initializer list
Een concreet voorbeeld van het gebruik van een initializer list wordt getoond in de bestanden binnen de map `vbInitList`. Hier is een schematische weergave [17](#page=17):
```cpp
class A {
public:
A(const B &b1, int i1) : b(b1), i(i1) {}
private:
B b;
int i;
};
```
In dit voorbeeld wordt het attribuut `b` geïnitialiseerd met de meegegeven `b1` en het attribuut `i` met de meegegeven `i1`, nog voordat de code binnen het constructorblok van `A` wordt uitgevoerd.
> **Tip:** Het is sterk aan te raden om bij het schrijven van constructors zoveel mogelijk gebruik te maken van initializer lists om onnodige initialisaties te vermijden en de correctheid van de objecttoestand te garanderen [17](#page=17).
---
# Friend functies en klassen
Dit onderwerp behandelt het concept van friend functies en klassen, die directe toegang hebben tot private leden van een klasse, en de implicaties hiervan voor objectgeoriënteerde principes.
### 4.1 Friend functies
Een friend functie van een klasse is een externe functie die, hoewel geen lidfunctie van de klasse, directe toegang heeft tot de private attributen en lidfuncties van die klasse. Dit betekent dat een friend functie dezelfde rechten heeft als een lidfunctie wat betreft toegang tot private leden [19](#page=19).
#### 4.1.1 Declaratie van een friend functie
Om een functie als vriend van een klasse te declareren, wordt het sleutelwoord `friend` gebruikt binnen de klassedeclaratie. Als de friend functie buiten de klasse wordt gedefinieerd, mag het sleutelwoord `friend` niet worden herhaald en mag er geen `klassennaam::` voor de functienaam staan [19](#page=19).
> **Voorbeeld:**
>
> ```cpp
> class A {
> int i;
> public:
> A(int _i) : i(_i) {}
> friend int fr(const A &); // Declaratie van friend functie
> };
>
> // Definitie van de friend functie
> int fr(const A &a) {
> return a.i; // Directe toegang tot private lid 'i'
> }
>
> int main() {
> A a [7](#page=7);
> cout << fr(a); // Roepen van de friend functie
> return 0;
> }
> ```
> [20](#page=20).
#### 4.1.2 Implicaties voor OO-principes
Volgens strikte objectgeoriënteerde principes zouden enkel lidfuncties directe toegang moeten hebben tot private attributen. Het concept van een friend functie schendt deze basisprincipes enigszins, omdat een externe functie toegang krijgt tot private leden. Desondanks wordt het in C++ gebruikt, met name voor operator overloading en voor efficiëntieverhoging. Het biedt directe toegang tot private attributen van een klasse, wat overhead kan besparen vergeleken met het benaderen via publieke lidfuncties (getters) [21](#page=21).
### 4.2 Friend klassen
Een hele klasse kan ook als vriend van een andere klasse worden aangewezen. Wanneer klasse `Y` wordt gedeclareerd als friend van klasse `Z`, krijgt klasse `Y` directe toegang tot de private leden van klasse `Z`. Het omgekeerde is niet waar; klasse `Z` krijgt geen toegang tot de private leden van klasse `Y` tenzij dit expliciet wordt toegekend. Vriendschap wordt dus toegekend, niet aangenomen [22](#page=22).
> **Voorbeeld:**
>
> ```cpp
> class Z {
> friend class Y; // Klasse Y is friend van klasse Z
> private:
> int secret_z;
> public:
> Z(int s) : secret_z(s) {}
> };
>
> class Y {
> public:
> void access_z_data(const Z& z_obj) {
> // Klasse Y kan direct bij private lid van Z
> cout << "Accessing Z's secret: " << z_obj.secret_z << endl;
> }
> };
> ```
> [22](#page=22).
---
# Operator overloading
Operator overloading makes code more readable and expressive by allowing the redefinition of operators for custom classes [24](#page=24).
### 5.1 Introduction to operator overloading
In C++, certain operators are implicitly defined for objects, such as the assignment operator (`=`) and the address-of operator (`&`). However, most operators can be redefined to work with user-defined types. The general syntax for an overloaded operator, when defined as a member function, is `return_type operator_name([parameter_list]) [const]`. When an operator like `t1 + t2` is used, it is compiled as `t1.operator+(t2)` if it's a member function, or `operator+(t1, t2)` if it's an external function [24](#page=24).
#### 5.1.1 Commonly overloaded operators
A range of operators can be overloaded to enhance class functionality [25](#page=25):
* Arithmetic operators: `+`, `-`, `*`, `/`, `%`
* Comparison operators: `<`, `<=`, `>`, `>=`, `==`, `!=`
* Assignment operators: `=`, `+=`, `-=`, `*=`, `/=`, `%=`
* Increment/Decrement operators: `++`, `--`
* Stream insertion/extraction operators: `<<`, `>>`
* Array subscript operator: `[]`
> **Tip:** The compiler automatically generates the assignment operator (`=`) unless an attribute is a `unique_ptr` (in which case it's deleted). By default, this generated operator performs a shallow copy. If a deep copy is required, especially when a class contains raw pointers, the assignment operator often needs to be overridden [25](#page=25).
### 5.2 Examples of operator overloading
#### 5.2.1 Overloading arithmetic and comparison operators
Consider a `tijd` (time) class with attributes for hours, minutes, and seconds. To support operations like `t0 = t1 + t2`, `t3 = t2 * 2`, and `if (t1 < t2)`, specific operators need to be overloaded. Since the left-hand operand is always an object of the `tijd` class, these are typically implemented as member functions (#page=26, page=27) [26](#page=26) [27](#page=27).
The signatures for such member functions would be:
* `tijd operator+(const tijd &) const;` for addition [27](#page=27).
* `tijd operator*(int) const;` for multiplication by an integer [27](#page=27).
* `bool operator<(const tijd &) const;` for comparison [27](#page=27).
**Example Implementation:**
```cpp
// Addition operator
tijd tijd::operator+(const tijd &t) const {
tijd som(uur + t.uur, min + t.min, sec + t.sec);
return som;
}
// Multiplication operator
tijd tijd::operator*(int factor) const {
return tijd(uur * factor, min * factor, sec * factor);
}
// Less than operator
bool tijd::operator<(const tijd &t) const {
// Convert times to seconds for comparison
return uur * 3600 + min * 60 + sec <
t.uur * 3600 + t.min * 60 + t.sec;
}
```
#### 5.2.2 Overloading unary operators
Unary operators, which operate on a single operand, can also be overloaded. For instance, the unary minus operator (`-`) for the `tijd` class can be implemented as a member function [29](#page=29):
```cpp
tijd tijd::operator-() const {
return tijd(-uur, -min, -sec);
}
```
It's important to note that a unary minus operator can coexist with a binary subtraction operator if both are needed [29](#page=29).
#### 5.2.3 Overloading assignment and compound assignment operators
The assignment operator (`=`) and compound assignment operators (`+=`, `-=`, `*=`, `/=`, `%=`) are crucial for managing object states. The assignment operator is usually provided by default, but it performs a shallow copy. For correct deep copying, especially with raw pointers, overriding the assignment operator is necessary (#page=25, page=30) [25](#page=25) [30](#page=30).
A key characteristic of overloaded assignment and compound assignment operators is that they should return a reference to the object itself (`type&`) to support chaining operations like `a = b = 123;` or `(a = 5)++;`. The `return *this;` statement is used for this purpose (#page=30, page=31) [30](#page=30) [31](#page=31).
**Example for `+=` operator:**
```cpp
tijd& tijd::operator+=(const tijd &t) {
sec += t.sec;
min += t.min;
uur += t.uur;
herbereken(); // Assumed member function to handle overflow
return *this;
}
```
This allows for chained operations like `(nu += t) += t;` [31](#page=31).
#### 5.2.4 Overloading increment and decrement operators
Prefix (`++x`) and postfix (`x++`) increment/decrement operators require distinct implementations. The prefix operator typically returns a reference to the modified object (`tijd& operator++()`), while the postfix operator needs to return a copy of the object's state *before* modification to correctly handle cases like `tijd t2 = t++;` or the more complex `(t++)++;` (#page=32, page=33) [32](#page=32) [33](#page=33).
To distinguish between prefix and postfix versions in the function signature, the postfix operator accepts an `int` argument, which is not used but signals its postfix nature [33](#page=33).
**Example for `++` operator:**
```cpp
// Prefix increment
tijd& tijd::operator++() {
sec++;
herbereken(); // Handles time recalculation
return *this;
}
// Postfix increment
tijd tijd::operator++(int) { // int argument distinguishes it as postfix
tijd temp(*this); // Save current state
sec++;
herbereken();
return temp; // Return saved state
}
```
#### 5.2.5 Overloading the array subscript operator `[]`
The array subscript operator `[]` is often overloaded to provide array-like access to class members, especially for classes that manage collections of data or provide indexed access to internal attributes. It typically returns a reference to the element being accessed (`type&`), allowing direct modification of the element, such as `t1 = t1;` or `t1 ++;` [1](#page=1) [2](#page=2) [36](#page=36).
**Example for `[]` operator:**
```cpp
// In class definition
class tijd {
// ...
int& operator[](int);
};
// Implementation
int& tijd::operator[] (int i) {
if (i==0) return uur;
else if (i==1) return min;
else return sec; // Default to seconds if index is not 0 or 1
}
```
### 5.3 Friend operators
When an operator's left-hand operand is not an object of the class, it cannot be implemented as a member function. For example, in `t1 = 2 * t2;`, the integer `2` is the left-hand operand. There are two common approaches to handle this [38](#page=38):
1. **External operator function:** Define a non-member function that only has access to the class's public interface (e.g., getters and setters) [38](#page=38).
2. **Friend operator function:** Declare the operator as a `friend` within the class. This grants the operator direct access to the class's private members [38](#page=38).
**Example using a friend operator:**
```cpp
class tijd {
// ...
friend tijd operator*(int f, const tijd &t);
};
tijd operator*(int f, const tijd &t) {
return tijd(t.uur * f, t.min * f, t.sec * f);
}
```
A more concise approach, if the binary operator is already defined as `tijd operator*(int f) const;` (as `t * f`), is to define the external operator to simply call the member function: `return t * f;`. In this case, the external operator does not need to be a `friend` [39](#page=39).
### 5.4 Overloading iostream operators (`<<` and `>>`)
The stream insertion (`<<`) and extraction (`>>`) operators are used with `cin`, `cout`, `ifstream`, and `ofstream`, which are the left-hand operands. Consequently, they cannot be implemented as member functions of a user-defined class. They are typically implemented as `friend` functions of the class [40](#page=40).
The syntax for these friend functions is:
* `friend istream& operator>>(istream&, A &)` for input [40](#page=40).
* `friend ostream& operator<<(ostream&, const A &)` for output [40](#page=40).
**Example for `<<` and `>>` operators for `tijd`:**
```cpp
class tijd {
// ...
friend ostream& operator<<(ostream&, const tijd &);
friend istream& operator>>(istream&, tijd &);
};
// Output stream operator
ostream& operator<<(ostream &os, const tijd &t) {
os << setw << setfill('0') << t.uur << ':' [2](#page=2).
<< setw << setfill('0') << t.min << ':' [2](#page=2).
<< setw << setfill('0') << t.sec [2](#page=2);
return os;
}
// Input stream operator
istream& operator>>(istream &is, tijd &t) {
is >> t.uur >> t.min >> t.sec;
t.herbereken(); // Assumed member function to handle potential overflow
return is;
}
```
These operators facilitate easy input and output of custom objects to and from streams (#page=40, page=41) [40](#page=40) [41](#page=41).
### 5.5 Operator signatures overview
A table summarizing the return types and `const`-correctness for various overloaded operators:
| Operator | Return Type | Parameter List | Const (Y/N) |
| :-------------- | :--------------- | :------------- | :---------- |
| `+`, `-`, `*`, `/` | `A` | `const A &` | Yes |
| `-` (unary) | `A` | (none) | Yes |
| `==`, `!=`, `<`, `>`, `<=`, `>=` | `bool` | `const A &` | Yes |
| `=`, `+=`, `-=`, `*=`, `/=`, `%=` | `A&` | `const A &` | No |
| `++`, `--` (prefix) | `A&` | (none) | No |
| `++`, `--` (postfix) | `A` | `int` | No |
| `[]` | `type&` | `int` | No |
---
# Klasse-templates
Dit onderwerp introduceert klasse-templates, waarmee generieke klassen kunnen worden gemaakt die met verschillende gegevenstypen kunnen werken [43](#page=43).
### 6.1 Introductie tot klasse-templates
Klasse-templates maken het mogelijk om een blauwdruk voor een klasse te definiëren die kan werken met verschillende gegevenstypen, zonder dat voor elk type een aparte klasse geschreven hoeft te worden. Dit bevordert code-hergebruik en maakt programma's flexibeler [43](#page=43).
### 6.2 Definitie van een klasse-template
Een klasse-template wordt gedefinieerd met het sleutelwoord `template` gevolgd door de template-parameterlijst, meestal met `typename` of `class` om de type-parameter aan te duiden.
* **Template-declaratie:**
```c++
template
class koppel {
// ... klasse-inhoud ...
};
```
Hier is `T` de type-parameter die staat voor een willekeurig gegevenstype [43](#page=43).
* **Gebruik van de template-parameter in de klasse:**
Binnen de klasse-template worden leden zoals data-leden en functies gedefinieerd met behulp van de type-parameter `T`. Bijvoorbeeld, voor data-leden:
```c++
private:
T first, second;
```
Dit betekent dat de data-leden `first` en `second` van het type `T` zullen zijn, wat bepaald wordt bij het instantiëren van de template [43](#page=43).
* **Constructor en member-functies:**
Constructor-declaraties en member-functies binnen de klasse-template declaratie gebruiken ook de type-parameter. Het is belangrijk om **géén** `` toe te voegen na de klassenaam bij het declareren van leden binnen de klasse-declaratie zelf [43](#page=43).
```c++
public:
koppel(const T &, const T &); // géén toevoegen!!
T get_first() const;
koppel operator+(const koppel &) const;
```
### 6.3 Definitie van template-leden buiten de klasse
De definities van member-functies van een klasse-template worden ook gedefinieerd als templates. Elke definitie buiten de klasse-declaratie vereist opnieuw de `template` prefix en een scope resolutie operator (`::`) met de klasse-naam gevolgd door de type-parameters in accolades.
* **Voorbeeld van een constructor-definitie:**
```c++
template
koppel::koppel(const T &_first, const T &_second)
: first(_first), second(_second) {}
```
Merk op dat hier de scope resolutie `koppel::` correct wordt gebruikt [44](#page=44).
* **Voorbeeld van een member-functie-definitie:**
```c++
template
T koppel::get_first() const { return first; }
```
Ook hier is de `template` prefix en de scope resolutie `koppel::` essentieel [44](#page=44).
* **Essentie:** Elke definitie van een lid van een klasse-template is op zichzelf een template en vereist daarom de `template` prefix [44](#page=44).
### 6.4 Gebruik van klasse-templates
Om een object van een klasse-template te creëren, moet het specifieke gegevenstype tussen punthaken (`< >`) worden gespecificeerd bij de naam van de klasse. Dit proces wordt instantiëren genoemd.
* **Instantiëren van een klasse-template:**
```c++
koppel p(9,2);
```
Hier wordt een instantie van de `koppel` klasse gecreëerd waarbij `T` wordt vervangen door `int` [44](#page=44).
* **Gebruik van geïnstantieerde objecten:**
Na het instantiëren kunnen de member-functies op de gebruikelijke manier worden aangeroepen.
```c++
cout << p.get_first(); // Roept get_first() aan op koppel
```
Dit zal de `first` waarde van het `koppel` object `p` retourneren [44](#page=44).
### 6.5 Vriendfuncties met klasse-templates
Vriendfuncties, zoals de output operator `operator<<`, die toegang nodig hebben tot de private leden van een klasse-template, moeten ook als template worden gedeclareerd. De type-parameter voor de vriendfunctie kan dezelfde zijn als die van de klasse, of een andere type-parameter (bijvoorbeeld `U` in het voorbeeld).
* **Declaratie van een vriend-output operator:**
```c++
template
friend ostream& operator<<(ostream&, const koppel &);
```
Dit is **verplicht** voor niet-lidfuncties die als vriend zijn gedeclareerd in een klasse-template om correct te kunnen werken met de specifieke types [43](#page=43).
> **Tip:** Bij het definiëren van leden van een klasse-template buiten de klasse, onthoud dat de scope resolutie operator altijd de volledige template-naam met de type-parameter moet bevatten, zoals `koppel::`.
> **Voorbeeld:** Als je een `koppel` zou willen gebruiken, zou je dat als volgt instantiëren: `koppel d_pair(1.5, 2.7);`. De compiler genereert dan specifiek de code voor een `koppel` die `double` waarden opslaat.
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Klasse | Een blauwdruk voor het creëren van objecten, die datastructuren (attributen) en methoden (lidfuncties) combineert in één eenheid. |
| Attribuut | Een variabele die bij een klasse hoort en de staat van een object van die klasse beschrijft. |
| Lidfunctie | Een functie die bij een klasse hoort en de operaties definieert die op de attributen van een object kunnen worden uitgevoerd. |
| Constructor | Een speciale lidfunctie die automatisch wordt aangeroepen wanneer een object van een klasse wordt gecreëerd, met als doel het object correct te initialiseren. |
| Destructor | Een speciale lidfunctie die automatisch wordt aangeroepen wanneer een object wordt vernietigd, vaak gebruikt om resources vrij te geven die door de constructor zijn verkregen. |
| Mutator | Een lidfunctie die de attributen van een object wijzigt. |
| Accessor | Een lidfunctie die de attributen van een object leest zonder ze te wijzigen. |
| `const` | Een sleutelwoord dat aangeeft dat een lidfunctie de attributen van het object niet mag wijzigen. Dit wordt vaak gebruikt bij accessors. |
| Scope-operator (`::`) | Een operator die wordt gebruikt om aan te geven tot welke klasse een functie of variabele behoort, vooral bij het definiëren van lidfuncties buiten de klassedeclaratie. |
| `this` | Een pointer die in een lidfunctie verwijst naar het huidige object waarop de functie wordt aangeroepen. |
| Object | Een instantie van een klasse, met zijn eigen set attributen die zijn waarden bevatten. |
| Initializer List | Een lijst die na de parameterlijst van een constructor komt en wordt gebruikt om attributen te initialiseren voordat het lichaam van de constructor wordt uitgevoerd. |
| Copy-constructor | Een constructor die wordt aangeroepen wanneer een nieuw object wordt geïnitialiseerd als een kopie van een bestaand object. |
| `friend` functie | Een functie die niet een lidfunctie van een klasse is, maar wel directe toegang heeft tot de private en protected leden van die klasse. |
| `friend` klasse | Een klasse waarvan alle lidfuncties directe toegang hebben tot de private en protected leden van een andere klasse. |
| Operator overloading | Het herdefiniëren van bestaande operatoren zodat ze kunnen werken met objecten van zelfgedefinieerde klassen. |
| Unaire operator | Een operator die slechts één operand nodig heeft, zoals `-` (negatie) of `++` (increment). |
| Binaire operator | Een operator die twee operanden nodig heeft, zoals `+` (optelling) of `=` (toekenning). |
| Prefix operator | Een operator die vóór de operand wordt geplaatst (bv. `++x`). |
| Postfix operator | Een operator die na de operand wordt geplaatst (bv. `x++`). |
| Klasse-template | Een blauwdruk voor het creëren van klassen die generiek zijn voor datatypes, waardoor dezelfde klassestructuur kan worden gebruikt met verschillende typen gegevens. |
| `typename` | Een sleutelwoord dat in klasse-templates wordt gebruikt om een datatype aan te duiden. |
Cover
H6_ExceptionHandling.pdf
Summary
# Exception handling in C++
Dit hoofdstuk introduceert het concept van exception handling in C++ en demonstreert hoe fouten op te vangen en af te handelen met behulp van try-catch blokken [1](#page=1) [2](#page=2).
## 1.1 Introductie tot exception handling
Traditionele C-stijl foutafhandeling maakt vaak gebruik van speciale returnwaarden om fouten aan te geven. Dit heeft echter nadelen [2](#page=2):
* Het is niet altijd mogelijk om een unieke speciale returnwaarde te definiëren die alle mogelijke foutcondities kan representeren [2](#page=2).
* Als de gebruiker van de functie de returnwaarde niet controleert, kan het programma doorgaan met een ongeldige status, wat leidt tot onvoorspelbaar gedrag [2](#page=2).
Exception handling biedt een robuustere en duidelijkere methode voor foutafhandeling in C++.
## 1.2 Het `throw`-sleutelwoord
Wanneer een fout optreedt die niet elegant afgehandeld kan worden binnen de functie zelf, kan deze een "exceptie" opwerpen met behulp van het `throw`-sleutelwoord [3](#page=3).
* Elk type, inclusief primitieve types zoals `int` en `char`, `string` objecten, of speciaal gedefinieerde exceptie-objecten, kan worden opgeworpen [3](#page=3).
* De functiesignatuur hoeft niet expliciet te vermelden dat er een exceptie kan worden opgeworpen [3](#page=3).
### 1.2.1 Voorbeeld: faculteitsfunctie
De faculteitsfunctie kan worden aangepast om een exceptie op te werpen wanneer een negatief getal wordt meegegeven:
```cpp
int fac(int getal) {
if (getal < 0)
throw "exceptie: negatief getal!"; // Werpt een C-string exceptie op
int res = 1;
for (int i = 2 ; i <= getal ; i++)
res *= i;
return res;
}
```
## 1.3 Het `try-catch`-mechanisme
Om een opgeworpen exceptie op te vangen en af te handelen, wordt gebruik gemaakt van `try-catch`-blokken [4](#page=4).
* De code waarvan verwacht wordt dat deze een exceptie zou kunnen opwerpen, wordt geplaatst binnen een `try`-blok [4](#page=4).
* Na het `try`-blok volgen één of meer `catch`-blokken, die elk een specifiek type exceptie kunnen opvangen [4](#page=4).
### 1.3.1 Vangen van specifieke fouten
Een `catch`-blok kan worden gespecificeerd voor een bepaald type, zoals een C-string:
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (const char *s) { // Vangt C-string excepties op
cout << s << endl;
}
return 0;
}
```
Er kunnen meerdere `catch`-blokken na elkaar staan om verschillende typen excepties te verwerken [4](#page=4).
### 1.3.2 Vangen van alle mogelijke fouten (`catch (...)`)
Als men alle mogelijke excepties wil opvangen, ongeacht hun type, kan een `catch (...)` blok worden gebruikt [5](#page=5).
* Dit "catch-all" blok moet altijd het laatste `catch`-blok zijn [5](#page=5).
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (...) { // Vangt alle excepties op
cout << "oei, een fout" << endl;
}
return 0;
}
```
## 1.4 Opmerkingen over exception handling
* **Variabelen in `catch`-clausules:** De declaratie in de `catch`-clausule hoeft niet noodzakelijk een variabele naam te bevatten; het type alleen is voldoende om een exceptie van dat type op te vangen [6](#page=6).
```cpp
catch (const char *) { cout << "ai ai ai"; }
```
* **`noexcept` sleutelwoord (C++11):** Sinds C++11 kan het sleutelwoord `noexcept` gebruikt worden in zowel de declaratie als de definitie van een functie om aan te geven dat deze functie geen excepties zal opwerpen. Dit kan door de compiler worden gebruikt voor optimalisaties [6](#page=6).
```cpp
int functie() noexcept;
```
## 1.5 Exception klassen in de standaardbibliotheek
De C++ standaardbibliotheek biedt een hiërarchie van exception klassen, die te vinden zijn in de `` header [8](#page=8).
* Sommige van deze klassen worden door bestaande standaardbibliotheekmethoden opgeworpen, zoals `out_of_range` of `invalid_argument` [8](#page=8).
* Andere klassen zijn bedoeld om door de programmeur zelf te worden gebruikt bij het opwerpen van specifieke foutcondities, zoals `domain_error` voor wiskundige domeinfouten [8](#page=8).
De belangrijkste exception klassen omvatten:
* `exception` (de basisklasse) [8](#page=8).
* Afgeleid van `exception`:
* `logic_error` (fouten in de programmalogica) [8](#page=8).
* `runtime_error` (fouten die pas tijdens de uitvoering optreden) [8](#page=8).
* Afgeleid van `logic_error`:
* `domain_error`
* `invalid_argument`
* `length_error`
* `out_of_range`
* Afgeleid van `runtime_error`:
* `overflow_error`
* `range_error`
* `underflow_error`
* Andere speciale exception klassen zoals `bad_exception`, `bad_typeid`, `bad_cast`, `bad_alloc` [8](#page=8).
### 1.5.1 Voorbeeld: Gebruik van `out_of_range`
Wanneer bijvoorbeeld een index buiten de grenzen van een `std::vector` wordt benaderd met de `.at()` methode, wordt een `out_of_range` exceptie opgeworpen [9](#page=9).
```cpp
#include
#include
#include
using namespace std;
int main() {
vector v = {8, 10, 12};
try {
// Poging om een element buiten de vector te benaderen
int i = v.at(v.size()); // v.size() is 3, geldige indices zijn 0, 1, 2
} catch (const out_of_range& e) {
// Vangt de out_of_range exceptie op
cout << "Exceptie: " << e.what() << endl; // e.what() geeft de beschrijving van de fout
}
return 0;
}
```
De methode `.what()` van een exception object geeft een beschrijvende string van de fout [9](#page=9).
## 1.6 Zelfgemaakte exception klassen
Het is mogelijk en vaak wenselijk om eigen exception klassen te definiëren, meestal door te erven van bestaande standaard exception klassen zoals `runtime_error` of `logic_error`. Dit zorgt voor een gestructureerde en herkenbare foutafhandeling [10](#page=10).
* De basisklasse `exception` heeft geen constructor die een `std::string` of C-style string als parameter accepteert. Daarom is het aan te raden om te erven van `runtime_error` of `logic_error` [10](#page=10).
### 1.6.1 Voorbeeld: `file_error` klasse
Een `file_error` klasse kan worden gemaakt om fouten gerelateerd aan bestandsoperaties te signaleren:
```cpp
// file_error.h
#include
#include
using namespace std;
class file_error : public runtime_error {
public:
// Constructor die de standaardfoutmelding gebruikt
file_error() : runtime_error("can't open file") {}
// Constructor die een specifieke foutmelding accepteert
file_error(const string &what) : runtime_error(what) {}
};
```
Deze zelfgemaakte klasse kan vervolgens worden opgeworpen en opgevangen:
```cpp
// vb_excep3.cpp
#include "file_error.h"
#include
#include
#include
using namespace std;
void openFile(const string &fname, ifstream& in) {
in.open(fname);
if (!in) {
// Werpt een eigen file_error exceptie op met een specifieke melding
throw file_error("Can't open file " + fname);
}
}
int main() {
string file_name = "niet_bestaand.txt";
ifstream inv;
try {
openFile(file_name, inv);
// Verdere verwerking van het bestand...
inv.close();
} catch (const file_error& fe) { // Vangt de zelfgemaakte exceptie op
cout << fe.what() << endl; // Toont de foutmelding
}
return 0;
}
```
> **Tip:** Door eigen exception klassen te definiëren, wordt de code leesbaarder en is het voor andere ontwikkelaars duidelijker welke soorten fouten er door een specifieke module kunnen worden afgehandeld.
## 1.7 Het `finally`-concept (conceptueel)
Hoewel C++ geen expliciet `finally`-sleutelwoord heeft zoals sommige andere talen, kan een vergelijkbaar effect worden bereikt door gebruik te maken van RAII (Resource Acquisition Is Initialization). Dit wordt vaak geïmplementeerd met behulp van destructors van objecten die binnen een `try`-blok worden aangemaakt. Wanneer een exceptie wordt opgeworpen, worden de destructors van de automatische objecten op de call stack uitgevoerd in omgekeerde volgorde van hun creatie, wat garandeert dat resources (zoals geopende bestanden of geheugen) worden opgeruimd [11](#page=11).
> **Tip:** Gebruik destructors om de opruiming van resources te garanderen, zelfs als er excepties optreden. Dit is de C++ manier om het 'finally'-gedrag te bereiken.
---
# Opvangen van specifieke en algemene fouten
Het opvangen van fouten in code is essentieel voor robuuste softwareontwikkeling, waarbij het mogelijk is om zowel specifieke als algemene fouten af te handelen. Dit wordt bereikt door het gebruik van meerdere `catch`-blokken voor specifieke fouttypen of een `catch-all` handler voor alle overige fouten [4](#page=4) [5](#page=5).
### 2.1 Opvangen van specifieke fouttypen
Wanneer specifieke foutcondities verwacht worden, kunnen meerdere `catch`-blokken na een `try`-blok worden geplaatst. Elk `catch`-blok is specifiek ontworpen om een bepaald type exception af te handelen. Dit maakt het mogelijk om gedifferentieerd te reageren op diverse fouten die tijdens de uitvoering kunnen optreden [4](#page=4).
**Voorbeeld:**
Stel dat een functie `fac` (waarschijnlijk voor faculteit) een `const char *` exception werpt bij een ongeldige invoer. In dit geval kan een `catch`-blok worden gedefinieerd om dit specifieke type exception op te vangen.
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (const char *s) {
cout << s << endl;
}
return 0;
}
```
Dit voorbeeld toont hoe de code specifieke C-string fouten kan opvangen [4](#page=4).
### 2.2 Opvangen van alle mogelijke fouten
Naast het specifiek afhandelen van bekende fouttypen, is het ook mogelijk om alle overige, onbekende exceptions te vangen met behulp van een `catch-all` handler. Deze handler wordt aangeduid met drie puntjes (`...`) binnen de `catch`-declaratie [5](#page=5).
**Belangrijk:** De `catch-all` handler moet altijd het **laatste** `catch`-blok zijn na de meer specifieke handlers. Als deze eerder wordt geplaatst, zullen de specifiekere blokken nooit bereikt worden [5](#page=5).
**Voorbeeld:**
In het volgende codefragment wordt getoond hoe een `catch-all` handler gebruikt kan worden om elke exception die niet door eerdere `catch`-blokken wordt afgehandeld, op te vangen.
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (...) {
cout << "oei, een fout" << endl;
}
return 0;
}
```
Deze opzet vangt elke mogelijke fout op, ongeacht het type, en geeft een generieke foutmelding weer. Dit is nuttig voor het garanderen dat er altijd een fallback is voor onvoorziene problemen [5](#page=5).
---
# Standaard en zelfgemaakte exception klassen
Dit onderwerp behandelt het gebruik van ingebouwde exception klassen uit de C++ standaardbibliotheek en de constructie van eigen, aangepaste exception klassen die afgeleid zijn van `runtime_error` of `logic_error` [8](#page=8).
### 3.1 Overzicht van exception klassen
De C++ standaardbibliotheek biedt een hiërarchie van exception klassen, georganiseerd onder de basisklasse `exception`. Deze klassen worden gebruikt om specifieke foutcondities tijdens de uitvoering van een programma te signaleren [8](#page=8).
#### 3.1.1 Categorieën van standaard exceptions
De standaard exceptions kunnen grofweg worden onderverdeeld in twee hoofdcategorieën, gebaseerd op de klasse waarvan ze afgeleid zijn:
* **`logic_error`**: Deze exceptions signaleren fouten die logischerwijs tijdens de uitvoering van het programma ontdekt hadden kunnen worden, zelfs zonder daadwerkelijk optreden van foutieve systeemtoestanden. Voorbeelden hiervan zijn [8](#page=8):
* `domain_error`: Geeft aan dat een functie een argument heeft ontvangen dat buiten het toegestane domein valt [8](#page=8).
* `invalid_argument`: Geeft aan dat een functie een ongeldig argument heeft ontvangen [8](#page=8).
* `length_error`: Geeft aan dat een poging is gedaan om een object te creëren dat te groot is [8](#page=8).
* `out_of_range`: Geeft aan dat een index of positie buiten de geldige bereiken valt [8](#page=8).
* `overflow_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te groot is om gerepresenteerd te worden [8](#page=8).
* `range_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te klein is om gerepresenteerd te worden [8](#page=8).
* `underflow_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te klein is om gerepresenteerd te worden [8](#page=8).
* **`runtime_error`**: Deze exceptions signaleren fouten die optreden tijdens de uitvoering van het programma en die niet noodzakelijkerwijs direct voortvloeien uit de logica van de algoritmes, maar eerder uit de interactie met de omgeving of onverwachte toestanden. Voorbeelden zijn [8](#page=8):
* `bad_alloc`: Wordt opgeworpen bij een mislukte geheugenallocatie [8](#page=8).
* `bad_cast`: Wordt opgeworpen bij een mislukte `dynamic_cast` naar een referentie [8](#page=8).
* `bad_typeid`: Wordt opgeworpen bij een mislukte `typeid` operatie [8](#page=8).
* `bad_exception`: Wordt opgeworpen wanneer een exception wordt gegenereerd die niet gespecificeerd is in de `noexcept` specificatie [8](#page=8).
> **Tip:** Veel standaardmethoden, zoals die van `std::vector` (bv. `.at()`), kunnen automatisch exceptions zoals `out_of_range` opwerpen wanneer incorrecte indexen worden gebruikt [9](#page=9).
#### 3.1.2 Gebruik van bestaande exception klassen
Om standaard exceptions te gebruiken, is het noodzakelijk de `` header in te sluiten. Een `try-catch` blok is de standaardmethode om exceptions te verwerken. Binnen het `try` blok wordt code geplaatst die mogelijk een exception kan opwerpen. In het `catch` blok wordt specifiek de exception afgehandeld [9](#page=9).
> **Example:** Het opvangen van een `out_of_range` exception bij het benaderen van een `std::vector` buiten zijn grenzen.
> ```cpp
> #include
> #include
> #include
>
> using namespace std;
>
> int main() {
> vector v = {8, 10, 12};
> try {
> // Poging om een element buiten het bereik van de vector te benaderen
> int i = v.at(v.size()); // v.size() is 3, index 3 is buiten bereik voor vector met grootte 3
> } catch (const out_of_range& e) {
> // De exception wordt hier opgevangen en de melding wordt uitgeprint
> cout << "Exceptie: " << e.what() << endl;
> }
> return 0;
> }
> ```
> In dit voorbeeld zal de `cout` regel "Exceptie: vector::_M_range_check: __n (which is 3) >= this->size() (which is 3)" (of een vergelijkbare melding afhankelijk van de compiler) uitvoeren [9](#page=9).
### 3.2 Zelfgemaakte exception klassen
Hoewel de standaardbibliotheek een breed scala aan exceptions biedt, kan het noodzakelijk zijn om eigen exception klassen te definiëren voor specifieke foutcondities die uniek zijn voor de applicatie [10](#page=10).
#### 3.2.1 Principes voor het maken van zelfgemaakte exceptions
Zelfgemaakte exception klassen worden typisch afgeleid van bestaande standaard exception klassen, zoals `runtime_error` of `logic_error`. Dit zorgt voor een consistente foutafhandeling en maakt het mogelijk om de standaard mechanismen van C++ exceptions te benutten. De basisklasse `exception` zelf heeft geen constructor die een `std::string` of C-string als parameter accepteert, vandaar de noodzaak om af te leiden van `runtime_error` of `logic_error`, die deze functionaliteit wel bieden [10](#page=10).
#### 3.2.2 Implementatie van een zelfgemaakte exception
Een zelfgemaakte exception klasse kan worden gecreëerd door de gewenste standaard exception klasse te overerven. De constructor van de zelfgemaakte klasse kan vervolgens de constructor van de basisklasse aanroepen om een specifieke foutmelding te initialiseren [10](#page=10).
> **Example:** Een `file_error` klasse, afgeleid van `runtime_error`, voor het signaleren van problemen bij het openen van bestanden.
> ```cpp
> // file_error.h
> #include
> #include
>
> using namespace std;
>
> class file_error : public runtime_error {
> public:
> // Default constructor met een standaard melding
> file_error() : runtime_error("can't open file") {}
>
> // Constructor die een specifieke melding accepteert
> file_error(const string &what) : runtime_error(what) {}
> };
> ```
In een functie die mogelijk een `file_error` kan veroorzaken, kan deze exception worden opgeworpen met behulp van de `throw` sleutelwoord wanneer de foutconditie optreedt [11](#page=11).
> **Example:** Een functie `openFile` die een `file_error` opwerpt als het bestand niet geopend kan worden.
> ```cpp
> // vb_excep3.cpp
> #include "file_error.h" // Veronderstelt dat file_error.h hierboven is gedefinieerd
> #include
> #include
> #include
>
> using namespace std;
>
> void openFile(const string &fname, ifstream& in) {
> in.open(fname);
> if (!in) {
> // Opwerpen van de zelfgemaakte exception met een specifieke melding
> throw file_error("Can't open file " + fname);
> }
> }
>
> int main() {
> string file_name = "nonexistent.txt";
> ifstream inv;
> try {
> openFile(file_name, inv);
> // Verdere code als het bestand succesvol geopend is
> // ...
> inv.close();
> } catch (const file_error& fe) {
> // Opvangen van de specifieke file_error
> cout << "Bestandsfout: " << fe.what() << endl;
> } catch (const exception& e) {
> // Opvangen van andere mogelijke exceptions
> cout << "Andere fout: " << e.what() << endl;
> }
> return 0;
> }
> ```
> Wanneer `nonexistent.txt` niet bestaat, zal de output zijn: "Bestandsfout: Can't open file nonexistent.txt" [11](#page=11).
Door eigen exception klassen te definiëren, kan de code robuuster worden gemaakt door specifieke foutcondities duidelijk te signaleren en te behandelen, wat leidt tot beter leesbare en onderhoudbare programma's.
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Exception handling | Een mechanisme in programmeertalen om fouten of uitzonderlijke condities tijdens de uitvoering van een programma op te vangen en af te handelen. Dit voorkomt dat het programma crasht. |
| Opwerpen (throw) | Het proces waarbij een foutconditie wordt aangegeven en een exception object wordt gecreëerd en doorgegeven in het programma. |
| Opvangen (catch) | Het proces waarbij een exception object dat is opgeworpen, wordt onderschept en behandeld door een specifiek blok code, vaak na een try-blok. |
| Try-blok | Een codeblok waarin potentieel foutgevoelige operaties worden uitgevoerd. Als er een exception wordt opgeworpen binnen dit blok, wordt de uitvoering ervan onmiddellijk onderbroken. |
| Catch-blok | Een codeblok dat wordt uitgevoerd wanneer een exception van een bepaald type wordt opgevangen. Meerdere catch-blokken kunnen na een try-blok komen om verschillende soorten exceptions af te handelen. |
| Catch-all handler | Een speciaal catch-blok (aangegeven met `...`) dat elke exception kan opvangen, ongeacht het type. Dit wordt meestal als laatste catch-blok geplaatst. |
| `noexcept` | Een sleutelwoord in C++ (vanaf C++11) dat aangeeft dat een functie geen exceptions zal opwerpen. Dit kan door de compiler worden gebruikt voor optimalisaties. |
| `stdexcept` | Een headerbestand in de C++ Standard Library dat een set standaard exception klassen bevat, zoals `runtime_error` en `logic_error`. |
| `runtime_error` | Een standaard exception klasse die fouten aangeeft die optreden tijdens de uitvoering van het programma, bijvoorbeeld het niet kunnen openen van een bestand. |
| `logic_error` | Een standaard exception klasse die fouten aangeeft die logische problemen in de code zelf impliceren, bijvoorbeeld een ongeldige argumentwaarde. |
| `out_of_range` | Een standaard exception klasse die wordt opgeworpen wanneer een poging wordt gedaan om toegang te krijgen tot een element buiten de geldige grenzen van een container, zoals een vector of string. |
| `invalid_argument` | Een standaard exception klasse die wordt opgeworpen wanneer een functie een argument ontvangt dat ongeldig is, zelfs als het van het juiste type is. |
| `exception` | De basisklasse voor alle standaard exceptions in C++. Andere exception klassen erven hiervan af. |
Cover
H7_Afsluiter.pdf
Summary
# Automatische type-afleiding met auto
Het `auto` sleutelwoord in C++11 faciliteert automatische type-afleiding, wat vooral handig is bij het werken met complexe datatypen [3](#page=3).
### 1.1 Werking van `auto`
Sinds C++11 kan het `auto` sleutelwoord worden gebruikt wanneer een variabele declaratie vergezeld wordt van een initialisatie. Het `auto` sleutelwoord instrueert de compiler om het type van de variabele af te leiden op basis van de initializer [3](#page=3).
#### 1.1.1 Syntaxis en voorbeelden
De basis syntaxis is:
`auto variable_name = initializer;` [3](#page=3).
Hier zijn enkele eenvoudige voorbeelden:
* `auto a = 0;` zal de variabele `a` declareren als een `int` [3](#page=3).
* `auto b = 'a';` zal de variabele `b` declareren als een `char` [3](#page=3).
> **Tip:** Het gebruik van `auto` is optioneel; het is niet verplicht. Echter, bij het initialiseren van een variabele met `auto`, is het type van de initializer cruciaal voor de afleiding [3](#page=3).
### 1.2 Nut van `auto` bij complexe types
Het ware nut van `auto` komt tot uiting bij het omgaan met complexe datatypen, zoals die vaak voorkomen in de Standard Template Library (STL). Het declareren van variabelen van deze complexe types kan leiden tot lange en potentieel foutgevoelige declaraties [3](#page=3).
#### 1.2.1 STL iteratoren
Voorheen moest men bij STL containers zoals `std::set` of `std::map` expliciet het type van iteratoren declareren. Dit type kan erg lang en moeilijk te lezen zijn, vooral wanneer templates gebruikt worden. Beschouw de volgende declaratie zonder `auto`:
```cpp
template
void proc(const std::set &s) {
typename std::set::const_iterator ci = s.begin();
// ... verdere code ...
}
```
Hier moet `typename std::set::const_iterator` de volledige type-declaratie zijn [3](#page=3).
Met `auto` kan deze declaratie aanzienlijk vereenvoudigd worden:
```cpp
template
void proc(const std::set &s) {
auto ci = s.begin();
// ... verdere code ...
}
```
De compiler leidt nu af dat `ci` het type `typename std::set::const_iterator` moet hebben, gebaseerd op de initializer `s.begin()` [3](#page=3).
#### 1.2.2 Range-based for loops
Een ander belangrijk toepassingsgebied is de range-based for loop, geïntroduceerd in C++11. Bij het doorlopen van complexe datastructuren, zoals een map van integers naar vectors van strings, kan het afleiden van het type van het element dat per iteratie wordt verwerkt, een uitkomst zijn.
Beschouw de volgende declaratie van een map:
`std::map> m;` [3](#page=3).
Een traditionele for-loop om elk element `el` te verwerken, zou de volgende declaratie vereisen:
`for (const std::pair>& el: m)` [3](#page=3).
Met `auto` wordt dit aanzienlijk leesbaarder:
`for (const auto &el: m) {... }` [3](#page=3).
De compiler zal automatisch het type van `el` afleiden als `const std::pair>&`, wat de code compacter en minder vatbaar voor typfouten maakt [3](#page=3).
> **Tip:** Het gebruik van `auto` in combinatie met een referentie (`&`) en `const` is vaak een goede praktijk, vooral in range-based for loops, om onnodige kopieën te voorkomen en de efficiëntie te waarborgen [3](#page=3).
---
# Wijzigingen in initialisatiesyntax
Dit onderwerp behandelt de introductie van een uniforme initialisatiesyntax met accolades in C++11, inclusief de mogelijkheden voor in-klasse initialisatie van attributen [5](#page=5).
### 2.1 Traditionele initialisatiesyntax
Vóór C++11 kende C++ verschillende, niet-uniforme notaties voor initialisatie:
* **Constructor-initialisatie met haakjes:** Dit werd gebruikt voor objecten, bijvoorbeeld `string s("hello");` [5](#page=5).
* **Accolades voor structs en arrays:** Voor het initialiseren van structs en arrays werden accolades gebruikt, zoals `int t = {0,1,2,3};` [4](#page=4) [5](#page=5).
* **Initializer list bij definitie van constructoren:** Class-leden konden worden geïnitialiseerd via een initializer list in de constructor-definitie, bijvoorbeeld `A(): x {}` [5](#page=5).
### 2.2 Uniforme initialisatiesyntax met accolades in C++11
C++11 introduceerde een uniforme initialisatiesyntax met accolades om de inconsistenties te verhelpen. Deze nieuwe syntax, ook wel *brace-initialization* genoemd, kan voor diverse typen worden gebruikt [6](#page=6):
* **Class-leden initialisatie:** Accolades kunnen worden gebruikt om leden van een klasse te initialiseren, zowel met default-waarden als bij het creëren van instanties [6](#page=6).
**Voorbeeld:**
> In het volgende voorbeeld worden `x` en `y` geïnitialiseerd met accolades binnen de constructor en de klasse definitie:
> ```cpp
> class A {
> int x;
> int y [4](#page=4);
> public:
> A(int _x=0) : x{_x}, y{1,2,3,4} {}
> };
> ```
> [6](#page=6).
* **Variabele-initialisatie:** Variabelen van basistypen en standaard-containerklassen kunnen met accolades worden geïnitialiseerd [6](#page=6).
**Voorbeeld:**
> In `main` wordt een instantie `a` van `A` geïnitialiseerd met `A a{3};` en een integer `b` met `int b{2};`. Een `std::vector` wordt geïnitialiseerd met `vector v = {10,20};` [6](#page=6).
* **Belangrijke nuanceringen:**
* `A a();` definieert een functie genaamd `a` die geen argumenten neemt en een `A` retourneert, en is **fout** als bedoeling om een object te creëren en te initialiseren [6](#page=6).
* `A a{};` creëert een object van type `A` en initialiseert de leden met hun default-waarden of via de constructor indien van toepassing (value-initialization voor fundamentele typen) [6](#page=6).
* `A a;` is een standaard objectcreatie en kan ook als correct worden beschouwd [6](#page=6).
* `int b = 2;` is de traditionele syntax, die nu kan worden vervangen door `int b{2};` [6](#page=6).
* `vector v{10,20};` is een directe initialisatie van de vector met de opgegeven elementen [6](#page=6).
* `vector v({10,20});` is ook een geldige vorm van initialisatie voor `std::vector` [6](#page=6).
* `vector v(10,20);` creëert een vector met 10 elementen, elk met de waarde 20, wat verschilt van het bedoelde gebruik van de uniforme initialisatiesyntax voor een lijst van elementen [6](#page=6).
### 2.3 In-klasse initialisatie van attributen
Sinds C++11 is het toegestaan om attributen (leden van een klasse) direct in de klasse-definitie te initialiseren. Deze initialisatie gebeurt voordat de constructor wordt aangeroepen [7](#page=7).
* **Syntax:** Attributen kunnen worden geïnitialiseerd met een gelijkteken (`=`) gevolgd door een waarde, of met accolades (`{}`) gevolgd door een waarde [7](#page=7).
**Voorbeeld:**
> In de klasse `A` worden de attributen `x` en `y` direct geïnitialiseerd:
> ```cpp
> class A {
> private:
> int x = 7;
> int y{9};
> public:
> A() {}
> };
> ```
> Hier wordt `x` geïnitialiseerd tot `7` en `y` tot `9` [7](#page=7).
* **Let op:** De traditionele constructor-syntax met haakjes `int y;` is **niet toegestaan** voor in-klasse initialisatie van leden; dit zou een compilerfout opleveren [7](#page=7) .
> **Tip:** De mogelijkheid tot in-klasse initialisatie vereenvoudigt de code en zorgt ervoor dat leden van een object altijd een gedefinieerde begintoestand hebben, zelfs als de constructor geen specifieke initialisatielijst bevat [7](#page=7).
---
# Defaulted en deleted lidfuncties
Dit onderwerp behandelt twee speciale functies in C++: defaulted lidfuncties, die de compiler instrueren om de standaardimplementatie te genereren, en deleted lidfuncties, die automatische generatie expliciet verbieden [9](#page=9).
### 3.1 Defaulted lidfuncties
Een defaulted lidfunctie, aangegeven met `= default;`, instrueert de compiler expliciet om de standaardimplementatie van die lidfunctie te genereren. Dit is nuttig wanneer je expliciet wilt aangeven dat je de door de compiler voorgestelde standaardgedrag wilt behouden, bijvoorbeeld voor constructors, copy-constructors, move-constructors, en destructors [9](#page=9).
**Voorbeeld van een defaulted lidfunctie:**
> **Example:**
> ```cpp
> class A {
> public:
> A() = default; // Expliciteert de default constructor
> A(const A&) = default; // Expliciteert de default copy constructor
> };
> ```
> In dit voorbeeld wordt de compiler gevraagd om de standaardimplementatie van de default constructor en de copy constructor te genereren voor klasse `A` [9](#page=9).
### 3.2 Deleted lidfuncties
Een deleted lidfunctie, aangegeven met `= delete;`, verbiedt de compiler expliciet de automatische generatie van die lidfunctie. Dit is het tegenovergestelde van een defaulted lidfunctie. Het wordt gebruikt om te voorkomen dat bepaalde operaties worden uitgevoerd, bijvoorbeeld om te verhinderen dat een object wordt gekopieerd of verplaatst, of om te zorgen voor expliciete controle over de levensduur van objecten [9](#page=9).
**Gevolgen van een deleted lidfunctie:**
Wanneer een lidfunctie als `delete` is gemarkeerd, zal elke poging om die functie aan te roepen resulteren in een compilatie-fout [9](#page=9).
**Voorbeeld van een deleted lidfunctie:**
> **Example:**
> ```cpp
> class A {
> public:
> A(const A&) = delete; // Verhindert kopiëren van objecten van klasse A
> };
> ```
> In dit geval zal de volgende code leiden tot een compilatie-fout:
> ```cpp
> A a;
> A b(a); // Compilatie-fout, omdat de copy constructor is gedelete
> ```
> Dit illustreert hoe `= delete;` effectief voorkomt dat een object wordt aangemaakt via de gedelete functie [9](#page=9).
---
# Move constructor en move operator
De move constructor en move operator in C++11 bieden een efficiënte manier om resources over te dragen in plaats van ze te kopiëren, wat cruciaal is voor prestatieoptimalisatie [12](#page=12) [14](#page=14).
### 4.1 Probleemstelling rond kopiëren
Standaard kopieerconstructeurs en toekenningsoperatoren kopiëren de attributen van een object. Dit kan inefficiënt zijn, vooral bij grote datastructuren, omdat het tijd en geheugen kost. Bij het kopiëren van objecten die resource-eigenaar zijn (zoals dynamisch toegewezen geheugen), is het standaardgedrag van de kopieerconstructeur en toekenningsoperator vaak onvoldoende, omdat dit leidt tot "gedeelde structuren" in plaats van onafhankelijke kopieën [11](#page=11) [15](#page=15) [17](#page=17).
> **Tip:** Een `std::vector` die aan een ander `std::vector` wordt toegewezen, voert een diepe kopie uit. Dit proces kan kostbaar zijn [11](#page=11).
### 4.2 De introductie van "move" semantiek
Sinds C++11 zijn de "move constructor" en de "move operator" geïntroduceerd. Hun doel is niet om attributen te kopiëren, maar om de attributen van het originele object over te nemen, vergelijkbaar met hoe `std::unique_ptr` werkt. Dit proces wordt ook wel "schaken" van attributen genoemd [12](#page=12).
#### 4.2.1 Gebruik van de move constructor en move operator
* **Move constructor:** Wordt gebruikt bij het initialiseren van een nieuw object met een bestaand object, met behulp van `std::move`.
```c++
A b(std::move(a));
```
* **Move operator:** Wordt gebruikt bij het toewijzen van de waarde van een bestaand object aan een ander bestaand object, met behulp van `std::move`.
```c++
c = std::move(b);
```
#### 4.2.2 Voorbeeldgebruik
In het volgende voorbeeld wordt `std::move` gebruikt bij `push_back` en de toekenningsoperator om de resources van `s1` over te dragen:
```c++
std::vector> v;
for (int i = 1; i <= 3; ++i) {
std::set s1, s2;
// ... iets toevoegen aan s1
v.push_back(std::move(s1)); // Move constructor van vector wordt aangeroepen
// ... opnieuw iets toevoegen aan s1
s2 = std::move(s1); // Move operator wordt aangeroepen
}
```
Na het gebruik van de move operator wordt het originele object (`s1` in dit geval) leeg achtergelaten. Sinds C++11 ondersteunen veel standaardcontainerfuncties, zoals `push_back`, move semantiek [13](#page=13).
### 4.3 Implementatie van move constructor en move operator
Standaard voorzien C++-compilers een move constructor en move operator. Deze standaardimplementaties passen de move constructor/operator toe op alle attributen. Het standaardgedrag voor primitieve types en raw pointers is echter kopiëren, niet verplaatsen [14](#page=14).
#### 4.3.1 Overerving voor raw pointers
Als een klasse een raw pointer als attribuut heeft, is het standaardgedrag van de copy/move constructor en operator niet optimaal, omdat dit kan leiden tot een gedeelde structuur. Om dit op te lossen, moet men deze zelf implementeren [15](#page=15) [17](#page=17):
* **Move constructor bij raw pointer:** Neem een ondiepe kopie van de pointer en zet de originele pointer op `nullptr`.
```c++
// Voorbeeld: Move constructor voor class A met raw pointer pA
A::A(A&& a) : vA(std::move(a.vA)), grA(a.grA), pA(a.pA) {
a.grA = 0;
a.pA = nullptr;
}
```
De move constructor is hierdoor eenvoudiger en efficiënter dan de copy constructor [16](#page=16).
* **Move operator bij raw pointer:** Neem de resources over, verwijder de eigen resources, en zet de originele pointer op `nullptr`.
```c++
// Voorbeeld: Move operator voor class A met raw pointer pA
A& A::operator=(A&& a) {
if (this != &a) {
vA = std::move(a.vA);
delete[] tA; // Verondersteld dat tA de eigen dynamische array is
grA = a.grA;
pA = a.pA;
a.grA = 0;
a.pA = nullptr;
}
return *this;
}
```
#### 4.3.2 Overerving voor primitieve types en andere objecten
* **Primitieve types:** Neem een kopie en zet eventueel de originele variabele op 0 [14](#page=14).
* **Andere objecten:** Gebruik de corresponderende move constructor/operator van het attribuut zelf [14](#page=14).
> **Tip:** De move operator wordt automatisch aangeroepen als het rechterlid een 'à la minute' aangemaakt object is (r-value) [19](#page=19).
### 4.4 De "Rule of Five"
Vóór C++11 kende men de "Rule of Three": als een klasse een aangepaste destructor, kopieerconstructeur of kopieer toekenningsoperator vereiste, vereiste deze vrijwel zeker alle drie [21](#page=21).
Sinds C++11 is dit uitgebreid naar de "Rule of Five": als je een van de standaardoperaties (destructor, kopieerconstructeur, kopieer toekenningsoperator, move constructor, move toekenningsoperator) definieert of `delete`, dan moet je ze allemaal definiëren of `delete`. Dit is om consistentie en correct resource management te garanderen [20](#page=20) [21](#page=21).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Automatische type-afleiding (auto) | In C++11 introduceert het `auto` sleutelwoord de mogelijkheid om het type van een variabele automatisch te laten afleiden op basis van de initialisatiewaarde, wat vooral handig is bij complexe datatypes. |
| Initialisatiesyntax | De manier waarop variabelen en objecten een beginwaarde krijgen bij hun declaratie. C++11 introduceerde uniforme notatie met accolades voor een consistentere aanpak. |
| Uniforme initialisatiesyntax | Een methode geïntroduceerd in C++11 die accolades `{}` gebruikt voor de initialisatie van diverse datatypen, inclusief klassen, arrays en standaardcontainers, wat zorgt voor een eenduidiger code. |
| In-klasse initialisatie | De mogelijkheid om attributen van een klasse direct bij hun declaratie binnen de klassedefinitie te initialiseren, een functionaliteit die is toegevoegd sinds C++11. |
| Defaulted lidfunctie | Een speciaal aangegeven lidfunctie (zoals een constructor of operator) die de compiler expliciet instrueert om de standaard door de compiler gegenereerde implementatie te gebruiken. |
| Deleted lidfunctie | Een lidfunctie die expliciet is gemarkeerd als ` = delete`, wat de compiler verbiedt om deze functie te genereren of te gebruiken, om ongewenst gedrag te voorkomen. |
| Copy constructor | Een constructor die een nieuw object initialiseert met de waarden van een reeds bestaand object van hetzelfde type, doorgaans door diepe kopieën van de attributen te maken. |
| Move constructor | Een constructor die de bronnen van een ander object (het *rvalue* object) overneemt in plaats van ze te kopiëren, waardoor een efficiëntere overdracht van resources mogelijk wordt en het originele object vaak in een lege of verplaatste staat achterblijft. |
| Toekenningsoperator (operator=) | Een operator die wordt gebruikt om de waarde van een reeds bestaand object toe te kennen aan een ander object van hetzelfde type, waarbij de bestaande inhoud van het doelobject doorgaans wordt verwijderd of beheerd. |
| Move operator | Een operator die de bronnen van een ander object (het *rvalue* object) overneemt voor toekenning aan een bestaand object, vergelijkbaar met de move constructor, maar dan voor toekenning. |
| The Big Three | Verwijst naar de drie speciale lidfuncties (destructor, copy constructor, copy assignment operator) die historisch gezien vaak tegelijkertijd door de programmeur moesten worden gedefinieerd om correct geheugenbeheer te garanderen vóór C++11. |
| The Big Five | Sinds C++11 wordt dit concept uitgebreid met de move constructor en move operator, wat resulteert in vijf speciale lidfuncties (destructor, copy constructor, copy assignment operator, move constructor, move operator) die symmetrisch gedefinieerd of gedelete moeten worden. |
| Rule of Three | Een programmeerrichtlijn die stelt dat als een klasse een aangepaste destructor, copy constructor of copy assignment operator vereist, deze waarschijnlijk alle drie nodig heeft om correcte resource management te waarborgen. |
| Rule of Five | Een uitbreiding van de "Rule of Three" in C++11, die aangeeft dat als een van de vijf speciale lidfuncties (destructor, copy constructor, copy assignment operator, move constructor, move assignment operator) wordt gedefinieerd of gedelete, de andere vier ook expliciet moeten worden gedefinieerd of gedelete. |
| Rule of Zero | Een modern programmeerprincipe in C++ dat adviseert om aangepaste destructors, copy/move constructors of assignment operators te vermijden door gebruik te maken van klassen die resource management correct afhandelen (bv. RAII). Klassen die dit niet doen, zouden geen aangepaste speciale lidfuncties moeten hebben. |
| Rvalue | Een tijdelijk object dat niet direct kan worden toegewezen aan een lvalue. Move semantiek maakt gebruik van rvalues om efficiënte overdracht van resources mogelijk te maken. |
| Lvalue | Een expressie die verwijst naar een geheugenlocatie die kan worden geïdentificeerd, zoals een variabele. Lvalues kunnen worden toegewezen aan nieuwe waarden. |
Cover
hoorcollege1a_object_orientatie - Tagged.pdf
Summary
# Inleiding tot objectgeoriënteerd programmeren
Dit onderwerp introduceert de fundamentele principes van objectgeoriënteerd programmeren (OOP), waarbij de nadruk ligt op het gebruik van objecten en klassen als de primaire bouwstenen voor softwareontwikkeling, in tegenstelling tot functionele programmeerparadigma's.
### 1.1 Basisconcepten van objectgeoriënteerd programmeren
Objectgeoriënteerd programmeren (OOP) beschouwt software niet als een verzameling functies, maar als een interactie tussen objecten. Deze objecten zijn geïnspireerd op entiteiten uit de "echte" wereld en combineren zowel gegevens als bijbehorende acties. Het algemene type of blauwdruk voor een object wordt een klasse genoemd. In OOP wordt de software ontworpen door klassen te definiëren, waarna objecten van deze klassen efficiënt kunnen worden gecreëerd [2](#page=2).
### 1.2 Vergelijking met functioneel programmeren
Functionele programmeertalen, zoals Python of BES in hun basisvorm, presenteren software primair als een verzameling functies die objecten gebruiken voor specifieke taken, zoals reeksen, lijsten of plots. Hoewel deze aanpak nuttig kan zijn om problemen op te delen en functies herbruikbaar te maken, leidt het bij grotere projecten vaak tot een overweldigend aantal functies, wat het overzicht bemoeilijkt en niet altijd overeenkomt met de manier waarop de werkelijkheid wordt ervaren [7](#page=7).
### 1.3 De wereld als een hiërarchie van objecten
De "echte" wereld bestaat uit objecten die natuurlijkerwijs in hiërarchieën kunnen worden ingedeeld, wat een hoger niveau van abstractie biedt dan een indeling puur op basis van functies. Denk bijvoorbeeld aan een auto: deze is niet slechts een verzameling functies zoals 'rijden' of 'remmen', maar een complex systeem van objecten zoals een motor, airbags, een audiosysteem en een carrosserie. Een ander voorbeeld is de administratie van een universiteit, die kan worden ingedeeld in objecten zoals professoren, studenten, vakken en lokalen, in plaats van een losse verzameling functies voor inschrijven, punten invoeren, enzovoort. Het hoofddoel van OOP is om de wereld te classificeren in zulke objecten [8](#page=8).
### 1.4 Voordelen van objectgeoriënteerde softwareontwikkeling
Objectgeoriënteerde softwareontwikkeling resulteert in software die een hiërarchie van objecten vertegenwoordigt. Dit leidt tot duurzamere software, omdat objecten veel minder snel veranderen dan losse functies. Functies gerelateerd aan universitaire administratie, zoals delibereren of inschrijven voor vakken, zijn door de eeuwen heen sterk veranderd. Daarentegen is de fundamentele indeling in professoren, vakken en studenten grotendeels constant gebleven. Nieuwe software wordt dan een combinatie van bestaande en nieuw of aangepaste objecten, vergelijkbaar met de ontwikkeling van een nieuw automodel [9](#page=9).
#### 1.4.1 Duurzaamheid en aanpasbaarheid
Een belangrijk voordeel van OOP is de duurzaamheid van de software. Objecten veranderen veel minder snel dan functies. Bij aanpassingen aan de software, zoals het toevoegen van extra functionaliteit of het aanpassen van bestaande, hoeft vaak slechts de betreffende klasse te worden aangepast, in plaats van het gehele programma. Het uitbreiden van een programma komt neer op het toevoegen van nieuwe klassen [13](#page=13) [9](#page=9).
#### 1.4.2 Beheersing van complexiteit en samenwerking
OOP helpt bij het beheersen van complexiteit. Het opdelen van software in klassen maakt het mogelijk om werk te verdelen over verschillende teams. Communicatie met belanghebbenden wordt effectiever omdat de discussie kan plaatsvinden op het niveau van herkenbare en begrijpelijke objecten, zonder te verzanden in implementatiedetails [13](#page=13).
### 1.5 Van objecten naar klassen
Sommige objectgeoriënteerde talen werken rechtstreeks met objecten, wat intuïtief kan aanvoelen omdat het de directe manipulatie van entiteiten mogelijk maakt, vergelijkbaar met het creëren van voorwerpen uit klei, met ad-hoc relaties tussen objecten. De meeste objectgeoriënteerde talen maken echter gebruik van klassen. Dit voegt een extra abstractielaag toe, maar maakt het creëren van grote aantallen objecten veel gemakkelijker, vergelijkbaar met de auto-industrie. Het creëren van een klasse kan complex zijn, maar het maken van objecten van die klasse is daarentegen zeer eenvoudig [11](#page=11).
### 1.6 Objectoriëntatie als verbetering van softwareontwikkeling
Objectoriëntatie wordt gezien als een methode voor betere softwareontwikkeling. Het stelt ontwikkelaars in staat om de complexiteit van software te beheersen door deze op te delen in klassen, wat efficiënte taakverdeling tussen teams bevordert. De communicatie met management en stakeholders wordt effectiever doordat de discussie kan plaatsvinden aan de hand van herkenbare concepten. OOP leidt tot duurzamere software, omdat aanpassingen aan de software vaak beperkt blijven tot specifieke klassen. Bovendien maakt OOP geavanceerde concepten mogelijk die bijdragen aan de ontwikkeling van complexere en flexibelere systemen [13](#page=13).
> **Tip:** Denk bij het modelleren van een probleem in termen van objecten na over de "dingen" die een rol spelen en wat die "dingen" kunnen doen. Vraag jezelf af: welke eigenschappen (gegevens) hebben deze dingen, en welke acties (methoden) kunnen ze uitvoeren [2](#page=2)?
>
> **Tip:** Het concept van klassen als blauwdrukken is cruciaal voor het efficiënt creëren van meerdere, vergelijkbare objecten. Dit bespaart veel tijd en voorkomt redundantie in de code [11](#page=11).
>
> **Voorbeeld:** Een 'Student'-klasse kan eigenschappen hebben zoals `naam`, `studentnummer` en `opleiding`, en methoden zoals `schrijfInVoorVak(vak)` of `toonCijfers()`. Van deze klasse kunnen vervolgens talloze individuele studentobjecten worden gemaakt, elk met hun eigen specifieke gegevens [9](#page=9).
---
# Sterke typering en datatypen in Java
Dit gedeelte introduceert het concept van sterke typering in Java, waarbij elke variabele een specifiek datatype heeft, wat de programmeerveiligheid verhoogt, en geeft een overzicht van veelvoorkomende datatypen.
### 2.1 Sterke typering in Java
Sterke typering houdt in dat elke variabele in een programma verplicht een specifiek type moet hebben. Bij de eerste aanroep van een variabele moet het type worden gespecificeerd. Vanaf dat moment kan die variabele alleen gegevens van dat specifieke type bevatten. Dit mechanisme biedt aanzienlijke voordelen op het gebied van veiligheid, omdat de compiler in staat is om potentiële fouten al in een vroeg stadium van de ontwikkeling te identificeren [4](#page=4).
In tegenstelling tot impliciete of dynamische typering, die soms onvoorspelbaar kan zijn, biedt sterke typering, zoals in Java, een voorspelbaar gedrag. Dit betekent dat de code altijd op een consistente manier reageert, wat de betrouwbaarheid van het programma ten goede komt [5](#page=5).
> **Tip:** Sterke typering verkleint de kans op runtime-fouten die veroorzaakt worden door incompatibele gegevenstypen, doordat typecontroles al tijdens het compileren plaatsvinden.
### 2.2 Veelvoorkomende datatypen in Java
Java biedt een reeks ingebouwde datatypen die kunnen worden gebruikt om verschillende soorten informatie op te slaan:
* **Gehele getallen (`int`):** Dit datatype wordt gebruikt voor het opslaan van gehele getallen, zonder decimalen [6](#page=6).
* **Reële getallen (`double`):** Dit datatype is bedoeld voor het opslaan van getallen met een decimale component [6](#page=6).
* **Boolse waarden (`boolean`):** Dit type kan slechts twee waarden aannemen: `true` of `false` [6](#page=6).
* **Teksten (`String`):** Wordt gebruikt om reeksen tekens, oftewel tekst, op te slaan [6](#page=6).
* **Exclusieve opsomming (`enum`):** Maakt het mogelijk een vaste set aan vooraf gedefinieerde constanten te creëren [6](#page=6).
* **Objecten:** Een programmeur kan zelf een nieuw datatype definiëren door een eigen klasse te creëren [6](#page=6).
* **Lijsten:** Java biedt ook de mogelijkheid om lijsten van bovenstaande datatypen te maken, wat flexibiliteit biedt voor het opslaan van verzamelingen van gegevens [6](#page=6).
> **Example:** Een variabele gedeclareerd als `int leeftijd;` kan alleen gehele getallen opslaan, zoals `leeftijd = 25;`. Als men probeert een tekst toe te kennen, bijvoorbeeld `leeftijd = "twintig";`, zal de compiler een fout geven.
---
# Kernconcepten van objectoriëntatie: abstractie, encapsulatie en de relatie tussen klasse en object
Dit onderwerp introduceert de fundamentele principes van objectoriëntatie, waarbij de nadruk ligt op abstractie, encapsulatie en de essentiële relatie tussen klassen en objecten.
### 3.1 De relatie tussen klasse en object
Een **klasse** fungeert als een algemene blauwdruk of beschrijving. Het definieert de structuur en het gedrag dat objecten van deze klasse zullen hebben. De inhoud van een klasse bestaat uit data members, ook wel eigenschappen of attributen genoemd, en methodes, die de acties beschrijven die objecten kunnen uitvoeren [12](#page=12).
Elk **object** is daarentegen een concrete instantie van een specifieke klasse. Objecten delen dezelfde data members en methodes als gedefinieerd in hun klasse, maar ze kunnen verschillende waarden hebben voor hun data members. Dit onderscheid in inhoud zorgt voor individualiteit tussen objecten, zelfs als ze tot dezelfde klasse behoren [12](#page=12).
> **Voorbeeld:** Stel een klasse `Auto` voor. Deze klasse kan data members hebben zoals `kleur` en `gewicht`, en methodes zoals `starten()` en `rijden()`. Twee objecten van de klasse `Auto`, genaamd `rodeAuto` en `zwarteAuto`, zouden bijvoorbeeld verschillende waarden kunnen hebben voor de `kleur` data member (respectievelijk "rood" en "zwart"), terwijl ze beide de `starten()` methode kunnen uitvoeren. Een ander voorbeeld is een `Teller` klasse, waarbij objecten verschillende tellerstanden kunnen hebben, zoals 4/5 of 7/12 [12](#page=12).
### 3.2 Abstractie
Abstractie is het proces van het vereenvoudigen van complexiteit door alleen de essentiële kenmerken van een entiteit weer te geven en de irrelevante details weg te laten. Binnen objectoriëntatie betekent abstractie het indelen van concepten in klassen met hun bijbehorende methodes [15](#page=15).
De kern van abstractie ligt in het verbergen van implementatiedetails. Hierbij focust men op:
* De naam van de klasse en een korte beschrijving [15](#page=15).
* De namen en effecten van methodes, oftewel wat men met de klasse kan doen [15](#page=15).
Dit leidt tot een beter zichtbare hoog-niveau-structuur van het programma [15](#page=15).
> **Tip:** Abstractie helpt bij het beheersen van de complexiteit van software door gebruikers alleen bloot te stellen aan wat ze nodig hebben, zonder de details van hoe het intern werkt.
### 3.3 Encapsulatie
Encapsulatie is het principe waarbij de data members (eigenschappen) en de methodes (acties) die op die data werken, worden gebundeld binnen één eenheid: de klasse. Het primaire doel van encapsulatie is het verbergen van implementatiedetails en het beschermen van de interne toestand van objecten [19](#page=19).
Dit houdt in dat informatie die alleen intern relevant is, zoals de concrete variabelen binnen een klasse, wordt verborgen. Ook de concrete code van methodes, en zelfs eventuele hulpmethodes die intern gebruikt worden, worden verhuld. Dit zorgt ervoor dat de interne werking van een object niet zomaar gewijzigd kan worden door externe partijen, wat de stabiliteit en voorspelbaarheid van het systeem bevordert [19](#page=19).
#### 3.3.1 De "zwarte doos" analogie
Conceptueel kan een object worden beschouwd als een **zwarte doos**. Dit betekent dat men niet noodzakelijk weet wat er binnenin de doos gebeurt of hoe de interne processen verlopen. Wel weet men dat er knopjes aan de buitenkant zijn om mee te interageren en uitgangen om waarden uit te lezen [17](#page=17).
Deze analogie illustreert het verschil tussen hoe je programmeert en hoe je iets gebruikt. De **gebruiker** van een klasse ziet en gebruikt alleen de buitenkant – de "zwarte doos met knoppen". De **programmeur of ontwerper** daarentegen, ontwerpt de buitenkant, implementeert de binnenkant (inclusief de gegevens en methodes), en bepaalt hoe de interactie met het object verloopt. Encapsulatie zorgt ervoor dat deze interne implementatie kan veranderen zonder dat dit invloed heeft op hoe de gebruiker het object aanroept, zolang de buitenkant (interface) hetzelfde blijft [18](#page=18).
---
# Praktische aspecten van softwareontwerp en data members/methoden
Dit gedeelte behandelt de praktische implementatie van objectgeoriënteerd ontwerp door middel van data members en methoden, geïllustreerd met een voorbeeld van een AutoRadio.
### 4.1 Data members
Data members, ook wel eigenschappen genoemd, representeren de permanente gegevens die bij elk object van een klasse worden bijgehouden. Ze zijn bedoeld voor informatie die essentieel is voor het object en niet slechts tijdelijk nodig is tijdens een berekening (zoals tellers) [23](#page=23).
Kenmerken van data members:
* **Zinvolle naamgeving:** Data members moeten een duidelijke en beschrijvende naam hebben, bij voorkeur in camelCase [23](#page=23).
* **Type:** Elk data member moet een specifiek datatype hebben, zoals `int`, `double`, `boolean`, `String`, een opsomming, een object, of een lijst van objecten [23](#page=23).
### 4.2 Methoden
Methoden, ook wel gedrag genoemd, definiëren de acties die een object kan uitvoeren of de informatie die het kan verschaffen. Er is een sterke samenhang tussen data members en methoden. Methoden kunnen worden onderverdeeld in getters en setters [23](#page=23):
* **Getters (Informatie opvragen):**
* Deze methoden worden gebruikt om de waarde van data members op te vragen [23](#page=23).
* Ze nemen meestal geen parameters [23](#page=23).
* Ze retourneren altijd een waarde die de status of informatie van het object weerspiegelt [23](#page=23).
* **Setters (Informatie veranderen):**
* Deze methoden worden gebruikt om de waarden van data members te wijzigen [23](#page=23).
* Ze nemen meestal parameters die de nieuwe waarden bevatten [23](#page=23).
* Soms retourneren ze een waarde, bijvoorbeeld om de succesvolle wijziging aan te geven, maar dit is niet altijd het geval [23](#page=23).
> **Tip:** De concepten van data members en methoden zijn fundamenteel voor het creëren van goed gestructureerde en onderhoudbare objectgeoriënteerde code.
### 4.3 Voorbeeld: Eenvoudige AutoRadio
Een vereenvoudigd voorbeeld van een `AutoRadio` klasse kan de toepassing van data members en methoden illustreren. Hoewel de specifieke implementatie van de `AutoRadio` niet gedetailleerd wordt beschreven op de opgegeven pagina's, wordt het wel genoemd als een concreet voorbeeld om de concepten te demonstreren [24](#page=24).
Reflectie op een mogelijke (eerdere) oplossing van de `AutoRadio` suggereert dat het bijhouden van informatie zoals frequentie en band (AM/FM) voor meerdere zenders onoverzichtelijk kan worden. Dit probleem wordt in latere delen van het materiaal opgelost met compositie [27](#page=27).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Object | Een entiteit die gegevens (data members of attributen) en acties (methodes) combineert. Objecten zijn concrete instanties van klassen. |
| Klasse | Een blauwdruk of sjabloon dat de structuur en het gedrag definieert voor objecten. Het beschrijft de data members en methodes die objecten van die klasse zullen hebben. |
| Variabele | Een benoemde opslaglocatie in het geheugen die een waarde kan bevatten. In sterk getypeerde talen heeft elke variabele een vooraf gedefinieerd datatype. |
| Sterke typering | Een programmeerconcept waarbij elke variabele een verplicht type heeft. Dit type wordt bij de eerste gebruik van de variabele gespecificeerd en beperkt de soorten gegevens die erin kunnen worden opgeslagen. |
| Methode | Een functie die geassocieerd is met een klasse of object. Methoden definiëren de acties die een object kan uitvoeren. |
| Data member (Attribuut) | Een variabele die deel uitmaakt van een klasse en de eigenschappen of staat van een object representeert. |
| AbstraHerie | Het proces van het vereenvoudigen van complexiteit door implementatiedetails te verbergen en te focussen op de essentiële kenmerken en functionaliteiten. |
| Encapsulatie | Het bundelen van data members en methodes die op die data werken binnen één eenheid (de klasse). Dit principe helpt bij het verbergen van interne implementatiedetails en het beschermen van de interne staat van objecten. |
| Instantiëren | Het proces van het creëren van een concreet object uit een klasse. Dit object is een "instantie" van die klasse. |
| Impliciete typering | Een programmeerparadigma waarbij de compiler of interpreter het type van een variabele automatisch afleidt op basis van de toegewezen waarde, in plaats van dat het expliciet wordt gedeclareerd. |
| Overerving | Een mechanisme in objectgeoriënteerd programmeren waarbij een nieuwe klasse (subklasse of afgeleide klasse) eigenschappen en gedrag van een bestaande klasse (superklasse of basisklasse) kan erven. |
| PolymorDe | Het vermogen van objecten van verschillende klassen om te reageren op dezelfde methodeaanroep op een manier die specifiek is voor hun eigen type. |
Cover
hoorcollege1b_compositie_minimaal - Tagged.pdf
Summary
# Introductie tot software-ontwerp en compositie
Dit onderwerp introduceert de basisprincipes van software-ontwerp in Java, met een focus op het concept van compositie en de toepassing ervan in het ontwerpen van een autoradioapplicatie.
### 1.1 Reflectie op een eerdere oplossing
Een eerdere oplossing voor een autoradio-applicatie bevatte vier frequenties en vier banden (AM/FM) voor de voorkeurzenders en de huidige zender. Dit werd als onoverzichtelijk beschouwd. Een verbetering zou zijn om een aparte klasse voor het concept 'zender' te maken, omdat een zender zelf voldoende gegevens bevat [3](#page=3).
### 1.2 Verbeterde oplossing: autoradio met zender klasse
De verbeterde oplossing introduceert een aparte klasse genaamd `Zender`. De `AutoRadio` klasse bevat nu data members van het type `Zender` voor zender één, twee en drie, evenals een `huidig` zender object van het type `Zender`. Andere data members in `AutoRadio` zijn onder andere `gekozenZender` (een `int`), `volume` (een `int`) en `mute` (een `boolean`). De `Zender` klasse zelf bevat informatie over de AM/FM band en de frequentie. Een `enum` type wordt gebruikt voor de band. Dit principe wordt aangeduid als compositie, zoals besproken in hoofdstuk 4 [4](#page=4).
### 1.3 Basisidee van compositie
Compositie is een software-ontwerpprincipe waarbij een object als data member een waarde van een ander klasse bevat [5](#page=5).
Bijvoorbeeld:
* Een `AutoRadio` heeft data members `zender1`, `zender2`, `zender3` van het type `Zender` [5](#page=5).
* Een `BeReal` heeft objecten van het type `Foto` [2](#page=2) [5](#page=5).
* Een `Paard` heeft een data member `eigenaar` van het type `Persoon` [5](#page=5).
* Een `Bedrijf` heeft een data member `ceo` van het type `Werknemer` [5](#page=5).
Via delegatie kunnen methodes van de onderliggende klasse worden aangeroepen. Dit maakt het bijvoorbeeld mogelijk om na te gaan of een bedrijf een vrouwelijke CEO heeft [5](#page=5).
### 1.4 Het `enum`-type in Java
Een `enum` (enumeratie) type in Java wordt gebruikt voor een exclusieve opsomming. Dit is een alternatief voor een gewone klasse [6](#page=6).
Voorbeeld van een `enum` voor geslacht:
```java
public enum Geslacht {
M, V, X
}
```
De waarde van een enum wordt gebruikt door middel van de notatie `Klasse.WAARDE`, vergelijkbaar met een statisch data member [6](#page=6).
### 1.5 Klasse `Zender`
De klasse `Zender` bevat de attributen voor een zender, zoals de AM/FM band en de frequentie [7](#page=7).
> **Tip:** De implementatie van de `Zender` klasse kan triviale getters bevatten om deze data toegankelijk te maken [7](#page=7).
### 1.6 `AutoRadio` met `Zender` (Compositie)
De `AutoRadio` klasse demonstreert het gebruik van compositie door data members van het type `Zender` te bevatten. Dit zorgt voor een betere structuur en overzichtelijkheid in vergelijking met het direct beheren van losse frequentie- en bandvariabelen [3](#page=3) [8](#page=8).
---
# Ontwerp van de autoradio applicatie
Dit onderwerp verkent de evolutie van het ontwerp van een autoradio applicatie, met een focus op de introductie van een aparte klasse voor zenders om de structuur te verbeteren [2](#page=2) [3](#page=3).
### 2.1 Probleemstelling en reflectie op initiële oplossing
De initiële implementatie van de autoradio applicatie bevatte duplicatie van informatie en maakte het beheer van zendergegevens onoverzichtelijk. Specifiek werden frequenties en banden (AM/FM) vier keer herhaald voor de voorkeurzenders en de huidige zender. Dit gebrek aan een duidelijke representatie van het concept 'zender' leidde tot een onoverzichtelijke codebasis [3](#page=3).
### 2.2 Verbeterde oplossing met een `Zender` klasse
Om de structuur te verbeteren en de conceptuele duidelijkheid te vergroten, werd een aparte klasse genaamd `Zender` geïntroduceerd. Deze klasse bevat alle relevante informatie die bij een zender hoort [3](#page=3) [4](#page=4).
#### 2.2.1 Attributen van de `AutoRadio` klasse in de verbeterde oplossing
In de verbeterde `AutoRadio` klasse zijn de volgende attributen aanwezig, waarbij de zenderinformatie nu is georganiseerd via de `Zender` klasse [4](#page=4):
* Een verzameling van `Zender` objecten, mogelijk vertegenwoordigd door indices zoals `zender een`, `zender twee`, `zender drie` [4](#page=4).
* Een index `gekozenZender` om de momenteel geselecteerde zender bij te houden [4](#page=4).
* Een `Zender` object dat de `huidig`e zender representeert [4](#page=4).
* Een `volume` instelling, opgeslagen als een `int` [4](#page=4).
* Een `mute` status, opgeslagen als een `boolean` [4](#page=4).
* Een `enum` voor de band (AM/FM), die gecomponeerd wordt in hoofdstuk 4 [4](#page=4).
#### 2.2.2 Attributen van de `Zender` klasse
De `Zender` klasse zelf bevat de volgende specifieke details over een zender [4](#page=4):
* De band van de zender, welke `AM/FM` kan zijn [4](#page=4).
* De frequentie van de zender, opgeslagen als een `double` [4](#page=4).
> **Tip:** Het gebruik van een aparte `Zender` klasse bevordert de modulariteit en herbruikbaarheid van code. Het maakt de `AutoRadio` klasse ook eenvoudiger te begrijpen doordat de details van een zender worden afgehandeld door de `Zender` klasse zelf [3](#page=3).
### 2.3 Compositie in het ontwerp
De verbeterde autoradio applicatie maakt gebruik van compositie, waarbij de `AutoRadio` klasse een `Zender` klasse bevat. Dit wordt visueel weergegeven als een structuur waarin de autoradio is samengesteld uit zenders. Dit ontwerp is consistenter met het objectgeoriënteerd programmeren en verbetert de onderhoudbaarheid van de applicatie [3](#page=3) [8](#page=8).
---
# Compositie in Java
Compositie in Java is een ontwerpprincipe waarbij een object een ander object bevat als een van zijn data members, wat leidt tot een "has-a" relatie.
### 3.1 Basisidee van compositie
Het kernconcept van compositie is dat een klasse een instantie van een andere klasse bezit als een van zijn attributen. Dit creëert een sterke koppeling tussen de twee klassen, waarbij de ene klasse afhankelijk is van de andere voor functionaliteit [5](#page=5).
#### 3.1.1 Voorbeelden van compositie
Verschillende voorbeelden illustreren dit principe:
* Een `AutoRadio` klasse kan data members hebben van het type `Zender`, bijvoorbeeld `zender1`, `zender2`, en `zender3` [4](#page=4) [5](#page=5).
* Een `BeReal` klasse kan een of meerdere objecten van het type `Foto` bevatten [5](#page=5).
* Een `Paard` klasse kan een data member `eigenaar` van het type `Persoon` hebben [5](#page=5).
* Een `Bedrijf` klasse kan een data member `ceo` van het type `Werknemer` hebben [5](#page=5).
#### 3.1.2 Delegatie via compositie
Via compositie kan een object methodes van de onderliggende klasse aanroepen. Dit proces wordt delegatie genoemd. Bijvoorbeeld, om te controleren of een bedrijf een vrouwelijke CEO heeft, kan de `Bedrijf` klasse een methode aanroepen die in de `Werknemer` klasse (van de CEO) is gedefinieerd [5](#page=5).
> **Tip:** Delegatie is essentieel voor het benutten van de functionaliteit van de gecomponeerde objecten zonder de logica direct in de containerklasse te hoeven implementeren.
### 3.2 AutoRadio met Zender als voorbeeld
De `AutoRadio` klasse die data members van het type `Zender` bevat, is een direct voorbeeld van compositie in Java. Hierbij heeft de `AutoRadio` de functionaliteit en de staat van meerdere `Zender` objecten. De `Zender` klasse zelf kan attributen hebben zoals de AM/FM band en de frequentie. De `AutoRadio` kan ook eigen attributen hebben zoals het volume en de mute-status [4](#page=4) [8](#page=8).
---
# Gebruik van enumeraties (enum) in Java
Enumeraties (enums) in Java bieden een gestructureerde manier om een set van constante waarden te definiëren, die vaak gebruikt worden als een alternatief voor gewone klassen bij exclusieve opsommingen [6](#page=6).
### 4.1 Wat zijn enumeraties (enums)?
Een enumeratie, of enum, is een speciaal type variabele in Java dat wordt gebruikt om een exclusieve opsomming van waarden te vertegenwoordigen. Dit betekent dat een enum-variabele slechts één van de vooraf gedefinieerde waarden kan aannemen [6](#page=6).
#### 4.1.1 Het doel van enums
Enums zijn met name nuttig wanneer je een beperkt en vaststaand aantal mogelijke waarden hebt. Een klassiek voorbeeld hiervan is de band van een radiozender, die óf AM óf FM kan zijn. In plaats van deze waarden te representeren met losse constanten of strings, biedt een enum een typeveilige en leesbare oplossing [4](#page=4).
> **Tip:** Enums helpen de code duidelijker te maken en voorkomen veelvoorkomende fouten die kunnen optreden bij het gebruik van magische getallen of strings.
#### 4.1.2 Alternatief voor gewone klassen
Een enum kan gezien worden als een alternatief voor een gewone klasse die enkel constanten bevat. Het belangrijkste voordeel is de beperking tot een vooraf gedefinieerde set van waarden, wat de code robuuster maakt [6](#page=6).
#### 4.1.3 Syntaxis en gebruik
De basis syntaxis voor het definiëren van een enum is eenvoudig. Je gebruikt het sleutelwoord `enum` gevolgd door de naam van de enum en de mogelijke waarden, gescheiden door komma's.
Een veelvoorkomend patroon is het definiëren van een enum voor een geslachtsindicatie:
```java
public enum Geslacht {
M, V, X
}
```
Om een waarde van een enum te gebruiken, verwijs je ernaar via de naam van de enum, gevolgd door een punt en de gewenste waarde: `EnumNaam.WAARDE` [6](#page=6).
Voorbeeld:
```java
Geslacht mijnGeslacht = Geslacht.V;
```
Dit is vergelijkbaar met het gebruik van statische constanten, maar met het extra voordeel van typeveiligheid. Als je bijvoorbeeld een `Geslacht`-variabele probeert toe te kennen met een ongeldige waarde, zal de compiler een fout geven.
#### 4.1.4 Enums in context: de AutoRadio
In het context van een `AutoRadio` klasse, kan een enum gebruikt worden om de band van een zender te specificeren. In plaats van een `String` of `int` te gebruiken voor de band, zou een `enum Band { AM, FM }` een betere keuze zijn [4](#page=4).
De `AutoRadio` klasse kan dan kenmerken hebben zoals:
* Een `Band` voor de zenderfrequentie [4](#page=4).
* Een `double` voor de frequentie [4](#page=4).
* Een `int` voor het volume [4](#page=4).
* Een `boolean` voor de mute-status [4](#page=4).
Door enums te gebruiken voor gesloten opsommingen zoals de band (AM/FM), creëer je code die gemakkelijker te lezen, te onderhouden en minder gevoelig voor fouten is.
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Compositie | Een ontwerptechniek waarbij een klasse objecten van andere klassen bevat als data members. Dit stelt objecten in staat om functionaliteit te delegeren aan de onderliggende objecten. |
| Data member | Een variabele die deel uitmaakt van een klasse. Deze variabelen slaan de staat van een object op en worden gebruikt om de gegevens van het object te representeren. |
| Klasse | Een blauwdruk voor het creëren van objecten. Een klasse definieert de eigenschappen (data members) en het gedrag (methoden) die objecten van die klasse zullen hebben. |
| Object | Een instantie van een klasse. Een object heeft een specifieke staat, bepaald door de waarden van zijn data members, en kan operaties uitvoeren via zijn methoden. |
| Delegeren | Het proces waarbij een methode in een klasse een verzoek doorstuurt naar een methode in een ander object (meestal een object dat het als een data member bevat) om de taak uit te voeren. |
| Enum-type (Enumeratie) | Een speciaal datatype in Java dat een vaste set van benoemde constanten vertegenwoordigt. Het is nuttig voor het definiëren van exclusieve opsommingen, zoals de mogelijke waarden van een band (AM of FM). |
| Triviale ge|ers | Dit zijn eenvoudige methoden of attributen binnen een klasse die vaak slechts data teruggeven of instellen, zonder veel complexiteit. Ze vormen de basiselementen van de klasse. |
| Hoorcollege | Een academische les die voornamelijk in de vorm van een lezing wordt gegeven, waarbij de docent de stof presenteert aan een groep studenten. |
| BlueJ | Een geïntegreerde ontwikkelomgeving (IDE) die speciaal is ontworpen voor het onderwijzen van objectgeoriënteerd programmeren in Java, met een focus op visuele representatie van klassen en objecten. |
Cover
hoorcollege2_2dec.pdf
Summary
# Codeerconventies en verzamelingen
Dit gedeelte behandelt de basisprincipes van goede codeerpraktijken in Java, zoals naamgevingsconventies en indentatie, en introduceert het concept van verzamelingen met de `ArrayList`.
### 1.1 Codeerconventies: internationale afspraken
Goede codeerconventies zijn essentiële afspraken die ervoor zorgen dat code leesbaar, onderhoudbaar en consistent is [3](#page=3).
#### 1.1.1 Naamgeving
De naamgeving van klassen, variabelen en methodes volgt specifieke patronen om de functionaliteit duidelijk te maken [3](#page=3).
* **Algemeen:** Gebruik altijd CamelCase, waarbij woorden aan elkaar worden geschreven en elk nieuw woord begint met een hoofdletter [3](#page=3).
* **Klassen en interfaces:** Gebruik *UpperCamelCase*, waarbij het eerste woord ook met een hoofdletter begint [3](#page=3).
* **Variabelen en methodes:** Gebruik *lowerCamelCase*, waarbij het eerste woord met een kleine letter begint [3](#page=3).
* **Seters:** Methoden die de waarde van een variabele instellen, beginnen met `set` gevolgd door de naam van de variabele (e.g., `setNaam()` ) [3](#page=3).
* **Getters:** Methoden die de waarde van een variabele ophalen, beginnen met `get` gevolgd door de naam van de variabele (e.g., `getNaam()` ) [3](#page=3).
* **Booleans:** Methoden die een booleaanse waarde teruggeven, kunnen beginnen met `is`, `can`, of `has` (e.g., `isActive()`, `canEdit()`, `hasPermission()` ) [3](#page=3).
* **Tellers in for-lussen:** Gebruik gangbare korte namen zoals `i`, `j`, `k` voor tellers in for-lussen [3](#page=3).
#### 1.1.2 Indentatie
Indentatie, het inspringen van code, is cruciaal voor de leesbaarheid en structuur van de code [3](#page=3).
* **Inspringen:** Na elke openende accolades `{` moet de code met 2 tot 4 spaties worden ingesprongen [3](#page=3).
* **Openen van accolades:** Accolades mogen aan het einde van de regel worden geopend of op een nieuwe regel [5](#page=5).
* **Sluiten van accolades:** Een sluitende accolade `}` wordt uitgelijnd met de eerste letter van het sleutelwoord dat de openende accolade heeft veroorzaakt (bijvoorbeeld de `i` van `if`, de `f` van `for`, of de `p` van `public`) [6](#page=6).
> **Tip:** Een extensie (zoals die genoemd wordt in het document) kan helpen bij het controleren van indentatie en naamgevingsconventies. Het controleert op correct gebruik van CamelCase, de beginletters van klassen, interfaces, variabelen en methodes, en de conventies voor getters en seters [7](#page=7).
### 1.2 Verzamelingen met de ArrayList
Wanneer een klasse meerdere objecten van hetzelfde type bevat, is een verzameling nodig. De `ArrayList` is een veelgebruikte implementatie hiervan in Java [10](#page=10) [11](#page=11).
#### 1.2.1 Het concept van verzamelingen
* **Situaties met één object:** Soms is er slechts één instantie van een bepaald kenmerk, zoals iemands haarkleur of natuurlijke moeder [10](#page=10).
* **Situaties met veel objecten:** Vaak heeft een object meerdere instanties van een ander type. Voorbeelden zijn een radio met voorkeurzenders, een klas met studenten, een studentenvereniging met leden, een gemeente met inwoners, een inbox met SMS'jes, of een muziekplayer met nummers. In dergelijke gevallen is een verzameling noodzakelijk [10](#page=10).
#### 1.2.2 De ArrayList
De `ArrayList` is een dynamische array die een verzameling van objecten van het type `T` kan bevatten [11](#page=11).
* **Generics:** De `` geeft aan dat de `ArrayList` type-veilig is en objecten van een specifiek type kan opslaan [11](#page=11).
* **For-each lus:** `ArrayList`s bieden een handige *for-each* lus om eenvoudig door de elementen van de verzameling te itereren [12](#page=12).
```java
for ( Object o : collection ) {
o.doSomething();
}
```
* **Voorbeeld:**
```java
double port;
for (Poststuk x : stukken) {
port = port + x.getPortKost();
}
```
```java
for (Hond dog : kennel) {
dog.makeSomeNoise();
}
```
#### 1.2.3 Verantwoordelijkheden: Element versus Verzameling
Het is cruciaal om het verschil tussen een enkel element en de verzameling ervan te onderscheiden en deze verantwoordelijkheden in aparte klassen te beheren [13](#page=13).
* **Element klasse:**
* Beheert alleen zichzelf [13](#page=13).
* Bevat een constructor, data members, en getters/seters [13](#page=13).
* Voorbeelden van elementen zijn Zender, Boek, Student, Wedstrijd, Meeting [13](#page=13).
* **Verzamelklasse:**
* Beheert voornamelijk de verzameling van elementen [13](#page=13).
* Bevat een constructor voor een lege verzameling [13](#page=13).
* Biedt methodes voor het toevoegen, verwijderen en opzoeken van elementen [13](#page=13).
* Kan methodes 'delegeren' naar elementen, vaak met een extra zoekparameter [13](#page=13).
* Voorbeelden van verzamelingen zijn Radio, Bibliotheek, Studentenvereniging, Competitie, Wetenschappelijk experiment [13](#page=13).
> **Belangrijk:** Houd deze twee concepten strikt gescheiden. Een typische beginnersfout is het creëren van een "gemengde klasse" die functionaliteit van zowel elementen als verzamelingen bevat [14](#page=14).
* **Niet doen in een Element-klasse:**
* Een lijst bijhouden van andere elementen [14](#page=14).
* Opzoekwerk doen binnen de verzameling [14](#page=14).
* **Niet doen in een Verzamelklasse:**
* Get/set-methodes zonder parameter die specifiek op één element gericht zijn, tenzij het een zoekfunctionaliteit betreft [14](#page=14).
#### 1.2.4 Voorbeeld: Studentenvereniging
Een studentenvereniging die leden beheert, illustreert het verschil tussen een element (een lid) en de verzameling (de ledenlijst van de vereniging) [15](#page=15).
#### 1.2.5 Voorbeeld: Universiteit, Vakken en Inschrijvingen
Een universiteit die vakken aanbiedt, waarbij studenten zich kunnen inschrijven en examens kunnen afleggen, toont een complexere relatie van compositie [16](#page=16).
* **Vak klasse:**
* Beheert alleen dat specifieke vak en de punten die op dat vak zijn behaald [16](#page=16).
* Data members: `Vak vak`, `int punt` (dit lijkt een vereenvoudiging, waarschijnlijk bedoeld als een relatie binnen de inschrijving) [16](#page=16).
* **Student klasse:**
* Schrijft zich in voor *veel* vakken, inclusief evaluaties [16](#page=16).
* Beheert zijn lijst van vakken waarin hij is ingeschreven [16](#page=16).
* Kan de punten voor een specifiek vak opzoeken of veranderen [16](#page=16).
* Data member: Een lijst van inschrijvingen (een verzameling van objecten die zowel het vak als de punten bevatten) [16](#page=16).
Dit voorbeeld toont aan dat een student meerdere "inschrijvingen" heeft, en elke inschrijving gerelateerd is aan één vak. De student beheert de verzameling van deze inschrijvingen. [16](#page=16).
---
# Erving in software-ontwerp
Erving is een krachtig ontwerpmiddel dat software-uitbreiding en herbruikbaarheid verhoogt door speciale gevallen toe te voegen [19](#page=19).
### 2.1 De noodzaak van erving
Software is in constante ontwikkeling en moet worden uitgebreid tegen minimale kosten en inspanning. Vaak betekent dit het toevoegen van "speciale gevallen". Zonder erving leidt dit tot problemen [19](#page=19) [20](#page=20):
* Data members moeten worden uitgebreid [22](#page=22).
* Methodes moeten worden herschreven, elke keer wanneer een nieuw type wordt toegevoegd [22](#page=22).
Dit is een veelvoorkomend probleem, bijvoorbeeld bij het beheren van verschillende soorten tickets (normaal, VIP, super-VIP) of gebruikersrollen (gebruiker, admin, superadmin) [22](#page=22).
### 2.2 Wat is erving?
Erving (inheritance) is een mechanisme dat het mogelijk maakt om "speciale soorten" van een klasse toe te voegen. Het documenteert gemeenschappelijke structuren tussen klassen, creëert een hiërarchie en verhoogt de herbruikbaarheid [23](#page=23) [25](#page=25).
#### 2.2.1 De syntax van erving
De basisvorm van erving in Java wordt gedefinieerd met het sleutelwoord `extends`:
```java
class Y extends X {
// ...
}
```
Hierbij breidt klasse `Y` klasse `X` uit. Klasse `Y` erft alle functionaliteiten van `X` en kan deze uitbreiden of aanpassen. `Y` is een subtype van `X`, en `X` is een supertype van `Y` [26](#page=26).
#### 2.2.2 Toepassing van erving
Een typisch voorbeeld is de relatie tussen een algemene klasse `Hond` en een specifiekere klasse `Rashond`. Een `Rashond` is een speciaal geval van een `Hond` [27](#page=27).
Wanneer een klasse een andere klasse uitbreidt:
* **Extra gegevens:** Nieuwe data members kunnen worden gedefinieerd zoals in een gewone klasse [28](#page=28).
* **Extra methodes:** Nieuwe methodes kunnen worden gedefinieerd zoals in een gewone klasse [28](#page=28).
* **Aangepaste methodes:**
* Een methode kan volledig worden herschreven als deze totaal niet voldoet. Dit kan door een nieuwe methode met dezelfde naam te maken, eventueel met de `@Override` annotatie [28](#page=28).
* Een methode kan gedeeltelijk worden hergebruikt. Dit gebeurt door een methode te maken die verwijst naar de super-methode, bijvoorbeeld `super.doetIets()` [28](#page=28).
#### 2.2.3 De constructor-keten
Tijdens de initialisatie wordt altijd eerst een object van de superklasse gemaakt. Als eerste opdracht van de constructor van de subklasse moet de constructor van de superklasse worden aangeroepen met `super()` [28](#page=28).
Wanneer een object van een subklasse wordt gemaakt, wordt altijd eerst een object van de superklasse gecreëerd. Standaard kiest Java de default constructor, tenzij expliciet `super()` wordt aangeroepen [31](#page=31).
##### Voorbeeld constructor-keten
Stel de hiërarchie is `LevendWezen` -> `Hond` -> `Rashond`.
* Bij het aanmaken van een `Hond` object wordt eerst een `LevendWezen` object aangemaakt en daarna de `Hond`-specifieke data members toegevoegd [31](#page=31).
* Bij het aanmaken van een `Rashond` object wordt eerst een `Hond` object aangemaakt (wat op zijn beurt weer een `LevendWezen` aanmaakt), en daarna de `Rashond`-specifieke data members toegevoegd [31](#page=31).
### 2.3 Subtypes en het Liskov Substitutie Principe
Erving introduceert het concept van subtypes. Een subtype is een gespecialiseerd deel van een algemener type [33](#page=33).
* **Klasse versus type:** Een object kan verschillende types hebben. In Java erft elk object van de klasse `Object`, waardoor elk object ook het type `Object` heeft [33](#page=33).
* **Toepassing in collecties:** Als `RasHond` een subtype is van `Hond`, mag een `RasHond` object worden opgeslagen in een `ArrayList`. Omgekeerd is dit niet toegestaan: `Honden` mogen niet zomaar in een `ArrayList` worden opgeslagen [34](#page=34) [36](#page=36).
#### 2.3.1 Het Liskov Substitutie Principe (LSP)
Het Liskov Substitutie Principe stelt dat, als klasse `Y` klasse `X` uitbreidt (`class Y extends X`), objecten van klasse `Y` objecten van klasse `X` moeten kunnen vervangen zonder dat dit de correctheid van het programma beïnvloedt. Dit principe moet altijd in gedachten worden gehouden bij het toepassen van erving [35](#page=35).
* Als klasse `Y` objecten van klasse `X` gebruikt en een **speciaal geval** is van `X`, dan is erving correct. Dit is de "is een" relatie [35](#page=35).
* Als klasse `Y` objecten van klasse `X` gebruikt, maar **geen speciaal geval** is van `X`, dan moet compositie worden gebruikt. Dit is de "heeft een" relatie [35](#page=35).
#### 2.3.2 Toepassing in lijsten en methodes
Wanneer methodes worden toegepast op elementen van een lijst (zoals een `ArrayList`), gebruikt Java de meest specifieke methode die beschikbaar is voor het object [36](#page=36).
Een betere oplossing voor het poststukken-voorbeeld, dankzij erving en het Liskov principe, maakt het mogelijk om nieuwe poststukken toe te voegen zonder extra werk [37](#page=37).
### 2.4 Abstracte klassen
Abstracte klassen voorkomen dat "valse" objecten worden aangemaakt, bijvoorbeeld objecten van een algemene klasse zoals `Poststuk` als dit conceptueel niet logisch is. Door een klasse abstract te maken, kunnen er geen instanties van worden gecreëerd [39](#page=39).
### 2.5 Gemengde lijsten en typecasting
Lijsten kunnen zowel gewone als speciale elementen bevatten (bijvoorbeeld `ArrayList` kan ook `AfgerondeRechthoek` objecten bevatten). Als er acties moeten worden uitgevoerd die specifiek zijn voor de speciale elementen (bv. alleen afgeronde rechthoeken tekenen), is het noodzakelijk om deze speciale objecten te herkennen [40](#page=40).
#### 2.5.1 De `instanceof` operator
De `instanceof` operator wordt gebruikt om te controleren of een object tot een specifieke klasse of subklasse behoort. Het retourneert een boolean waarde [40](#page=40).
> **Tip:** Gebruik `instanceof` om te controleren of een object een specifiek type heeft voordat je een typecast uitvoert.
#### 2.5.2 Typecasten naar een hogere klasse (upcasting)
Als een `ArrayList` objecten van klasse `Rashond` bevat, en je wilt de specifieke `getRas()` methode van `Rashond` aanroepen, kan dit standaard niet omdat `Hond` deze methode niet heeft. De oplossing is "upcasting", wat alleen mag als je zeker bent dat de hond een rashond is. Dit controleer je met `instanceof` [42](#page=42).
### 2.6 Compositie versus erving
Het is cruciaal om onderscheid te maken tussen compositie en erving [45](#page=45).
* **Compositie:** Staat voor "heeft een". Een `Rechthoek` heeft een `Punt` en breedte/hoogte. Een rechthoek *is geen* punt [35](#page=35) [45](#page=45).
* **Erving:** Staat voor "is een". Een `AfgerondeRechthoek` *is een* `Rechthoek` [35](#page=35) [45](#page=45).
Hoewel compositie soms technisch mogelijk is voor relaties die lijken op erving, is het conceptueel fout. Compositie is soms minder handig omdat het niet direct toegang geeft tot data members en methodes. Erving kan technisch wel, maar moet conceptueel correct zijn [45](#page=45).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Codeerconventies | Internationale afspraken die helpen om code leesbaar, consistent en onderhoudbaar te maken, zoals naamgevingsregels en indentatie. |
| CamelCase | Een naamgevingsstijl waarbij woorden aan elkaar worden geschreven en elk nieuw woord met een hoofdletter begint. Wordt onderscheiden in UpperCamelCase voor klassen en lowerCamelCase voor variabelen en methodes. |
| UpperCamelCase | Een variant van CamelCase waarbij de eerste letter van het eerste woord ook een hoofdletter is, typisch gebruikt voor klassen- en interfacenamen. |
| lowerCamelCase | Een variant van CamelCase waarbij de eerste letter van het eerste woord een kleine letter is, typisch gebruikt voor variabelen-, methode- en constructor-namen. |
| Indentatie | Het proces van het inspringen van code om de structuur en leesbaarheid te verbeteren, waarbij blokken code op een consistente manier worden aangegeven, vaak met 2 tot 4 spaties per niveau. |
| ArrayList | Een dynamische verzameling in Java die elementen van een bepaald type (aangegeven door T) opslaat en biedt methoden voor het toevoegen, verwijderen en opzoeken van elementen. |
| For-each lus | Een vereenvoudigde manier om door alle elementen van een collectie of array te itereren zonder expliciet een index te hoeven beheren. |
| Element | Een enkel object binnen een verzameling dat een specifieke entiteit vertegenwoordigt, zoals een zender, een boek of een student. |
| Verzameling | Een klasse die verantwoordelijk is voor het beheren van meerdere elementen, inclusief functionaliteit voor het toevoegen, verwijderen en opzoeken van deze elementen. |
| Composi=e | Een ontwerpconcept waarbij een klasse een ander object bevat als data member, wat een "heeft een"-relatie vertegenwoordigt. |
| Erving (Inheritance) | Een mechanisme in objectgeoriënteerd programmeren waarbij een nieuwe klasse (subklasse) eigenschappen en gedrag van een bestaande klasse (superklasse) kan overnemen, wat een "is een"-relatie creëert. |
| Subtype | Een klasse die eigenschappen van een superklasse overerft, waardoor het een gespecialiseerde versie van de superklasse wordt en hiermee compatibel is. |
| Supertype | Een klasse waarvan de eigenschappen en gedrag worden overgeërfd door subklassen, waardoor het een algemener concept vertegenwoordigt. |
| Liskov Substitution Principle (LSP) | Een ontwerpprincipe dat stelt dat objecten van een subklasse de objecten van hun superklasse moeten kunnen vervangen zonder dat de correctheid van het programma wordt aangetast. |
| Abstracte klasse | Een klasse die niet direct geïnstantieerd kan worden en bedoeld is om als basis te dienen voor subklassen. Kan abstracte methoden bevatten die door subklassen geïmplementeerd moeten worden. |
| instanceof | Een operator in Java die wordt gebruikt om te controleren of een objectinstantie behoort tot een bepaald type klasse of interface. |
| Typecasten (Type casting) | Het proces waarbij een waarde van het ene type wordt geconverteerd naar een ander type. In Java kan dit "upcasting" (naar een algemener type) of "downcasting" (naar een specifieker type) zijn. |