Pointers, arrays & dynamische geheugen The scary stuff Gydo deze keer niet aanwezig Practicum feedback graag
Wat is een pointer? Verwijzing naar een stuk geheugen Enge spul in C++ Bron van al het kwaad … Veel van jullie hebben wel eens iets gehoord over pointers
Wat is een pointer?
Wat is een pointer? Beetje net een referentie in C# Maar niet helemaal! Verwijst naar een stuk geheugen net als in C#
Pointer voorbeeld! Output: 0026F7E4 #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer; // Point to myNumber myPointer = &myNumber; cout<<myPointer; return 0; } Output: 0026F7E4 Wat komt hier uit? Paar nieuwe dingen: Int aanmaken Pointer aanpaken Pointer laten wijzen naar de integer Printen de pointer Nieuwe syntax: * Teken om pointer aan te geven & teken, geheugenadres Hoe outputten we de waarde?
Pointer voorbeeld! Output: 10 #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer; // Point to myNumber myPointer = &myNumber; cout<<*myPointer; return 0; } Output: 10 Door * toe te voegen aan de variabele naam, geven we aan dat we de waarde aan willen spreken.
Pointer syntax Type gevolgd met een *-teken int* ook een type! Later meer hierover Zonder * verwijzen we naar de pointer Met * naar de waarde
Pointer syntax voorbeeld #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int mySecondNumber = 20; int* myPointer = &myNumber; *myPointer = 100; myPointer = &mySecondNumber; *myPointer = 200; cout<<myNumber<<endl; cout<<mySecondNumber<<endl; return 0; } Output: 100 200 Twee integers Één integer pointer We laten eerst de pointer verwijzen naar myNumber Door * voor de pointer te zetten, spreken we de waarde aan Zonder de * voor de pointer veranderen we waar de pointer naar wijst
Pointer syntax - andere types #include <iostream> using namespace std; int main(int argc, char* argv[]) { float myFloat = 1.5f; float *myPointer = &myFloat; cout<<*myPointer; cin.ignore(); return 0; } Werkt ook met floats! Andere notatie: spatie tussen * en type
Pointer syntax - andere types #include <iostream> using namespace std; int main(int argc, char* argv[]) { float myFloat = 1.5f; int *myPointer = &myFloat; cout<<*myPointer; cin.ignore(); return 0; } Een int pointer kan alleen verwijzen naar integers Error 1 error C2440: 'initializing' : cannot convert from 'float *' to 'int *’
Pointer syntax - andere types #include <iostream> using namespace std; int main(int argc, char* argv[]) { float myFloat = 1.5f; int *myPointer = (int*)&myFloat; cout<<*myPointer; cin.ignore(); return 0; } Output: 1069547520 Met een cast kunnen we het wel naar een int pointer forceren. Float geheugenlocatie interpreteren als een int! Wat heb je er nu aan? Vrij weinig.
Pointer syntax – more! #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; int* myOtherPointer = myPointer; *myOtherPointer = 100; cout<<myNumber; cin.ignore(); return 0; } Output: 100 Pointers kun je ook gewoon aan elkaar toewijzen.
Pointer syntax – pointer to pointer #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; int** myOtherPointer = &myPointer; **myOtherPointer = 100; cout<<myNumber; cin.ignore(); return 0; } Output: 100 Pointers kunnen ook naar andere pointers verwijzen Int** pointer naar pointer, je kan er ook meer sterretjes in zetten.
Pointer syntax – pointer to pointer #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; int** myOtherPointer; *myOtherPointer = myPointer; **myOtherPointer = 100; cout<<myNumber; cin.ignore(); return 0; } Output: 100 Zonder * voor de myOtherPointer kun je alleen een int** toewijzen (zie vorige slide) Met één * voor myOtherPointer kun je een int* aan toewijzen Met twee *-tekens kun je een int toewijzen
Pointer syntax #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; *myPointer++; cout<<myNumber<<endl; cout<<*myPointer<<endl; cin.ignore(); return 0; } Output 10 1586225894 Het lijkt of we de waarde waar onze pointer naar verwijst verhogen. Niet het geval! ++ heeft voorrang op * We voeren ++ uit op de pointer locatie => locatie van pointer verandert!
Pointer syntax #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; (*myPointer)++; cout<<myNumber<<endl; cout<<*myPointer<<endl; cin.ignore(); return 0; } Output 11 We fixen dit door haakjes toe te voegen. Toch interessant dat we ++ op een pointer kunnen doen
Pointer syntax – pointer ophogen? #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; cout<<myPointer<<endl; myPointer++; cout<<(int)myPointer - (int)&myNumber<<endl; return 0; } Output 0036FAC0 0036FAC4 4 sizeof(int) We outputten eerst het adres waar myPointer naar verwijst Daarna hogen we de pointer op. Dan outputten we opnieuw het adres waar myPointer naar verwijst Daarna outputten we het verschil tussen de nieuwe adres en het oude.
Pointer syntax – pointer ophogen? #include <iostream> using namespace std; int main(int argc, char* argv[]) { short myNumber = 10; short* myPointer = &myNumber; cout<<myPointer<<endl; myPointer++; cout<<(int)myPointer - (int)&myNumber<<endl; return 0; } Output 0038FAF4 0038FAF6 2 sizeof(short) Zelfde, maar dan met shorts
Pointer syntax – pointer ophogen? #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 10; int* myPointer = &myNumber; cout<<myPointer<<endl; myPointer = myPointer + 3; cout<<(int)myPointer - (int)&myNumber<<endl; return 0; } Output 002AF83C 002AF848 12 Weer terug naar ints Getal optellen bij pointer.
Arrays! Blok aaneengesloten geheugen Verwijzing naar begin
Voorbeeld – pointer naar array #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myArray[10]; for(int i = 0; i < 10; i++) myArray[i] = i*5; int* myPointer = &myArray[0]; cout<<*(myPointer+4)<<endl; cin.ignore(); return 0; } #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myArray[10]; for(int i = 0; i < 10; i++) myArray[i] = i*5; int* myPointer = &myArray[0]; myPointer += 4; cout<<*myPointer<<endl; cin.ignore(); return 0; } Output: 20 We creëren een array van lengte 10 Initialiseren de array {0, 5, 10, 15 etc..} Laten myPointer verwijzen naar de eerste element van onze array Tellen 4 op bij de pointer, waardoor het 4*4bytes verplaatst Kijken naar de waarde Zelfde functionaliteit als de index operator []!
Voorbeeld – Array == pointer? #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myArray[10]; for(int i = 0; i < 10; i++) myArray[i] = i*5; int* myPointer = myArray; myPointer += 4; cout<<*myPointer<<endl; cin.ignore(); return 0; } Output: 20 Bijna zelfde code Array wordt direct toegewezen aan myPointer! Array in essentie niets anders dan een pointer naar eerste element!
Array syntax Initialisatie 2D array declaratie: 2D array initialisatie int myArray[10]; Initialisatie int myArray[] = {0, 1, 2, 3, 4}; 2D array declaratie: int myArray[5][10]; 2D array initialisatie int myArray[3][2] = { {10, 20}, {30, 40}, {50, 60} }; Allemaal syntactische suiker voor het werken met pointers Uitkijken met 2D arrays, is compiler specifiek qua memory management. Inconsistent qua syntax (als je het als parameter mee wil geven) Kan voor vele fouten zorgen Gebruik liever 1D array met size hoogte*breedte
Dynamische geheugen new delete new[] en delete[] Reserveert geheugen en geeft een pointer delete Krijgt een pointer mee, geeft geheugen vrij new[] en delete[] Voor arrays
new en delete Output: 50 #include <iostream> using namespace std; int main(int argc, char* argv[]) { int* myPointer = new int; *myPointer = 50; cout<<*myPointer<<endl; delete myPointer; myPointer = nullptr; cin.ignore(); return 0; } Output: 50 We declareren een int pointer Gebruiken new int om een integer te reserveren Deze kunnen we gebruiken als elk andere pointer. Geheugen vrijgeven met delete Omdat we netjes zijn, zetten we de pointer op null om dubbele deletes te voorkomen.
new[] en delete[] Output: 50 #include <iostream> using namespace std; int main(int argc, char* argv[]) { int* myPointer = new int[5]; myPointer[3] = 50; cout<<myPointer[3]<<endl; delete [] myPointer; myPointer = nullptr; cin.ignore(); return 0; } Output: 50 Alloceren geheugen met new int[5]. Net C# Net zoals dat we de [] operator konden nabootsen met pointer operaties, kan het omgekeerde ook! Elementen accessen met [] operator Geheugen vrijgeven met delete []
Bad allocation Memory allocations kunnen fout gaan Out of memory Standard gedrag: exception. Ja, C++ heeft ook exceptions Liever geen exceptions? Gebruik (nothrow)
Bad allocation Output: 00000000 #include <iostream> #include <new> using namespace std; int main(int argc, char* argv[]) { int* myPointer = new (nothrow) int[500000000]; cout<<myPointer<<endl; delete[] myPointer; cin.ignore(); return 0; } We includen <new> Bij het aanroepen van new[] gebruiken we nothrow, deze is gedefineerd in de <new> header We outputten de geheugenadres. Dit is gelijk aan 0 Check met een if statement of de allocatie is gelukt.
Dynamic allocation #include <iostream> #include <new> using namespace std; int main(int argc, char* argv[]) { int arraySize; cin>>arraySize; int* myPointer = new (nothrow) int[arraySize]; if(myPointer) cout<<"Memory allocated!"<<endl; else cout<<"Out of memory!"<<endl; delete [] myPointer; return 0; } Output: 2000 Memory allocated! De groottes van deze dynamische allocaties kunnen natuurlijk dynamisch (at run-time) worden bepaald. Hier wordt de gebruiker gevraagd om een integer in te voeren. Vervolgens wordt een array van deze grootte gealloceerd. Afhankelijk van of het gelukt is, wordt er iets op de scherm geprint.
Main functie revisisted int main(int argc, char* argv[]) char* argv[] Array van arrays Dus argv is een array van arrays van chars: char**
References Beetje pointer functionaliteit Minder krachtig Veiliger Simpelere syntax
References - voorbeeld #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 50; int& myReference = myNumber; myReference++; cout<<myNumber; cin.ignore(); return 0; } Output: 51 We creëren een reference We laten het verwijzen naar myNumber. Dit kun je niet opslitsen, want een reference is een constante We hogen de reference op myNumber is veranderd
References - voorbeeld #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 50; int* myPointer = &myNumber; (*myPointer)++; cout<<myNumber; cin.ignore(); return 0; } Output: 51 Vergeleken met pointer syntax is het simpeler.
References - voorbeeld #include <iostream> using namespace std; int main(int argc, char* argv[]) { int myNumber = 50; int myOtherNumber = 100; int& myReference = myNumber; myReference++; myReference = myOtherNumber; cout<<myNumber<<endl; cout<<myOtherNumber<<endl; cin.ignore(); return 0; } Output: 101 100 References zijn constant, na creatie kunnen ze niet meer worden aangepast. Bij de tweede assignement wordt de waarde aangepast in plaats van het adres. Minder krachtig, maar simpeler.
Function arguments Output: 2500 #include <iostream> using namespace std; void Kwadraat(int* x) { *x = *x * *x; } int main(int argc, char* argv[]) int myNumber = 50; Kwadraat(&myNumber); cout<<myNumber; cin.ignore(); return 0; Output: 2500 “Pass by reference” van een int We geven adres van myNumber mee, de waarde hiervan wordt vervolgens gewijzigd
Function arguments Output: 2500 #include <iostream> using namespace std; void Kwadraat(int& x) { x = x * x; } int main(int argc, char* argv[]) int myNumber = 50; Kwadraat(myNumber); cout<<myNumber; cin.ignore(); return 0; Output: 2500 Met references wordt het een stuk simpeler.
Collections List<T> Dictionary<Key, Val> Stack<T> vector<T> Dictionary<Key, Val> map<key, val> Stack<T> stack<T> Queue<T> queue<T> En meer… maar deze gebruik je het meest. Veel ingebouwde collecties, waardoor gebruik van dynamische allocaties een stuk minder wordt Minder bugs! Net zoals in C#, heeft C++ wat standaard collecties in de standard template library.
Collections - Vector Output: 3 6 9 #include <iostream> #include <vector> using namespace std; int main(int argc, char* argv[]) { vector<int> myArray; myArray.push_back(3); myArray.push_back(6); myArray.push_back(9); for(int i=0;i<myArray.size();i++) cout<<myArray[i]<<endl; } cin.ignore(); return 0; Output: 3 6 9 Include <vector> Type meegeven, net zoals in List van C# Toevoegen met push_back(). Vergelijkbaar met List.Add() Aantal elementen opvragen met size() Wat ook opvalt, we gebruiken geen new Vector, later meer over met classes.
Collections - Vector Type meegeven push_back() - Toevoegen pop_back() - Verwijderen size() - Aantal elementen clear() - Leegt de collectie [] operator En meer… deze zijn de meest gebruikte
Collections - map Output: 0.6 #include <iostream> #include <map> #include <string> using namespace std; int main(int argc, char* argv[]) { map<string, float> prijzen; prijzen["melk"] = 0.6f; prijzen["chicken"] = 2.5f; prijzen["sate"] = 0.28f; prijzen["jello"] = 1.2f; cout<<prijzen["melk"]<<endl; cin.ignore(); return 0; } Output: 0.6 Key type en value type aangeven Met [] operator inserten en opvragen O(log n) tijd
Collections - map [] operator find() erase() size() begin() end() map<key, value>::iterator Find kun je ook gebruiken om te kijken of een element wel/niet in de collectie zit.
Collections - map int main(int argc, char* argv[]) { map<string, float> prijzen; prijzen["melk"] = 0.6f; prijzen["chicken"] = 2.5f; prijzen["sate"] = 0.28f; prijzen["jello"] = 1.2f; map<string, float>::iterator it = prijzen.find("koffie"); if(it == prijzen.end()) cout<<"Geen koffie"<<endl; else cout<<"Koffie kost "<< it->second <<endl; cin.ignore(); return 0; } We maken een iterator object, dit is een object die naar een element in de verzameling kan verwijzen Ook kun je met een iterator de verzameling doorlopen, later meer hierover. Net enumerators van C# Als een element niet bestaat, verwijst deze naar end()
Collections - map Output chicken => 2.5 jello => 1.2 melk => 0.6 sate => 0.28 int main(int argc, char* argv[]) { map<string, float> prijzen; prijzen["melk"] = 0.6f; prijzen["chicken"] = 2.5f; prijzen["sate"] = 0.28f; prijzen["jello"] = 1.2f; map<string, float>::iterator it; for(it = prijzen.begin(); it != prijzen.end(); it++) cout<< it->first << " => " << it->second << endl; } cin.ignore(); return 0; Met de ++ operator laten we de iterator wijzen naar het volgende element, net als bij pointers.
Collections - stack push() pop() top() Bekijk top element zonder te verwijderen
Collections - queue push() pop() front() back() Voeg toe aan staart Verwijder kop front() back() Access kop of staart zonder verwijderen
Collections – More! Veel meer datstructuren Set Multiset Multimap Priority queue Unordered_map Array En meer!