C++ und Pointer - asc.tuwien.ac.at fileC++ und Pointer I Schlüsselwort new I Schlüsselwort delete...

38
C++ und Pointer I Schlüsselwort new I Schlüsselwort delete I Unterschied zu malloc und free 85

Transcript of C++ und Pointer - asc.tuwien.ac.at fileC++ und Pointer I Schlüsselwort new I Schlüsselwort delete...

C++ und Pointer

I Schlüsselwort new

I Schlüsselwort delete

I Unterschied zu malloc und free

85

Pointer mit new

I dyn. Erzeugung von Objekten mit new statt malloc

int* i1 = new int;

*i1 = 25;

I Vergleich zu C

int* i2 = malloc(sizeof(int));

*i1 = 25;

I Erzeugung von Arrays mittel new[]

int* feld = new int[128];

I Vergleich zu C

int* feld2 = malloc(128*sizeof(int));

I Speichergröße muss nicht angegeben werden

86

Freigabe mit delete

I Freigeben von Objekten mit delete statt free

int* i1 = new int;

delete i1;

I Vergleich zu C

int* i2 = malloc(sizeof(int));

free(i2);

I Freigabe von Arrays mittel delete[]

int* feld = new int[128];

delete[] feld;

I Vergleich zu C

int* feld2 = malloc(128*sizeof(int));

free(feld2);

I nicht verwechseln: new, new[], delete, delete[]

I schlecht:

int* t = new int[3];

delete t;

I vielleicht sogar noch schlechter:

int* t = new int;

delete[] t;

87

Pointer auf Objekte

1 #include <iostream>2 using namespace std;34 class Test5 {6 public:7 Test()8 {cout << "constructor" << endl;}9 ~Test()

10 {cout << "destructor" << endl;}11 void Hello()12 {cout << "Hello World!" << endl;}13 };1415 int main()16 {17 Test* t1 = new Test();18 t1->Hello();19 delete t1;20 t1 = 0;21 delete t1;2223 Test* t2 = (Test*) malloc(sizeof(Test));24 t2->Hello();25 free(t2);26 }

I Ausgabe: constructorHello World!destructorHello World!

I Zeiger nach delete auf 0 setzen (Z. 20)

88

Zusammenfassung

I new legt Speicher an und ruft Konstruktor auf• Konstruktor kann sogar ausgewählt werden

Test* t1 = new Test(x,y,z);• new[] ruft immer den Standardkonstruktor auf

I Rückgabewert ist der richtige Zeiger

I Speichergröße muss nicht angegeben werden

I malloc legt nur Speicher an• keine Initialisierung durch malloc• Konstruktor kann nicht mehr aufgerufen werden

I Speicher in Bytes muss angegeben werden

I Rückgabewert ist immer void*• explizites type-cast notwendig

(bei nativen Typen optional)

I delete löscht Speicher und ruft Destruktor auf

I free gibt nur Speicher frei• Destruktor kann nicht mehr aufgerufen werden

I Fazit: Verwende new, delete mit C++

I Verwende malloc, free mit C

I niemals die beiden vermischen• z.B. new mit free freigeben

I es gibt keine Alternative zu realloc• z.B. vector verwenden

89

ReferenzenI Beispielcode

I Unterschied Referenz <-> Pointer

I wann verwendet man was ?

90

Was ist eine ReferenzI Referenzen sind Aliasnamen

I Erzeugung mittels (&)• nicht verwechseln mit Adressoperator• ähnlich zu (*)

int &someRef = someInt;

I Referenz verhält sich wie Zielobjekt

1 #include <iostream>2 using namespace std;34 int main()5 {6 int intOne;7 int &reference = intOne;89 intOne = 5;

10 cout << "intOne: " << intOne << endl;11 cout << "reference: " << reference << endl;1213 reference = 7;14 cout << "intOne: " << intOne << endl;15 cout << "reference: " << reference << endl;16 }

