TDIU20
Pontus Haglund
Institutionen för datavetenskap
1 Vad gjorde vi förra gången?
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
Dagens föreläsning
1. Pekare
‚ new
‚ delete
‚ avreferering
‚ adress
‚ nullptr 2. Objektorientering
‚ Composition
‚ Nästlad klass
1 Vad gjorde vi förra gången?
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
Vad gjorde vi förra gången
Kom ihåg att ni alltid kan gå tillbaka till förra föreläsningen och se slides i sin helhet.
‚ Felhantering
‚ Operatorer
‚ binära
‚ unära
‚ interna
‚ externa
‚ Typkonvertering
‚ static_cast
‚ operatorer
‚ konstruktorer
5 / 52
Felhantering i c++
‚ Testar att göra övre blocket
‚ Vid fel (en throw) hoppa till ett av catch‐blocken
‚ Fel kastas upp en nivå i programmet
try //Försök göra {
}
//Fånga exception catch(logic_error& e) {
}
catch(...) {
}
Att tänka på
‚ try är i princip lika snabbt som vilken annan kod som helst
‚ Exceptions är VÄLDIGT långsamma när du kastar dem
‚ Använd endast i exceptionella situationer
‚ Använd INTE exceptions som en styrstruktur
‚ Mycket långsammare
‚ Dåligt praxis
7 / 52
Binära operatorer
//Exempel c1 + c2;
c3 = c1;
cout << c3;
//Vad C++-kompilatorn "ser"
c1.operator+(c2);
c3.operator=(c1);
cout.operator<<(c3);
class Complex {
public:
Complex operator+(Complex const& rhs) const;
Complex& operator=(Complex const& rhs);
//...
};
std::ostream& operator<<(std::ostream& lhs, Complex const& rhs);
Unära operatorer
Har 1 operand. Vår klass är den operanden.
//Exempel c1++; //post ++c1; //pre --c1; //pre c1--; //post -c1; //???
//Hur c++ ser det
c1.operator++(int{}); //?
c1.operator++();
c1.operator--();
c1.operator--(int{}); //?
c1.operator-();
//Objektet är vänstersidan //I Complex.h
class Complex {
public:
//...
Complex& operator++();
Complex operator++(int);
Complex& operator--();
Complex operator--(int);
Complex& operator-();
//...
};
postinkrement är lite av ett fulhack...
9 / 52
Typomvandling
‚ Att omvandla en typ till en annan
‚ Exempelvis göra om en int till en double
‚ Varför!
‚ Hur?
static cast
‚ Kontrolleras compile time
‚ Krävs att objektet vet hur det konverteras till typen eller visa versa
string cs1 = static_cast<string>( Complex{1, 3} );
Complex c{1, 3};
string cs2 = static_cast<string>( c );
‚ Hur kan vi se till att Complex vet hur den skall konverteras till string?
11 / 52
Hur vet klassen?
Genom att lägga till den operatorn i klassen. Objektet vet hur den kan konverteras till målet
//Unary operator //Medlemsfunktion
Complex::operator string() {
return to_string();
}
class Complex {
//...
operator string();
operator int();
//...
}
Observera:
‚ Ingen returtyp
‚ Fungerar för andra typer
Typkonverterande konstruktorer
Målet vet hur objektet kan konverteras till sig
‚ konstruktorn sköter
konvertering från en annan typ till din typ
//h-fil class Complex {
//...
// Konstruktor som tar en parameter av typen Complex(std::string const& str);
//...
}
1 Vad gjorde vi förra gången?
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
Minne
‚ Datorns minne löper från 0‐N
‚ N = minnetsstorlek ‐ 1
‚ Består av bytes
‚ Siffran i intervallet är platsen, adressen
‚ 1 megabyte är 0 till 220‐1
‚ Vi kan visualisera minnet
15 / 52
Vad har en adress?
‚ Allt vi lägger i minnet har en adress
‚ Vanligtvis använder vi variabler för att komma åt den biten av minne
‚ Men vi kan också lagra och manipulera adresser
// Ger oss en int-stor bit av minnet // Vi kan komma åt minnet med variabeln int var{10};
// Ger oss en char-stor bit av minnet // -||-
char var2{'h'};
Lagra adress
// Vad händer?
#include <iostream>
using namespace std;
int main() {
int var{};
int* ptr{};
cout << ptr << endl;
}
17 / 52
Lagra adress
// Vad händer?
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{17};
cout << ptr << endl;
}
Lagra adress
// Vad händer?
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{var};
cout << ptr << endl;
}
19 / 52
Lagra adress
// Vad händer?
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{&var};
cout << ptr << endl;
}
Avreferera en adress
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{&var};
cout << *ptr << endl;
// ^^^^
// avreferering med * operatorn }
21 / 52
Nullptr
int main() {
int* ptri{nullptr};
cout << ptri << endl;
}
int main() {
int* ptri{nullptr};
cout << *ptri << endl;
}
0
Segmentation fault
nullptr, inte 0 eller NULL!
sizeof
Storleken i chars (bytes), är en operator Kontroll av storleken på ett heltal och en adress
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{&var};
cout << sizeof(var) << endl;
cout << sizeof(ptr) << endl;
}
23 / 52
sizeof
Kontroll av storleken på två (samma) heltal
#include <iostream>
using namespace std;
int main() {
int var{17};
int* ptr{&var};
cout << sizeof(var) << endl;
cout << sizeof(*ptr) << endl;
}
sizeof
Kontroll av storleken på en sträng och en adress
#include <iostream>
using namespace std;
int main() {
string var{"12345678901234567890"};
string* ptr{&var};
cout << sizeof(var) << endl;
cout << sizeof(ptr) << endl;
}
25 / 52
sizeof
Kontroll av storleken på två (samma) strängar
#include <iostream>
using namespace std;
int main() {
string var{"12345678901234567890"};
string* ptr{&var};
cout << sizeof(var) << endl;
cout << sizeof(*ptr) << endl;
}
Stjärna och Ampersang
Unära operatorn “Stjärna” *
‚ I uttryck framför pekare: Avreferera (gå till adress)
‚ I deklarationer: Skapa adressvariabel (pekare) Unära operatorn “Ampersang” &
‚ I uttryck framför variabel: Hämta variabelns adress
‚ I deklarationer: Skapa referensvariabel (alias till variabel)
Notera 1: Båda operatorerna finns även i binära varianter Notera 2: “variabel” ovan inkluderar “pekarvariabel”
27 / 52
Stjärna och Ampersang cont.
int var{4};
int* ptri{ &var }; // Pekardeklaration och adressoperatorn cout << *ptri; // Avreferering
// Tre referensdeklarationer
ostream& operator<<(ostream& lhs, Complex const& rhs) {
//...
}
Vad händer här?
Ändras adressen eller vad som ligger på den adressen?
int var{42};
int* ptri{&var};
ptri = 109;
cout << var << endl;
29 / 52
Vad händer här?
Ändras adressen eller vad som ligger på den adressen?
int var{42};
int* ptri{&var};
*ptri = 109;
cout << var << endl;
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
31 / 52
Vad gör new?
‚ Vilka delar av minnet allokeras till c++ vid start?
‚ New ger oss minne i heapen / free store
‚ Massor av ansvar...
Varför behövs både stack och heap(new)?
Varför pekare ‐ räcker det inte med vanliga variabler?
Varför new ‐ räcker det inte med vanliga variabler?
I alla tidigare slides behövs varken pekare eller new!
Men vad händer här?
int* give_me_more_memory() { int memory{}
int* ptri{&memory}
return ptri // serious problem!
}
int main() {
int* ptri{ give_me_more_memory() }
*ptri = 4711 }
33 / 52
Minnesallokering på heapen och nåbar läcka
Minnesläcka: Vad händer här?
int* give_me_more_memory() {
int* ptri{ new int{} };
return ptri; // who is responsible?
}
int main() {
int* ptri{ give_me_more_memory() }
*ptri = 4711 cout << *ptri
} // leak: serious problem
Onåbar minnesläcka och återlämning med delete
Minnesläcka: Vad händer här?
int main() {
int* ptri{ give_me_more_memory() }
*ptri = 4711 cout << *ptri
ptri = give_me_more_memory() // leak: serious problem delete ptri
}
35 / 52
Ansvarsfördelning och minnestätning
void free_up_this_memory(int* adr) { delete adr;
}
int main() {
int* ptri{ give_me_more_memory() };
*ptri = 4711;
cout << *ptri;
free_up_this_memory(ptri);
ptri{ give_me_more_memory() };
*ptri = 17;
cout << *ptri;
free_up_this_memory(ptri);
}
Fortfarande finns en enklare lösning
int main() {
int* ptri{ new int{4711} };
cout << *ptri;
delete ptri;
int* ptri{ new int{17} };
cout << *ptri;
delete ptri;
}
37 / 52
Se upp för ogiltiga pekare!
int main() {
int* ptri{ new int{4711} } cout << *ptri
delete ptri
*ptri = 17 // serious problem!
cout << *ptri // serious problem!
}
Se upp för stackadresser!
int main() {
int var{17};
int* ptri{ new int{4711} } cout << ptri
delete ptri ptri = &var cout << ptri
delete ptri // serious problem!
}
39 / 52
Enklast är ändå bäst och säkrast
int main() {
int ptr{4711};
cout << ptri;
int ptr{17};
cout << ptri;
}
Så hur blir pekare meningsfulla?
Dynamisk datastruktur!
Med verktygen pekare och new kan vi skapa datatyper som går att bygga ut!
struct data_and_pointer {
int stored_data;
data_and_pointer* more_memory;
};
41 / 52
void* och cast
‚ Undvik void*
‚ Vid hantering av pekare och new är vi på en låg nivå
‚ Typsystemet är vårt sista skydd
‚ Poängen är att ni ska låta bli i nästan alla fall
void* pv = new int{0}
*pv = 2 // Error, varför?
int pi{pv} // Error, varför?
pv[1] // Not subscriptable int* pi = static_cast<int*>(pv)
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
43 / 52
Block och destruktorer
‚ När en variabel går ut ur scope kommer den destrueras
‚ När ett objekt destrueras körs dess destruktor
‚ Ett objekt destrueras när dess minne avallokeras
‚ Rekursivt
{
vector<int> v{};
v.push_back(4);
cout << v.at(0) << endl;
} //Här destrueras v
class Array {
public:
Array();//Konstruktor
~Array();//Destruktor private:
//array containing data int* data;
}
Block och destruktorer
class Array {
public:
Array(int size);//Konstruktor
~Array();//Destruktor private:
//array containing data int* data;
}
Array::Array(int size) : data{new int[size]}
{}
Array::~Array() {
delete[] data;
}
45 / 52
Destruktor vs Konstruktor
Det är Konstruktorns jobb att initiera datamedlemmarna korrekt.
Det är destruktorns jobb att destruera datamedlemmarna korrekt.
Pekare och medlemmar
class Array {
public:
Array(int size);
~Array();
void add(int index, int value);
int get(int idx) const;
private:
int* data;
};
int main() {
Array* pa = new Array{4};
pa.add(0, 4); // Syntaxfel (*pa).add(0, 4); // Vad? Hur?
pa -> add(0, 4); // Skillnad?
}
47 / 52
Destruktor vs Konstruktor konceptuellt
Destructors are cenceptually simple but are the foundation for many of the most effective C++ programming
techniques. The basic idea is simple:
‚ Whatever resources a class object needs to function, it acquires in a constructor.
‚ During the object’t lifetime it may release resources and acquire new ones.
‚ At the end of the object’ts lifetime, the destructor releases all resources still owned by the object.
Från: Bjarne S. 2014 Programming Principles...
Felhantering Operatorer Typkonvertering
2 Grundläggande om pekare 3 New
4 Destruktorer 5 Composition
49 / 52
Vad är composition
‚ Nyckelkoncept inom OOD och OOP
‚ Medlemmen är en del av objektet
‚ Medlemmen kan bara tillhöra ett objekt åt gången
‚ Medlemmens Lifetime styrs av objektet
‚ Medlemmen känner inte till objektet
UML
‚ Composistion visas med ifylld diamant
‚ Är en del av
‚ Kardinalitet
51 / 52
Vi tänker oss en stack
‚ Vad är en stack?
‚ Hur kan vi implementera en stack?
‚ Med pekare?
‚ Syntaktiskt‐/Semanstiskt korrekt
Head
Node
Node
5
10
Nästlad klass
‚ Tydligt relation mellan Element och Stack
‚ Inkapslad trots public åtkomst
‚ Vilken klass hör vad till?
class Stack { public:
Stack();
Stack(std::initializer_list<int> il);
~Stack();
void push(int v);
int pop();
std::string to_string();
bool empty();
private:
struct Element { Element* next;
int value;
};
Element* first;
};
ostream& operator<<(ostream& os, Stack const& lhs);