|
|||||||||||
|
2. Abend |
||||||||||
|
Ziele : Ihr seid nach diesem Abend in der Lage dieses Beispiel nachzuvollziehen und zu erklären ! Ihr könnt new und delete verwenden um dynamisch Objekte zu erzeugen und zu löschen Ihr könnt in einer Klasse mehrere Funktionen mit dem gleichen Namen aber unterscheidlichen Parametern definieren (Überladen von Funktionen) |
||||||||||
Das Beispiel |
|||||||||||
Das Beispiel, das wir heute komplett ansehen werden, ist die Musterlösung zur Übung von letzter Woche. Wir werden zusammen eine CD Liste implementieren, wobei nicht die Funktionalität im Vordergrund steht, sondern die Schritte, wie man zu einer ersten Version der CD Liste zu kommen, auf der man die "Datenbank"-Funktionen implementieren kann. Betrachten wir zuerst die Klassen, die wir implementieren werden : |
|||||||||||
CD :Die Klasse CD enthält folgende Angaben zu einer CD : Den Titel,
den Interpreten, die Länge der CD. Zusätzlich ergänzen
wir die Klasse mit einem Zeiger (!) auf die CD, die in der Liste folgt. Wir erzeugen danach eine Datei CD.h, die die Deklaration unserer Klasse enthält. Diese Datei müssen wir jedesmal einbinden, wenn wir auf die Klasse CD bezug nehmen : |
|||||||||||
CD.h |
|
||||||||||
In dieser Klasse fehlt noch einiges, zum Beispiel können wir nichts ausgeben auf diese Weise, denn wir können nur die Eigenschaften der CD setzen aber nicht ausgeben. Nützlich wäre zum Beispiel ein Ausgabe-Funktion. Doch die folgt etwas später. |
|||||||||||
Die Implementation (das .cpp-File) enthält dann die Funktionen, die ziemlich einfach sind und darum hier nicht ganz abgedruckt sind. Als Ausnahme nur der Konstruktor, denn es ist wichtig, dass wir den Zeiger auf 0 initialisieren. Die komplette Datei CD.cpp kannst Du dir einfach downloaden. |
|||||||||||
|
|||||||||||
CDList :Diese Klasse versteckt (kapselt) das Anfügen eines CD-Elementes am Ende der Liste. Sie verwaltet den Zeiger auf das erste Element und das Letzte ser Liste, so dass wir das im Hauptprogramm nicht mehr müssen. Auch wird es dadurch einfacher, mehr als eine Liste zu halten. |
|||||||||||
|
|||||||||||
Mit dieser Header-Datei deklarieren wir also unsere einfache Listenklasse für CD's. Die wichtigsten zwei Funktionen betrachten wir genauer : |
|||||||||||
|
|||||||||||
Der Konstruktor ist zwar ziemlich klar und einfach. Wir werden aber im
Unterricht sehen was geschieht, wenn wir die Zeiger nicht initialisieren
(oder probier es selber).
|
|||||||||||
Test der Liste mit einem einfachen mainWir sind soweit, dass wir nun eine main - Funktion schreiben können mit der wir die CDList-Klasse brauchen können und Elemente einfügen. |
|||||||||||
|
|||||||||||
Das Projekt besteht nun aus folgenden Dateien :
|
|||||||||||
Nach erfolgreicher Kompilation können wir das Programm laufen lassen. Der aufmerksame Leser wird bemerkt haben, dass in unserem Programm einige new - Aufrufe stattfinden aber keine delete ! Natürlich ist das Absicht, damit wir auch die Wirkung von unserem Aufrug _CrtSetDbgFlag sehen können. Hier ein Beispiel wenn wir das Programm 4 CD's erstellen lassen. |
|||||||||||
|
|||||||||||
Speicherlecks bereinigenDie Speicherlecks entstehen dadurch, dass der Speicher den wir mit new allozieren nie mit delete freigegeben wird. Wir könnten kurz vor dem Programmende eine Schleife einbauen, die alle Element in der Liste holt, und den Speicher aufräumt : |
|||||||||||
Hier der Code für das ganze, geänderte main.cpp
|
|||||||||||
Noch schöner im Sinne der Datenkapselung wäre es aber, wenn wir den Code der die Liste aufräumt auch gleich in der Liste haben. Die Liste soll sich doch selber aufräumen. Wo ? Am Ort der für das Aufräumen geschaffen wurde, dem Destruktor von CDList. |
|||||||||||
|
|||||||||||
Erste Erweiterung |
|||||||||||
|
Funktionen überladen"Funktionen überladen" bedeutet eine zweite (oder mehrere)
Funktionen mit dem gleichen Namen wie eine bereits vorhandene zu schreiben.
Diese Funktion unterscheidet sich von den Funktionen, die gleich heissen
durch andere Parameter ! Im Grunde genommen kennen wir das bereits durch
verschiedene Konstruktoren. Wenn wir einen Konstruktor mit Parameter haben
ist das gelichwertig mit einer Funktion überschreiben. |
||||||||||
Diese neue Funktion unterscheidet sich nur durch die Parameterliste und den Rückgabewert von der gleichnamigen Funktion. Die Implementation im .cpp File ist ziemlich einfach, das sie sogar die andere AddNewCD - Funktion aufruft. |
|||||||||||
|
|||||||||||
Also heisst das Überladen von Funktionen nicht viel mehr als mehrere Funktionen mit gleichem Namen aber unterschiedlichen Parametern zu haben. Das Überladen von Funktionen funktioniert auch mit Funktionen, die nicht zu Klassen gehören. |
|||||||||||
Funktionen überladen, 2. Beispiel Um dieses Konzep besser zu verstehen betrachten wir ein zweites Beispiel mit unserer geliebten Auto-Klasse. Nehmen wir an unser Auto könnte mit in eine beliebige Richtung in der Ebene beschleunigen. Das heisst die Beschleunigung hat eine x und eine y - Komponente. |
|||||||||||
Und hier gleich die .cpp - Datei:
|
|||||||||||
Beim Aufruf haben wir nun die Wahl, welche Version der Funktion aufgerufen wird. |
|||||||||||
|
|||||||||||
Operatoren Überladen |
|||||||||||
Nun folgt etwas besonderes, nicht ganz einfaches, aber etwas entscheidendes zur C++ Sprache. Das Überladen von Operatoren ermöglicht es selber definierte Datentypen (Klassen) so zu verwenden wie eingebaute. Nehmen wir an wir haben eine selber definierte Klasse Complex. |
|||||||||||
Natürlich mit noch mehr Elementfunktionen, um die Datenelemente zu setzen und zu lesen, etc. Falls wir komplexe Zahlen (Objekte der Klasse Complex) genauso behandeln könnten wie einen eingebauten Datentypen wie zum Beispiel double oder int, könnten wir folgenden Code schreiben:
Um solchen Code zuzulassen müssen wir dem Compiler nur zeigen wie er die Plus-Operation ausführen muss. Wir überladen den operator + ! Das sieht dann so aus : |
|||||||||||
Und der Code in der .cpp-Datei |
|||||||||||
Wir brauchen hierfür also das Schlüsselwort operator. Was der Code dann in der .cpp-Datei wirklich macht, ist uns selber überlassen. Wir könnten zum Beispiel einen operator+ schreiben, der statdessen ein Subtraktion ausführt, was aber nicht den Erwartungen entsprechen würde. Wir könnte aber andere Sinnvolle Tätigkeiten ausführen, wie zum Beispiel in eine Datei zu schreiben, dass der Operator aufgerufen wurde und mit welchen Parametern. |
|||||||||||
Es gibt noch viele Operatoren, die wir überschreiben können :
Eine komplette Liste findet ihr in euren Unterlagen (Kapitel 8. Operator Overloading). Wichtig sind zum Beispiel der Zuweisungsoperator, der aufgerufen wird, wenn wir einem Objekt unserer Klasse ein anderes Objekt unserer Klasse zuweisen wollen. |
|||||||||||
Da wir in unserer Complex-Klasse keinen Zuweisungsoperator definiert haben, produziert der Compiler einen eigenen, der einfach alle unsere Datenelement vom Objekt c1 in das Objekt c2 kopiert. Das geht bei unserer Klasse Complex problemlos, es gibt aber Situationen in der wir genau bestimmen müssen wie ein Objekt dem anderen zugewiesen wird. Interessant für uns ist auch der <<(Insertion) und der >>(Extraction) Operator. Denn beide habe wir schon häufig verwendet : |
|||||||||||
Das heisst, dass bei diesen Eingabe-und Ausgabefunktionen jeweils der >>-Operator des Objektes cin und der <<-Operator des Objektes cout verwendet werden. Was für Objekte sind cin und cout eigentlich ? Wir kommen im nächsten Kapitel (Klassenableitung und Vererbung) dazu. |
|||||||||||
Klassenableitung und Vererbung |
|||||||||||
Versuchen wir das Prinzip anhand von uns bereits bekannten Klassen zu beschreiben. Zuerst wieder einmal unsere Klasse Auto ;-) Angenommen wir haben eine Klasse Auto mit ein paar Eigenschaften, die für ein Auto allgemein gültig sind. Vielleicht verlangt aber unser Problem eine genauere Unterteilung in zum Beispiel Cabriolets, Sportwagen und Kombis. Dabei haben diese verschiedenen Auto-Typen gemeinsame Attribute wie Farbe und/oder Hubraum etc. Angenommen wir müssen eine Verkaufsdatenbank für Autos machen, dann hätten wir für ein Cabrio noch zusätzlich Angaben für die Windgeräusche. Beim Sportwagen interessiert möglicherweise eine Zulassung für Rennsport-Anlässe und ein Kombi hätte evtl. das maximal mögliche Ladevolumen als zusätzliche Angabe. Um diese Verwandtschaft auszudrücken bietet C++ die Möglichkeit der Vererbung. |
|||||||||||
|
|||||||||||
Nach dem Klassennamen wird einfach nach einem Doppelpunkt angegeben, von welcher Klasse dass abgeleitet werden soll. Das bedeutet dass die ein Objekt der Klasse Cabrio auch ein Auto ist. Alle Datenelemente, die zur Klasse Auto gehören sind auch in der Klasse Cabrio, SportAuto oder Kombi vorhanden. Diese Klassen sind jeweils Spezialisierungen der Basisklasse Auto. Folgender Code ist also gültig: |
|||||||||||
|
|||||||||||
Häufig wird auch folgendes Beispiel verwendet. Eine Personendatenbank basiert auf der Klasse Person. |
|||||||||||
|
|||||||||||
Nun ist es also möglich eine Variable der Klasse Angestellter zu erzeugen und diese so zu verwenden wie ein Objekt der Klasse Person. |
|||||||||||
|
|||||||||||
Zurüch zu cin, coutDas Objekt cin ist von einer speziellen Klasse. Wir brauchen diese Klasse
nicht zu kennen, wir können aber herausfinden, dass diese Klasse
von der Klasse istream abgeleitet ist. Genauso ist ifstream
auch von dieser Klasse abgeleitet ! |
|||||||||||
Wie können wir das anwenden ? Um den Kreis zu schliessen nehmen wir wieder die Klasse CD von zuoberst ! Es fehlt ja noch jegliche Eingabe als auch Ausgabe-Funktionalität ! Wir ergänzen sie mit folgender Funktion : |
|||||||||||
Der cpp Code ist hier : |
|||||||||||
|
|||||||||||
Wir definieren dass die Funktion eine Referenz auf ein Objekt als Parameter nimmt, dass von der Klasse ostream abgleitet ist. Da das cout Objekt ein Objekt von der Klasse ostream ist, können wir cout also verwenden. Genauso können wir aber ein Objekt der Klasse ofstream verwenden, denn die Klasse ofstream ist ebenso von der Klasse ostream abgeleitet und die Klasse ostream hat den operator << überladen, den wir in der Funktion WriteToStream verwenden. |
|||||||||||
|
|||||||||||