I Ausgabe: intOne: 5reference: 5intOne: 7reference: 7

91

Der Adressoperator beiReferenzen

1 #include <iostream>2 using namespace std;34 int main()5 {6 int intOne;7 int &reference = intOne;89 intOne = 5;

10 cout << "intOne: " << intOne << endl;11 cout << "reference: " << reference << endl;1213 cout << "&intOne: " << &intOne << endl;14 cout << "&reference: " << &reference << endl;15 }

I Ausgabe: intOne: 5reference: 5&intOne: 0x7fff5fbffa2c&reference: 0x7fff5fbffa2c

I Adressen der beiden Variablen sind identisch

I Referenzen werden bei Erzeugung initialisiert• dienen nur als Synonyme für ihr Ziele

92

Funktionsargumente als Zeigerübergeben

1 #include <iostream>2 using namespace std;34 void swap(int *px, int*py)5 {6 int tmp;7 tmp = *px;8 *px = *py;9 *py = tmp;

10 cout << "swap: " << *px << ", " << *py << endl;11 }1213 int main()14 {15 int x=5, y=10;16 swap(&x, &y);17 cout << "main: " << x << ", " << y << endl;18 }

I Ausgabe: swap: 10, 5main: 10, 5

I mühsam durch ständiges Dereferenzieren

I Vorbereitungen für Aufruf nötig

93

Funktionsargumente als Referenzübergeben

1 #include <iostream>2 using namespace std;34 void swap(int &rx, int &ry)5 {6 int tmp;7 tmp = rx;8 rx = ry;9 ry = tmp;

10 cout << "swap: " << rx << ", " << ry << endl;11 }1213 int main()14 {15 int x=5, y=10;16 swap(x, y);17 cout << "main: " << x << ", " << y << endl;18 }

I Ausgabe: swap: 10, 5main: 10, 5

I Syntax sehr viel direkter (leichter)

I genauso leistungsfähig

I keine Vorbereitungen in main nötig

94

Neuzuweisungen

I Vorsicht beim Versuch einer Neuzuweisung

1 #include <iostream>2 using namespace std;34 int main()5 {6 int x;7 int &ref = x;89 x = 5;

10 cout << "x: " << x << endl;11 cout << "ref: " << ref << endl;1213 int y = 8;14 ref = y; //not what you think1516 cout << "x: " << x << endl;17 cout << "y: " << y << endl;18 cout << "ref: " << ref << endl;19 }

I Ausgabe: x: 5ref: 5x: 8y: 8ref: 8

I Referenz wirkt wirklich nur als Alias• Neuzuweisung ist nicht möglich

95

Übersicht Referenzen

I Refenzen sind Synonyme für Variablen

I sehr leichte Syntax

I fast so leistungsfähig wie Zeiger

I Initialisierung mit (&)• nicht mit Adressoperator verwechseln

I immer direkt initialisieren

I nicht versuchen Referenzen erneut zuzuweisen

I wie bei Zeigern auf Lebensdauer achten:

1 int& f()2 {3 int x = 4711;45 /*Achtung: Referenz auf lokale Variable*/6 return x;7 }

I Syntax kann Programmablauf verschleiern• bei Pointern ist immer klar was passiert

I keine vollsändige Alternative zu Pointern• keine Mehrfachzuweisung• Referenzen dürfen nicht 0 sein• kein dynamischer Speicher möglich• keine Felder von Referenzen möglich

96

Vererbung 2

I Polymorphie

I virtuelle Methoden

I abstrakte Klassen

I Mehrfachvererbung

97

Polymorphie

I jedes Objekt der abgeleiteten Klasse ist auch einObjekt der Basisklasse• Vererbung impliziert immer ist-ein-Beziehung

I Jede Klasse definiert einen Datentyp• Objekte können mehrere Typen haben• abgeleitete Klassen haben mindestens zwei

