9. Klassen Prof. Dr. Markus Gross Informatik I für D-MAVT (FS 2014) Objektorientierte Programmierung, Klassen Members Objekte Konstruktoren und Destruktoren this-pointer Public und Private Sections Überladen von Operatoren
Objektorientierung Objektorientierung (OO) ist ein konzeptioneller Ansatz zum Klassendesign, welcher unabhängig von der Programmiersprache ist Merkmale von OO sind: Abstraktion (abstraction) Verkapselung (encapsulation) Polymorphismus (polymorphism) Vererbung (inheritance) Wiederverwendbarkeit (reusability) OO-Programmierung ist datenzentriert Repräsentation der Daten steht im Vordergrund Methoden zur Datenbearbeitung In C++ dient die Klasse zur Implementierung der OO (class) 2
Objektorientierung Merkmale eines klassischen C-Structs: Verkapselt Mitgliedsvariablen Zugriff mittels.-operator Legt fest, wieviel Speicher benötigt wird Bestimmt, welche Operationen auf den Daten möglich sind Operationen (Methoden) werden in Form von Funktionen definiert Funktionen haben globalen Gültigkeitsbereich Idee: Verstecke Daten komplett vor dem Benutzer Zugriff über bestimmte Zugriffsfunktionen Diese Funktionen stellen das Interface zur Klasse Funktionen bekommen somit lokalen Charakter 3
Implementation einer Klasse Erfolgt durch Klassendeklaration Beschreibt Mitgliedsdaten (data members) Und Mitgliedsfunktionen (member functions/methods) Diese stellen das public interface der Klasse dar Sowie durch Definition der Methoden Konvention: Klassennamen schreiben wir mit führendem Grossbuchstaben Eselsbrücke : Die Klasse ist zunächst ein Struct mit lokalen Funktionen und verbesserten Zugriffsrechten! 4
Klassendeklaration Eine Klasse besitzt also sowohl Mitgliedsdaten, als auch Mitgliedsfunktionen Allgemeine Deklarationssyntax sieht wie folgt aus class ClassName { private: data member variables public: member function prototypes // Interface } Zur Verwendung von Mitgliedsfunktionen muss zuerst ein entsprechendes Objekt angelegt werden Aufruf erfolgt mit dem Membership Operator objectname.foo(); 5
Beispiel: Stock-Klasse Benötigte Methoden Neue Aktie ins Portfolio einfügen Aktien kaufen und verkaufen Aktienwert anpassen Anzeige und Darstellung wichtiger Information Benötigte Daten Firmenname Anzahl der Aktien Aktienwert Gesamtwert des Portfolios 6
Beispiel_1: Stock-Klasse class Stock { Private Mitgliedsvariablen private: char company[30]; int shares; double share_val; double total_val; void set_tot() { total_val = shares * share_val;} public: void acquire(const char * co, int n, double pr); void buy(int num, double price); void sell(int num, double price); void update(double price); void show(); }; 7 Oeffentliche Benötigte Methoden Neue Aktie ins Portfolio einfügen Aktien kaufen und verkaufen Aktienwert anpassen Anzeige und Darstellung wichtiger Information Benötigte Daten Firmenname Anzahl der Aktien Aktienwert Gesamtwert des Portfolios Mitgliedsfunktionen
Objekte Stock ist somit als neuer Typenname für eine Klasse deklariert Erlaubt die Definition von Objekten dieses Klassentyps Stock sally; Stock solly; Mit der Definition werden sowohl Mitgliedsvariablen angelegt, als auch Zugriffsfunktionen festgelegt Die Zugriffskonstrolle erfolgt durch die Schlüsselwörter private und public Mitglieder der private section Nur für Klassenmitglieder sichtbar Kein Zugriff von aussen möglich Sind verkapselt (data hiding) 8
Objekte Mitglieder der public section Stellen das Klasseninterface in Form von Zugriffsfunktionen dar Diese können auf private Daten der Klasse zugreifen Verstecken die Details der Implementation Sind von aussen zugreifbar Klasse kann damit als Black Box verwendet werden, wobei nur das Interface bekannt gegeben wird Klassenbibliotheken Wiederverwendbarkeit von Code Goldene Regel: Jegliche Daten sollten möglichst in die private Section der Klasse private ist default Zugriffstyp 9
Mitgliedsfunktionen Definition erfolgt in Analogie zur Definition regulärer Funktionen Verwendung des Scope Operators (::) Haben Zugriff auf die private Section der Klasse Somit kann gleicher Name für verschiedene Klassen verwendet werden Beispiel: void Stock::update(double price) update() wird hier als Mitglied der Klasse Stock definiert update() hat also class scope Stock::update() ist der qualifizierte Name (qualified name) der Methode 10
Beispiel_2: Implementation void Stock::acquire(const char * co, int n, double pr) { strncpy(company, co, 29); // truncate co to fit if needed company[29] = '\0'; shares = n; share_val = pr; set_tot(); } void Stock::buy(int num, double price) { shares += num; share_val = price; set_tot(); } Verwendung privater Mitglieder der Klasse void Stock::sell(int num, double price) { if (num > shares) { cerr << "You can't sell more than you have!\n"; exit(1); } shares -= num; share_val = price; set_tot(); } void Stock::update(double price) { share_val = price; set_tot(); } 11
Beispiel_3: Verwendung int main() { Stock stock1; Definition eines Objektes stock1.acquire("nanosmart", 20, 12.50); cout.precision(2); cout.setf(ios_base::fixed); cout.setf(ios_base::showpoint); // #.## format // #.## format // #.## format } stock1.show(); stock1.buy(15, 18.25); stock1.show(); return 0; Methodenaufrufe 12
Scope und Zugriff Deklaration der Klassen wird in einem getrennten File vorgenommen Headerfile stock.h Definition der Methoden erfolgt im Quellfile stock.cpp Zugriff auf Methoden eines Objektes erfolgt durch bekannten membership Operator Stock kate; kate.show(); // Aufruf einer Mitgliedsfunktion Mitgliedsvariablen müssen für jedes Objekt getrennt verwaltet werden Mitgliedsfunktionen werden vom Compiler nur einmal angelegt! 13
Konstruktoren Daten der private Section der Klasse sind von aussen nicht sichtbar Daher können sie nicht direkt initialisiert werden Vergleiche Struct und dessen Initialisierung Man benötigt eine spezielle Funktion zur Initialisierung Sowohl als Funktionsargument, als auch als Rückgabetyp Dies kann durch einen Konstruktor erreicht werden Wird bei Objektdefinition automatisch aufgerufen Der Name eines Konstruktors entspricht dem Klassennamen Man benötigt Prototyp und Funktionsdefinition Argumente dürfen keine Namen von Mitgliedsvarablen tragen 14
Konstruktoren Beispiel: Stock(const char *co, int n = 0, double pr = 0.0); Stock::Stock(const char *co, int n, double pr) {...} Konstruktoren können explizit bei der Objektdefinition aufgerufen werden Stock food = Stock("World", 250, 1.0); Konstruktoren können implizit aufgerufen werden Stock food("world", 250, 1.0); In Verbindung mit Pointern Stock *pstock = new Stock("World", 250, 1.0); Default Konstruktor wird von C++ angelegt, wenn kein Konstruktor implementiert ist Eigener Default Konstruktor möglich Stock(); 15
Destruktoren Wenn Gültigkeitsbereich des definierten Objektes ausläuft, wird ein Destruktor aufgerufen Destruktoren sorgen für ordnungsgemässe Freigabe von verwendetem Speicher (Kontext new) Destruktoren haben keine Argumente Tragen Klassennamen mit vorgestellter Tilde ~Stock(); // Destruktor Compiler generiert ebenfalls einen Default Konstruktor Destruktoren werden automatisch aufgerufen Stock::~Stock(){... // implementation } Beim fortschrittlichen Klassendesign muss genaustens auf Konstruktoren und Destruktoren geachtet werden 16
Header Files Klassendeklarationen werden in eigene Headerfiles geschrieben Präprozessor-Direktiven verhindern die Mehrfacheinbindung eines Headerfiles #ifndef _STOCK1_H #define _STOCK1_H // place include file contents here #endif Beim ersten Durchlauf wird _STOCK1_H generiert Bei weiteren Aufrufen wird Inhalt ignoriert und somit Mehrfachdeklaration vermieden Beispiel unserer Stock-Klasse 17
Beispiel_4: Headerfile // stock1.h #ifndef _STOCK1_H_ #define _STOCK1_H_ class Stock { Direktive private: char company[30]; int shares; double share_val; double total_val; void set_tot() { total_val = shares * share_val; } public: Stock(); // default constructor Stock(const char * co, int n = 0, double pr = 0.0); ~Stock(); // noisey destructor void buy(int num, double price); void sell(int num, double price); void update(double price); void show(); }; #endif Destruktor Konstruktoren 18
Beispiel_5: Verwendung von Stock // usestok1.cpp -- use the Stock class #include <iostream> using namespace std; #include "stock1.h" int main() { // using constructors to create new objects Stock stock1("nanosmart", 12, 20.0); // syntax 1 Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // syntax 2 cout.precision(2); cout.setf(ios::fixed, ios::floatfield); cout.setf(ios::showpoint); stock1.show(); stock2.show(); stock2 = stock1; // #.## format // #.## format // #.## format // object assignment // using a constructor to reset an object stock1 = Stock("Nifty Foods", 10, 50.0); // temp object } cout << "After stock reshuffle:\n"; stock1.show(); stock2.show(); return 0; 19
Bemerkungen Objekte können einander zugewiesen werden stock1 = stock2; Destruktoren werden am Ende von main aufgerufen Last in First out Mit Konstruktoren können Objekte reinitialisiert werden Dabei legt der Compiler ein unsichtbares, temporäres Objekt zu Kopierzwecken an Dieses wird vom Destruktor wieder gelöscht Implementationsabhängig Konstante Objekte benötigen Funktionen, welche das entsprechende Objekt nicht verändern const Stock land = Stock(...); Neue Art von konstanter Funktion (nachgestellt) void stock::show() const 20
Der this-pointer Manche Mitgliedsfunktionen müssen das Objekt, welches sie aufruft, erkennen Dies erfolgt über den sogenannten this-pointer Beispiel: Funktion, welche zwei Stock-Objekte vergleicht und das grössere Objekt zurückgibt Eine solche Funktion benötigt ein Objekt als Argument Objekt wird von der Funktion nicht verändert Wir verwenden einen Call by Reference Funktion gibt eine Referenz auf das grössere Objekt zurück const Stock & topval(const Stock & s) const; // Prototyp, Funktion verändert Objekt nicht! top = stock1.topval(stock2); //oder auch top = stock2.topval(stock1); 21
Der this-pointer Implementation const Stock & Stock::topval(const Stock & s) const } { if (s.total_val > total_val) else return s; return *this; // Pointer auf Objekt Der this-pointer ist also ein Pointer auf das aufrufende Objekt total_val entspricht also this->total_val this kann in diesem Fall nicht geändert werden 22
Arrays von Objekten Arrays von Objekten können beliebig angelegt werden Beispiel: Stock mystuff[4]; // Definition mystuff[2].show(); // Aufruf Initialisierung erfolgt mittels Konstruktoren In diesem Fall muss ein Default-Konstruktor vorhanden sein Member Variablen haben class scope 23
Speicherklassen Gewöhnliche Variablen innerhalb von Funktionen haben Speicherklasse automatic Ihr Gültigkeitsbereich ist auf den aktuellen Block beschränkt (block scope) Werden bei Eintritt in den Block angelegt Erlöschen bei Austritt aus dem Block Werden auf dem Stack verwaltet Soll eine Variable während des gesamten Programmes Gültigkeit besitzen, so gibt es zwei Möglichkeiten Definition ausserhalb von Funktionen (globale Variable) Verwendung der Speicherklasse static (statische Variable) 24
Speicherklassen Statische Variablen werden durch das zusätzliche Keyword static definiert static int s = 5; Werden automatisch initialisiert und behalten ihren Wert bei Beenden der Funktion (Ausnahme main) Können sinnvoll zur Speicherung funktionsunabhängiger Werte verwendet werden 25
Ueberladen von Operatoren Das Ueberladen von Operatoren ist eine Variante des Polymorphismus Es erlaubt, die gleichen Operatoren für verschiedene Aufgaben zu verwenden In C++ sind bereits diverse Operatoren überladen * steht sowohl für einen Pointer als auch für die Multiplikation & steht sowohl für die Adresse als auch für die Referenz Das Ueberladen bedarf einer Neudefinition des entsprechenden Operators Dies erfolgt durch Implementation einer entsprechenden Operator-Funktion Nur für gültige C++Operatoren möglich! 32
Ueberladen von Operatoren Funktionsheader in allgemeiner Form: operatorop(argument_list) Angenommen, ein + Operator ist überladen Bei Anwendung des Operators auf zwei Objekte district = sid + sara; Ruft der Compiler die entsprechende Operator- Funktion auf district = sid.operator+(sara); Damit entspricht der Operator einem Funktionsaufruf Das sid Objekt wird implizit verwendet (this- Pointer), das sara Objekt explizit als Argument Beispiel: Time-Klasse 33
Beispiel_2: Time-Klasse // mytime1.h -- Time class after operator overloading #ifndef MYTIME1_H_ #define MYTIME1_H _ #include <iostream> using namespace std; class Time { private: int hours; int minutes; public: Time(); Time(int h, int m = 0); void AddMin(int m); void AddHr(int h); void Reset(int h = 0, int m = 0); Time operator+(const Time & t) const; void Show() const; }; #endif 34
Beispiel_2: Time-Methoden // mytime1.cpp - Auszug void Time::Reset(int h, int m) { hours = h; minutes = m; } Ueberladen des Operators Time Time::operator+(const Time & t) const { Time sum; sum.minutes = minutes + t.minutes; sum.hours = hours + t.hours + sum.minutes / 60; sum.minutes %= 60; return sum; } void Time::Show() const { cout << hours << " hours, " << minutes << " } minutes"; cout << '\n'; 35
Beispiel_2: Time-main // usetime1.cpp -- use second draft of Time class // compile usetime1.cpp and mytime1.cpp together #include <iostream> #include "mytime1.h" using namespace std; int main() { Time A; Time B(5, 40); Time C(2, 55); cout << "A = "; A.Show(); cout << "B = "; B.Show(); cout << "C = "; C.Show(); Aufrufe der Operator-Funktion } A = B.operator+(C); // function notation cout << "A = B.operator+(C) = "; A.Show(); B = A + C; // operator notation cout << "A + C = "; B.Show(); return 0; 36
Ueberladen von Operatoren Operatoren können also auf zwei Arten aufgerufen werden A = B.operator+(C); Sowie A = B + C; Das linke Objekt ist das aufrufende Objekt Einschränkungen: Zumindest ein Operand muss ein benutzerdefinierter Typ sein Die Syntax des Originaloperators muss eingehalten werden Die Precedence der Operatoren muss eingehalten werden Neue Operator-Symbole sind NICHT möglich 37
Friend-Funktionen Auf die private Section einer Klasse kann nur mittels Methoden dieser Klasse zugegriffen werden Encapsulation! Manchmal ist dies zu restriktiv Beispiel: Ueberladene Multiplikation A = B*2.75; //entspricht A = B.operator*(2.75); Jedoch A = 2.75*B; //? Asymmetrischer Aufruf Der Aufruf ist nichtkommutativ Zur Zahl existiert keine entsprechende Operator-Funktion Man benötigt eine Funktion, welche nicht Mitglied der Klasse ist und trotzdem auf die private Section zugreifen kann 38
Friend-Funktionen Funktionsaufruf wäre also A = operator*(2.75,b); Entsprechender Prototyp dieser Funktion Time operator*(double m, const Time & t); Linker Operand entspricht erstem Argument Problem: Nicht-Mitgliedsfunktion hat keinen Zugriff auf private Daten der Klasse Einführung der friend-funktion, welche Zugriff auf die private Section der Klasse hat VORSICHT: Hier umgehen wir das Konzept der Verkapselung Kritik an C++ 39
Friend-Funktionen Prototyp der Friend-Funktion wird innerhalb der Klasse deklariert friend Time operator*(double m, const Time & t); Implementation erfolgt konventionell ohne Scope- Operator :: Time operator*(double m, const Time & t) { Time result; Result.hours = totalminutes / 60; totalminutes = t.minutes;... } 40