Was sind Klassen Objektorientiertes Design I Klassen I Typen I Objekte I Klassen sind benutzerdefinierte Datentypen I Verwendung völlig analog zu int, string I Klassen erweitern struct aus C erlauben Methoden (Funktionen) I Klassen erlauben das Prinzip der Datenkapselung Code wird besser wiederverwendbar black-box-implementierung möglich I Klassen sind Schablonen für Objekte I Klassen sind der Grundbaustein zur OO I Vergleich zu C Klasse b= typedef + struct 5 Beispiel 5 class Student 7 string name; 8 string studiengang; int matrikelnummer; 10 11 void lernen(); 12 void testschreiben(); I Deklaration mit Schlüsselwort class I enthält Daten (members) analog zu struct aus C I enthält Funktionen (Methoden) erlaubt es komplette Objekte abzubilden I zu Namenskonvention: C++ ist case sensitive Groß- und Kleinschreibung beachten I Klassen (Coding Standard) AlleWorteCapitalizedOhneUnderlines I Methoden, Funktionen und Variablen ersteswortkleinrestcapitalizedohneunderlines Objekte I Objekte sind Instanzen einer Klasse Sie sind die tatsächlichen Variablen werden angelegt wie bei primitiven Typen entsprechen Variablen vom struct Datentyp Student Marcus; int x; I Zugriff auf Klassenelemente mit Punktoperator (.) Marcus.name = "Marcus Page"; Marcus.Lernen(); I Achtung: An Objekte zuweisen, nicht an Klassen int = 5; Student = "Marcus Page"; // Fehler // Fehler 6 7
Zugriffskontrolle I Klassen und Objekte dienen der Abstraktion genaue Implementierung nicht wichtig zum Beispiel durch Bibliotheken I Benutzer soll so wenig wissen wie möglich black-box Programmierung nur Ein- und Ausgabe müssen bekannt sein I richtiger Zugriff muss sichergestellt werden I Schlüsselwörter private und public I private (Standard) Zugriff nur von Methoden der gleichen Klasse I public erlaubt Zugriff von überall I protected teilweiser Zugriff von außen (später) Beispiel 5 class Student 7 private: 8 string studiengang; 10 public: 11 string name; 12 int matrikelnummer; 1 void lernen(); 1 void testschreiben(); 15 }; 16 17 int main() 18 { 1 student Marcus; 20 Marcus.name = "Marcus Page"; 21 Marcus.matrikelnummer = 5552555; 22 Marcus.studiengang = "Mathematik"; 2 } I Fehler in Zeile 22: std::string student::studiengang is private 8 Klassenmethoden implementieren 1 class Student 2 { public: string name; 5 int matrikelnummer; 6 void Lernen(); 7 void TestSchreiben(); 8 }; 10 void Student::lernen() 11 { 12 cout << "oh mann..." << endl; 1 } 1 15 void Student::testSchreiben() 1 17 cout << "geschafft!" << endl; 18 } 1 20 int main() 21 { 22 Student Marcus; 2 Marcus.lernen(); 2 Marcus.testSchreiben(); 25 } I Ausgabe: oh mann... geschafft! I Implementierung wie bei anderen Funktionen I Zugehörigkeit zur Klasse durch (::) student :: testschreiben() I Deklaration auch in Header-Datei möglich (wie C) 0 Deklaration in Header-Datei I student.h 1 #include <string> class Student 6 public: 7 string name; 8 int matrikelnummer; void lernen(); 10 void testschreiben(); 11 }; I student.cpp 2 #include "student.h" 5 void Student::lernen() 7 cout << "oh mann..." << endl; 8 } 10 void Student::testSchreiben() 11 { 12 cout << "geschafft!" << endl; 1 } 1 1 17 Student Marcus; 18 Marcus.lernen(); 1 Marcus.testSchreiben(); 20 } 1
Klassen auslagern 1/2 Methoden Implementieren 2 5 class Student 8 string name; int matrikelnummer; 10 void lernen(){cout << "oh mann..." << endl;}; 11 void testschreiben(){cout << "geschafft!" 12 << endl;}; 1 1 17 Student Marcus; 18 Marcus.lernen(); 1 Marcus.testSchreiben(); 20 } I Implementierung direkt bei der Klassendefinition I nach Definition in geschweiften Klammern {} bietet sich an für kurze Methoden (get, set) häßlich und unübersichtlich für längere I Klasse in Datei auslagern 5 class Student 8 string name; int matrikelnummer; 10 void lernen(); 11 void testschreiben(); 12 }; 1 1 void Student::lernen() 1 16 cout << "oh mann..." << endl; 17 } 18 1 void Student::testSchreiben() 20 { 21 cout << "geschafft!" << endl; 22 } I mit include einbinden 1 #include "student6.cpp" 2 int main() { 5 Student Marcus; 6 Marcus.Lernen(); 7 Marcus.TestSchreiben(); 8 } I unschön (siehe nächste Folie) 2 Wozu Zugriff einschränken? 1/ Klassen auslagern 2/2 I besseres Vorgehen: Dateien student6.h student6.cpp prog.cpp I kompilieren mittels: g++ -c student6.cpp g++ -c prog.cpp g++ -o prog prog.o student.o I Analog zu C! class Ratio 6 public: 7 int z, n; //ratio = z/n 8 }; 10 int main() 11 { 12 Ratio myratio; 1 myratio.z = -1000; 1 myratio.n = 0; 15 } I wie sinnvolle Werte sicherstellen? (Z. 1) mögliche Fehlerquellen direkt ausschließen Programmierer muss sich um möglichst wenig kümmern 5
Wozu Zugriff einschränken? / Wozu Zugriff einschränken? 2/ 5 class Car 8 int wert; string farbe; 10 }; 11 12 int main() 1 { 1 Car TestCar; 15 TestCar.wert = -1000; 16 int preis = TestCar.wert - 500; 17 } I wie sinnvolle Werte sicherstellen? (Z. 15) I was wenn sich Implementierung ändert? Preis könnte sich neu berechnen (Z. 16) zum Beispiel durch neue Version ) kompletten Code umschreiben 5 class Car 7 private: 8 int wert; string farbe; 10 public: 11 int getpreis(){return wert - 500;}; 12 void setpreis(int x); 1 15 void Car::setPreis(int x) 1 17 if(x < 500){wert = 500;} 18 else{wert = x;}; 1 } 20 21 int main() 22 { 2 Car TestCar; 2 TestCar.setPreis(-1000); 25 cout << TestCar.getPreis() << endl; 26 } I Ausgabe: 0 I vermeidet inkonsistente Objekte I Änderung der Preisberechnung ) nur eine Methode umschreiben 6 7 Namensgleichheit 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 Zugriff kontrollieren mittels get und set (reine Daten sollten immer private sein) 1 /* class definitions */ 2 void Student::print() { 5 cout << "Name: " << name << endl; 6 cout << "Mat. Nu.: " << matrikelnummer << endl; 7 } 8 void Car::print() 10 { 11 cout << "Wert: " << wert << endl; 12 cout << "Farbe: " << farbe << endl; 1 } 1 1 17 Student Marcus; 18 Marcus.name = "Marcus Page"; 1 Marcus.matrikelnummer = 555555; 20 Car TestCar; 21 TestCar.farbe = "Dakota Beige"; 22 TestCar.wert = 10000; 2 2 Marcus.print(); 25 TestCar.print(); 26 } I Ausgabe: Name: Marcus Page Mat. Nu.: 555555 Wert: 10000 Farbe: Dakota Beige I Es wird immer die zugehörige Funktion aufgerufen Punktoperator: Marcus.print(); 8
Objekte inititalisieren I Klassen können viele Daten haben bislang: alle einzeln inititalisieren Übersicht Klassen I Klassen sind benutzerdefinierte Datentypen I zentrale Datenstruktur der OO Programmierung I Sie erlauben Datenkapselung I Sie erlauben Datenabstraktion Kenntnis der Implementierung nicht nötig warten / wiederverwenden / weitergeben leicht I ermöglichen kontrollierte Zugriffe verwende private Daten verwende get und set Methoden I komplette Objekte können nachgebaut werden enthalten mehr als nur die reinen Werte keine Probleme durch doppelte Namensgebung 5 class Car{ 7 int preis; 8 int speed; int age; 10 string farbe; 11 12 /* public get and set methods */ 1 1 17 Car TestCar; 18 TestCar.setPreis(1000); 1 TestCar.setFarbe("Dakota Beige"); 20 TestCar.setSpeed(150); 21 TestCar.setAge(); 22 } I mühsam besonders für Große Objekte I mühsam wenn mehrere Objekte angelegt werden I besser direkt bei der Initialisierung spezielle Methoden (Konstruktoren) heißen wie die Klasse selbst 50 51 Konstruktoren 5 class Car{ 7 int preis; 8 int speed; int age; 10 string farbe; 11 public: 12 Car(int p, int s, int a, string f); 1 15 Car::Car(int p, int s, int a, string f) 1 17 preis = p; 18 speed = s; 1 age = a; 20 farbe = f; 21 } 22 2 int main() 2 { 25 Car TestCar1(1000, 150,, "Dakota Beige"); 26 Car TestCar2(700, 100, 6, "Feuerrot"); 27 } I Konstruktoren dienen zum initialisieren I werden automatisch aufgerufen Konstruktoren 2 1 /* class definition */ 2 Car::Car(int p, int s, int a, string f): preis(p), speed(s), age(a),farbe(f){} 5 6 Car::Car(int preis, int speed) 7 { 8 this->preis = preis; this->speed = speed; 10 this->age = 10; 11 this->farbe = "unknown"; 12 } I alle Konstruktoren heißen wie die Klasse unterscheiden sich durch ihre Signatur I Kurzschreibweise mittels Doppelpunkt (:) (Z. ) direkte Zuweisung an Membervariablen I Standardkonstruktor nur wenn es keinen anderen gibt I der Zeiger this Zeiger der auf das Objekt selbst zeigt kann lokale Namensgleichheiten auflösen nützlich wenn man das Objekt übergeben muss I Standardkonstruktor wenn kein anderer definiert I es kann mehrere in einer Klasse geben 52 5
Destruktoren 1 /* includes and namespace definition */ 2 class Car{ private: 5 6 /* private member definition */ 7 8 public: Car(int p, int s, int a, string f); 10 ~Car(){}; 11 }; 12 1 /* constructor definition */ 1 1 17 Car TestCar1(1000, 150,, "Dakota Beige"); 18 Car TestCar2(700, 100, 6, "Feuerrot"); 1 } I werden beim Auflösen des Objektes aufgerufen Überladen I Überladen von Funktionen I Überladen von Operatoren I Was überlädt man? I heißen wie die Klasse mit Tilde ( ) I Standarddestruktor wird ggfs. automatisch generiert I kompliziertere Objekte müssen aufgeräumt werden I Objekt könnte auch Netzwerk-Socket sein muss geschlossen werden evtl. Buffer leeren... 5 55 Überladen von Funktionen class Car 7 int preis; 8 int speed; public: 10 Car(int p, int s){preis=p; speed=s;}; 11 void drive(); 12 void drive(int km); 1 15 void Car::drive() 1 17 cout << "10 km gefahren" << endl; 18 } 1 20 void Car::drive(int km) 21 { 22 cout << km << " km gefahren" << endl; 2 } 2 25 int main() 2 27 Car TestCar(1000,150); 28 TestCar.drive(); 2 TestCar.drive(5); 0 } I Ausgabe: 10 km gefahren 5 km gefahren 56 Überladen von Funktionen 2 class Real 7 double value; 8 public: Real(double v){value = v;}; 10 double getvalue(){return value;}; 11 void print(); 12 void print(int nks); 1 15 void Real::print() 1 17 cout.precision(6); 18 cout << getvalue() << endl; 1 } 20 21 void Real::print(int nks) 22 { 2 cout.precision(nks); 2 cout << getvalue() << endl; 25 } 26 27 int main() 28 { 2 Real myreal(21.887621); 0 myreal.print(); 1 myreal.print(10); 2 } I Ausgabe: 21.888 21.887621 57
Standardwerte class Real 7 double value; 8 public: Real(double v){value = v;}; 10 double getvalue(){return value;}; 11 void print(int nks = 10); 12 }; 1 1 void Real::print(int nks) 1 16 cout.precision(nks); 17 cout << getvalue() << endl; 18 } 1 20 int main() 21 { 22 Real myreal(21.887621); 2 myreal.print(); 2 myreal.print(); 25 } Überladen von Funktionen I mehrere Funktionen gleichen Namens möglich analog zu Konstruktoren unterscheiden sich durch ihre Signatur I diesen Vorgang nennt man überladen I durch Aufruf wird die richtige ausgewählt Compiler erkennt dies über Signatur I Standardwerte können direkt gesetzt werden sinnvoll wenn Änderungen nur marginal nicht gut wenn z.b. andere Algorithmen nötig I Ausgabe: 21.887621 21.88 58 5 Überladen von Operatoren class Complex 7 double vreal; 8 double vimag; public: 10 Complex(double r, double i); 11 double getreal(){return vreal;}; 12 double getimag(){return vimag;}; 1 void setreal(double r){vreal = r;}; 1 void setimag(double i){vimag = i;}; 15 void print(); 16 }; 17 18 Complex::Complex(double r, double i) 1 { 20 vreal = r; 21 vimag = i; 22 } 2 2 void Complex::print() 2 26 cout << getreal() << " + " << getimag() 27 << "i" << endl; 28 } 2 0 int main() 1 { 2 Complex mycomplex(, 5); mycomplex.print(); } I Ausgabe: + 5i 60 Überladen von Operatoren 2 class Complex 6 /* private data */ 8 Complex(double r = 0, double i = 0); Complex add(complex rhs); 10 11 /* remaining public data */ 12 }; 1 1 /* constructor and print definition */ 15 16 Complex Complex::add(Complex rhs) 17 { 18 double rtmp = getreal() + rhs.getreal(); 1 double itmp = getimag() + rhs.getimag(); 20 return Complex(rTmp, itmp); 21 } 22 2 int main() 2 { 25 Complex mycomplex(, 5), mycomplex2(2, 7); 26 Complex mycomplex; 27 mycomplex = mycomplex.add(mycomplex2); 28 mycomplex.print(); 2 mycomplex2.print(); 0 mycomplex.print(); 1 } I Ausgabe: + 5i 2 + 7i 5 + 12i 61
Überladen von Operatoren class Complex 6 /* private data */ 8 Complex(double r = 0, double i = 0); Complex operator+(complex rhs); 10 11 /* remaining public data */ 12 }; 1 1 /* constructor and print definition */ 15 16 Complex Complex::operator+(Complex rhs) 17 { 18 double rtmp = getreal() + rhs.getreal(); 1 double itmp = getimag() + rhs.getimag(); 20 return Complex(rTmp, itmp); 21 } 22 2 int main() 2 { 25 Complex mycomplex(, 5), mycomplex2(2, 7); 26 Complex mycomplex; 27 mycomplex = mycomplex + mycomplex2; 28 mycomplex.print(); 2 mycomplex2.print(); 0 mycomplex.print(); 1 } I Ausgabe: + 5i 2 + 7i 5 + 12i 62 Überladen von Operatoren I Operatoren selbst können überladen werden mittels Schlüsselwort operator wie im Beispiel operator+ I Methode add funktioniert auch ist aber viel mühsamer in der Verwendung I vgl. Syntax: mycomplex = mycomplex.add(mycomplex2); mycomplex = mycomplex + mycomplex2; I interne Auswertung: mycomplex = mycomplex.operator+(mycomplex2); I Verwendung wird übersichtlicher Programm wird lesbarer I Überladen sollte nur dafür verwendet werden (+) mit(-) zu überladen nicht ratsam I Operatoren von Standardtypen können nicht überladen werden (z.b. int) I Zusätzliche Operatoren können hier auch nicht definiert werden 6 Überladen von typecasts was kann überladen werden + - * / & ˆ & ~! = < > += -= *= /= %= ˆ= &= = »= «= ==!= <= >= && ++ ->*, -> [] () new new[] delete delete[] I unäre und binäre Operatoren I unterscheide zwischen Postfix und Präfix leicht unterschiedliche Syntax I operator kann natürlich auch überladen werden gemischte Arithmetik möglich z.b. complex + real 1 class complex 2 { /* private data */ public: 5 Complex(int r){vreal = r; vimag = 0;}; 6 operator int(); 7 /* remaining public data */ 8 }; 10 Complex::operator int() 11 { 12 return int(vreal); 1 } 1 1 17 Complex mycomplex(, 5), mycomplex2(2, 7); 18 int x = 1; 1 mycomplex2.print(); 20 mycomplex2 = x; 21 mycomplex2.print(); 22 cout << x << endl; 2 x = mycomplex; 2 cout << x << endl; 25 } I Ausgabe: 2 + 7i 1 + 0i 1 I typecast in nativen Datentyp mittels operator type I andere Richtung mittels Konstruktor 6 65
Anmerkung strings I rückblickend: string ist eine Klasse mit vielen Methoden string überlädt Operatoren I für genauere Informationen API checken I Links: Klassendiagramm in UML I Unified Modeling Language I Modelierungssprache Softwaredesign zum 66