I In C++ kann der jeweils passende Typ verwendetwerden• Diese Eigenschaft nennt man Polymorphie

(griech. Vielgestaltigkeit)

98

in Code gegossen

I Wir betrachten die Klasse Sortierverfahren:

1 class Sortierverfahren{2 protected:3 int N; //Anzahl4 int* a; //zu sort. Zahlen5 public:6 Sortierverfahren();7 Sortierverfahren(int);8 virtual ~Sortierverfahren(){delete[] a;};9 virtual void sort(){cout << "Hallo" << endl;};

10 void ausgabe();11 };

I und die Methode sortMe:

1 void sortMe(Sortierverfahren &s)2 {3 s.sort();4 s.ausgabe();5 }

I sortMe übernimmt Referenz auf Sortierverfahren

I virtuelle Methoden durch Schlüsselwort virtual• ermöglichen späte Bindung (late binding)• Einsprungadresse wird zur Laufzeit ermittelt• dies geschieht intern mittels V-tables

99

in Code gegossen - 2

I restliche Implementierung:

1 Sortierverfahren::Sortierverfahren(int n)2 {3 N = n;4 a = new int[n];5 for (int i = 0; i<n; i++)6 a[i] = 1+rand() % 99;7 }89 void Sortierverfahren::ausgabe()

10 {11 cout << "aktuelle Sortierung: " << endl;12 for (int i = 0; i<N; i++)13 cout << a[i] << endl;14 }

I und abgeleitete Klasse BubbleSort:

1 class BubbleSort : public Sortierverfahren{2 public:3 BubbleSort(int N):Sortierverfahren(N){};4 void sort();5 };67 void BubbleSort::sort()8 {9 for (int i = N-1; i>=1; i--){

10 for(int j = 0; j<i; j++)11 {if (a[j] > a[j+1]) swap(a[j], a[j+1]);}12 }13 }

100

Polymorphie- 2

I BubbleSort redefiniert die Methode sort

I Nun ist BubbleSort auch ein Sortierverfahren

I ) folgende Anweisung macht Sinn

Sortierverfahren *mySort = new BubbleSort(10);

I Bei Zeigern und Referenzen können alle Typen desjeweiligen Objektes verwendet werden

I intern wird Zeiger auf Sortierverfahren gespeichert• zunächst einmal kann nur auf diese

Methoden und Felder zugegriffen werden

I Frage: Was passiert bei Aufruf von mySort->sort()?

I Antwort: genau das Richtige!• durch Einsatz virtueller Methoden• V-table wird angelegt• zur Laufzeit wird entsprechende Funktion

aufgerufen

101

in Code gegossen - 3

I Aufrufender Code:

1 void sortMe(Sortierverfahren& s)2 {3 s.sort();4 s.ausgabe();5 }67 int main()8 {9 BubbleSort mySort(10);

10 mySort.ausgabe();11 sortMe(mySort);12 }

I Ausgabe: aktuelle Sortierung:77 35 78 84 86 81 25 45 54 38aktuelle Sortierung:25 35 38 45 54 77 78 81 84 86(Zufallszahlen)

I Methode sortMe kennt nur s• (Referenz auf Sortierverfahren)

I Bei Aufruf von sort wird Einsprungstelle ausV-table gelesen

I es wird tatsächlich sort von BubbleSort aufgerufen

I Achtung: Klappt nur bei Zeigern und Referenzen

102

was es zu wissen gibt

I virtuelle Methoden erlauben Polymorphie• abgeleitetes Objekt verhält sich wie Basisobjekt

I dies geschieht durch dynamische Bindung

I Methoden als virtuell deklarierenwenn sie polymorph verwendet werden sollen

I in diesem Fall auch virtueller Destruktor• Objekt wird sonst evtl. nicht richtig bereinigt

I Konstruktoren können nicht virtuell sein

I in Java sind Methoden standardmäßig virtuell

I in C++ explizit durch Benutzer

