Cover
Start now for free 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. |