I generell: Erzeugung von V-tables kostet• Code wird dadurch langsamer• virtual macht ohne Polymorphie keinen Sinn

103

Andere Aspekte der Polymorphie

I polymorphes Verhalten ermöglicht Behandlung desabgeleiteten Objektes wie ein Basisobjekt• Compiler betrachtet das Objekt als Basisobjekt

(Slicing)• Zugriff auf zusätzliche Methoden nicht möglich

I Zugriff auch nicht sinnvoll

I zumindest programmiertechnisch unsauber

I Problem: was wenn man doch Zugriff braucht?

I Lösung: dynamic cast

fortbewegungsmittel *A = new car();

car *myCar = dynamic cast <car *> (A);

myCar->fahren();

I gibt Nullpointer zurück wenn cast fehlschlägt

I nur bei polymorph verwendeten Objekten möglich

I es gibt auch noch andere casts:• static cast• const cast• reinterpret cast

104

abstrakte Datentypen

I Die Methode sort von Sortierverfahren ist sinnlos• bietet keinerlei Sortierung• Objekte können in Wahrheit nicht sortieren

I überhaupt sind Objekte dieser Klasse sinfrei• von Sortierverfahren wird nur abgeleitet

I das sollte sich auch im Code wiederspiegeln

I abstrakte Klassen bieten genau das• dienen als Schablone für abgeleitete Klassen• können gemeinsame Funktionen implementieren• müssen nicht alle Details implementieren

I abstrakte Datentypen können nicht instanziiertwerden!• erleichtert Fehlersuche

105

Beispiel

I abstrakte Klasse Sortierverfahren:

1 class Sortierverfahren{2 protected:3 int N; //Anzahl4 int* a; //zu sort. Zahlen5 public:6 Sortierverfahren();7 Sortierverfahren(int);8 virtual ~Sortierverfahren(){delete[] a;};9 virtual void sort() = 0;

10 void ausgabe();11 };

I abstrakte Methode in Zeile 9

I Abstraktion durch =0 nach virtueller Methode

I AM müssen nicht implementiert werden

I AM nennt man auch rein virtuell

I durch eine AM wird die ganze Klasse abstrakt• abstrakte Kl. können nicht instanziiert werden

106

Beispiel

I abgeleitete Klasse InsertionSort:

1 class InsertionSort : public Sortierverfahren{2 public:3 InsertionSort(int N):Sortierverfahren(N){};4 void sort();5 };67 void InsertionSort::sort()8 {9 int key,i;

10 for(int j=1;j<N;j++)11 {12 key=a[j];13 i=j-1;14 while(a[i]>key && i>=0)15 {16 a[i+1]=a[i];17 i--;18 }19 a[i+1]=key;20 }21 }

I InsertionSort redefiniert die AM sort• alle AM müssen redefiniert werden

107

Beispiel

1 int main()2 {3 BubbleSort mySort(10);4 InsertionSort mySort2(10);5 Sortierverfahren testSort(10);6 mySort.ausgabe();7 mySort2.ausgabe();8 sortMe(mySort);9 sortMe(mySort2);

10 }

I Ausgabe: aktuelle Sortierung:77 35 78 84 86 81 25 45 54 38aktuelle Sortierung:73 18 88 72 64 30 35 17 98 38aktuelle Sortierung:25 35 38 45 54 77 78 81 84 86aktuelle Sortierung:17 18 30 35 38 64 72 73 88 98(Zufallszahlen)

I Eigentlich gelogen: Fehler in Zeile 5• error: cannot declare variable testSort

to be of abstract type Sortierverfahren

108

Übersicht - abstrakte Klassen

I dienen als Schablone / Schnittstelle für Vererbung

I können nicht instanziiert werden• Fehlerfindung durch Compiler!

I Was Sie tun sollten:• Verwenden Sie AK für gemeinsame Funktionen

(delegieren nach oben)• jedoch nur wenn sie für alle Kinder relevant sind• redefinieren Sie alle abstrakten Methoden• definieren Sie alle Methoden abstrakt die zu

redefinieren sind

109

Mehrfachvererbung

I C++ erlaubt Vererbung mit multiplen Basisklassen

I Syntax:

class Auto : public Wertgegenstand, public Fortbew{ . . . }

I Vertieft Konzept der Objektorientierung• erhöht Wiederverwendbarkeit von Code

I Problem: Mehrdeutigkeiten (nächste Folie)

110

Diamantvererbung

I Es könnte eine gemeinsame Oberklasse geben

I führt zu Mehrdeutigkeit• Felder und Methoden sind mehrfach vorhanden• unklar worauf zugegriffen werden soll• Speicherverschwendung• schlimmstenfalls: Objekte inkonsistent

111

Diamantvererbung 2

I Klasse Matrix hat ein das Feld int n

I beide abgeleiteten Klassen erben int n

I SPD erbt von zwei Klassen• SPD hat int n doppelt

I Lösung 1: Zugriff mittels vollem Namen

symmMat::n bzw. posDefMat::n

I unschön, da Speicher dennoch doppelt• unübersichtlich• schlimmstenfalls sogar verschiedene Werte

I Lösung 2: virtuelle Basisklassen

class symmMat : virtual public matrixclass posDefMat : virtual public matrix

I virtuelle Basisklasse wir nur einmal eingebunden

I Literatur: Uneinigkeit ob Mehrfachvererbung sinn-voll

I Konzept nicht möglich in Java, C#

112

sonstiges überKlassen

I const

I static

I inline

113

konstante Funktionen

I konstante Funktionen ändern die Klasse nicht

I Deklaration mit nachgestelltem const• nach Methodenname und vor Semikolon

I zum Beispiel in car :

void setAge(int a);

int getAge() const;

I set-Methoden können natürlich nicht konstant sein

I Ziele:• möglichst kontrollierter Zugriff• Fehler schon zur Kompilierung abfangen• richtigen Gebrauch der Klasse vorschreiben

) verhindert inkonsistente Objekte

I guter Stil:• möglichst viel als const deklarieren

) Fehler vor der Fertigstellung abfangen

I ähnlich zu Zugriffskontrolle mit private, public

114

ein Beispiel

1 #include <iostream>2 #include <string>3 using namespace std;45 class car6 {7 private:8 int wert;9 int age;

10 public:11 int getAge() const;12 void setAge(int age) {this->age = age;};13 };1415 int car::getAge() const16 {17 return age++;18 }1920 int main()21 {22 car TestCar;23 TestCar.setAge(10);24 cout << TestCar.getAge() << endl;25 }

I Fehler in Methode getAge()• increment of data-member ’car::age’ in

read-only structure

I Wichtig: const auch in Methodendefinition• gehört zur Signatur

115

warum so viel Kontrolle ?

I Fakt ist: alle Programmierer machen Fehler• Code läuft beim ersten mal nie richtig

I Großteil der Entwicklungszeit geht in Fehlersuche

I Wie unterscheiden sich Profis von ’Anfängern’ ?• durch effizientere Fehlersuche

I Compiler-Fehler sind leicht einzugrenzen• es steht sogar die Zeilennummer dabei

I Laufzeitfehler sind viel schwieriger zu finden• Programm ’läuft’, tut aber nicht das richtige• manchmal fällt der Fehler ewig nicht auf

) sehr schlecht z.B. bei kommerzieller Software

I ) möglichst viele Fehler durch Compiler abfangen

I Methoden werden mit Verstand geschrieben

I das sollte sich im Code wiederspiegeln• gehören Daten strikt zu einer Klasse ) private• Liest die Methode nur Daten aus ) const• Zugriff kontrollieren mittels get und set

116

inline Funktionen

I Traditionell in der OO: viele kleine Methoden• man denke an get, set

I ’Problem’: Funktionsaufrufe sind nicht umsonst

I ’Ausweg’: inline Funktionen• Code wird direkt eingefügt (ähnlich zu Makros)

1 #include <iostream>2 using namespace std;34 inline int min(int a, int b)5 {6 return a<b ? a : b;7 }89 int main()

10 {11 cout << min(4, 3) << endl;12 cout << min(3, 4) << endl;13 }

I Umsetzung durch Compiler1 #include <iostream>2 using namespace std;34 int main()5 {6 cout << 4<3 ? 4 : 3 << endl;7 cout << 3<4 ? 3 : 4 << endl;8 }

117

inline Funktionen 2

I Deklaration durch Schlüsselwort inline

I Compiler versucht Code direkt einzufügen• inline-Deklaration für Compiler nicht zwingend• Compiler wägt ab und entscheidet nach Nutzen

) evtl. hat inline nicht gewünschten Effekt

I Vorteile:• Code kann schneller werden (kein Overhead)• Optimierungspotential

Berechnungen zur Kompilierzeit möglich

I Nachteile:• Code wird länger• ausführbare Datei wird größer

I Fazit:• inline bei kurzen Funktionen die oft verwendet

werden

118

statische Daten

I Daten die zu keinem konkreten Objekt gehören• die Daten gehören zu der Klasse• sie existieren nur einmal für die gesamte Klasse• sie sind dennoch an die Klasse gebunden

1 #include <iostream>2 using namespace std;34 class mitarbeiter{5 private:6 string name;7 string Abteilung;8 public:9 static int count;

10 mitarbeiter(){++count;};11 };1213 int mitarbeiter::count = 0;1415 int main(){16 mitarbeiter tom;17 mitarbeiter bob;18 cout << mitarbeiter::count << endl;19 cout << tom.count << endl;20 }

I Ausgabe: 22

I Deklaration mit Schlüsselwort static

I Initialisierung außerhalb von Methoden / Klassen

I Zugriff über Klassenname oder Objekt (public)

I globale Daten ) möglichst sparsam verwenden

119

statische Daten 2

I private static Daten können nur überKlassennamen verwendet werden

I Zugriff nur über statische Funktionen

I statische Funktionen können nur auf statischeDaten zugreifen

1 #include <iostream>2 using namespace std;34 class mitarbeiter{5 private:6 string name;7 string Abteilung;8 static int count;9 public:

10 static int getCount(){return count;};11 mitarbeiter(){++count;};12 };1314 int mitarbeiter::count = 0;1516 int main(){17 mitarbeiter tom;18 mitarbeiter bob;19 cout << mitarbeiter::getCount() << endl;20 }

I Ausgabe: 2

120

noch ein Beispiel

1 #include <iostream>2 using namespace std;34 enum Art {BRUCH, FLOAT};56 class ratio{7 private:8 int z, n;9 static Art ausgabeArt;

10 public:11 ratio(int a, int b){z=a; n = b;};12 static int setzeAusgabeArt(Art art);13 void print();14 };1516 Art ratio::ausgabeArt = BRUCH;1718 int ratio::setzeAusgabeArt(Art art)19 {ausgabeArt = art;}2021 void ratio::print()22 {23 if (ausgabeArt == BRUCH)24 {cout << z << "/" << n << endl;}25 else26 {cout << double(z)/double(n) << endl;}27 }2829 int main(){30 ratio myRatio1(3,5);31 ratio myRatio2(1,7);32 myRatio1.print();33 myRatio2.print();34 ratio::setzeAusgabeArt(FLOAT);35 myRatio1.print();36 myRatio2.print();37 }

I Ausgabe: 3/5 1/70.6 0.142857

121

und weiter ?

I templates

I Standardbibliothek

I auto ptr

I Headerfiles

I exception handling

I Softwaredesign / UML

I Programmierprojekt

122