Du bist hier: Startseite > Entwicklung (C/C++) > C++ Grundlagen > Klassen und Vererbungen

Klassen und Vererbungen

Klassen in C++ Mit überladenen Konstruktoren

Alles, was Du in den vorherigen Tutorials kennen gelernt hast, war natürlich bereits Bestandteil der Programmiersprache C++. Dennoch sieht die Entwicklung eines C++-Programms meist anders aus als das, was Du bisher an Beispielen gesehen hast.

Bis jetzt hast Du große Teile des Grundwortschatzes von C++ kennen gelernt und sie eher rudimentär angewandt. In der Praxis bestehen C++-Programme normalerweise vorrangig aus Klassen. Entwickler konzentrieren sich auf das Erstellen von Klassen, die an die speziellen Bedürfnisse, die die Aufgabenstellung erfordert, angepasst werden. Sind die Klassen erstellt, werden sie als Datentypen für die Definition von Variablen verwendet. Diese Variablen heißen Objekte, und mit diesen wird innerhalb eines Programms dann gearbeitet.

Nachdem wir wie immer ein neues leeres Projekt mit einer main.cpp angelegt haben, erstellen wir noch eine Headerdatei Namens klasse.

Datei:  Headerdateien/klasse.h
  1. #ifndef KLASSE_H
  2. #define KLASSE_H
  3. class rennwagen
  4. {
  5. public:
  6. rennwagen(); // constructor
  7. rennwagen(int kmh); // Überladener Konstruktor
  8. ~rennwagen(); // destructor
  9. void beschleunigen(int kmh);
  10. void bremsen(void);
  11. int geschwindigkeit(void);
  12. private:
  13.  
  14. protected:
  15. int kmh;
  16. };
  17. #endif

In der Headerdatei wird die Klasse nur deklariert. Somit weiss der Compiler, das es später noch eine Klasse mit dem Namen rennwagen geben wird. Das Schlüsselwort public bedeutet, dass das was darunter folgt von aussen zugänglich ist. Private bedeutet dagegen, dass der darunter folgende Code nur in der Klasse selbst aufgerufen werden kann. Es gibt noch protected, es spielt aber nur bei der Vererbung eine Rolle. Für eine Klasse selbst ist protected gleichbedeuted mit private. Im Beispiel wird die Integer Variable kmh definiert. Sie soll durch eine getter Methode (int geschwindigkeit(void);) von aussen ausgelesen werden. Die Methoden rennwagen(); und rennwagen(int kmh); sind die 2 Konstruktoren der Klasse. Hier wurde der Konstruktor wie bei Funktionen überladen. Je nach dem mit welchen Parametern die Klasse instanziert wird, entscheidet der Compiler welcher von beiden aufgerufen werden soll. Der Konstruktor rennwagen(int kmh); startet nicht nur die Karre, sondern drückt direkt das Pedal runter und beschleunigt auf 200Km/h. Der Destruktor ~rennwagen(); wird aufgerufen, wenn die Instanz mit delete zerstört wird, oder das Programm beendet wird. Bei den Methoden void beschleunigen(int kmh);, void bremsen(void); und int geschwindigkeit(void); gelten die gleichen Regeln wie bei normalen Funktionen. Als nächstes erstellen wir die klasse.cpp Datei unter Quelldateien. Hier kommt dann der Code für die Klasse selber rein.

Datei:  Quelldateien/klasse.cpp
  1. #include <iostream> // std::cout, std::endl
  2. #include "klasse.h"
  3.  
  4. rennwagen::rennwagen()
  5. {
  6. // constructor
  7. this->kmh = 0;
  8. std::cout << "Wagen wurde gestartet" << std::endl;
  9. }
  10.  
  11. rennwagen::rennwagen(int kmh)
  12. {
  13. // constructor
  14. this->kmh = kmh;
  15. std::cout << "Wagen wurde gestartet und ist mit " << this->kmh << "Km/h los geheizt!" << std::endl;
  16. }
  17.  
  18. rennwagen::~rennwagen()
  19. {
  20. // destructor
  21. this->kmh = 0;
  22. }
  23.  
  24. void rennwagen::beschleunigen(int kmh)
  25. {
  26. this->kmh += kmh;
  27. std::cout << "Beschleunigen auf: " << this->kmh << " Km/h" << std::endl;
  28. }
  29.  
  30. void rennwagen::bremsen(void)
  31. {
  32. this->kmh -= 25;
  33. if (this->kmh < 0)
  34. this->kmh = 0;
  35. std::cout << "Bremsen auf: " << this->kmh << " Km/h" << std::endl;
  36. }
  37.  
  38. int rennwagen::geschwindigkeit(void)
  39. {
  40. return this->kmh;
  41. }

Zuerst wird der Konstruktor rennwagen::rennwagen(){} definiert. Da er immer ein Objekt (nämlich sich selbst) zurück gibt, wird kein Datentyp voran gestellt. Im Konstruktor wird die interne Variable kmh mit dem Schlüsselwort this angesprochen und auf 0 gesetzt. This ist immer der Pointer (Zeiger) auf die eigene Klasse und man kann innerhalb der Klasse auf Variablen und Methoden zugegriffen. Ausserdem wird auf der Konsole noch ein Text ausgegeben.

Danach wird der destruktor der Klasse definiert. Man erkennt ihn daran, dass er genau denn gleichen Namen hat wie der Konstruktor mit dem Unterschied das eine Tilde ~ voran steht. Er muss nur vorhanden sein, wenn er auch in der Header-deklaration steht. Dem Destruktur wird ebenfalls kein Datentyp voran gestellt. In diesem Fall wird die Variable kmh auf 0 gesetzt.

Die restlichen Methoden dürften unter Einbehaltung der Regeln für Funktionen selbst erklärend sein.

Als nächstes müssen wir die main.cpp anpassen.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3. #include "klasse.h"
  4.  
  5. int main(void)
  6. {
  7. rennwagen Bmw; // ruft denn rennwagen() Konstruktor auf
  8. // rennwagen Bmw(199); // ruft denn rennwagen(int kmh) Konstruktor auf
  9. std::cout << "Der Wagen faehrt " << Bmw.geschwindigkeit() << " Km/h" << std::endl;
  10. Bmw.beschleunigen(100);
  11. Bmw.beschleunigen(80);
  12. std::cout << "Der Wagen faehrt " << Bmw.geschwindigkeit() << " Km/h" << std::endl;
  13. Bmw.bremsen();
  14. Bmw.bremsen();
  15. Bmw.bremsen();
  16. Bmw.bremsen();
  17. Bmw.bremsen();
  18. Bmw.bremsen();
  19. Bmw.bremsen();
  20. Bmw.bremsen();
  21. std::cout << "Der Wagen faehrt " << Bmw.geschwindigkeit() << " Km/h" << std::endl;
  22.  
  23. std::cin.get();
  24. return EXIT_SUCCESS;
  25. }

Wichtig ist hier, dass die Headerdatei klasse.h eingebunden wird. Falls diese vergessen wird, kann der Compiler die Klasse die in der Datei deklariert ist, nicht finden. Beim Instanzieren der Klasse haben wir jetzt 2 Konstruktoren zur Verfügung. Im Beispiel wurde der Konstruktor rennwagen() aufgerufen. Dieser startet die Karre nur. Der andere Konstruktor scheint dagegen "auf der Flucht" zu sein, da er denn Motor anlässt, und direkt auf 199 Km/h beschleunigt. Nach die Klasse instanziert wurde, haben wir in der Variablen Bmw ein Objekt der Klasse rennwagen. Die Klassenvariablen (Eigenschaften gennant) und die Methoden sind jetzt über Bmw gefolgt von einem Punkt erreichbar (insofern die Variablen oder Methoden als public definiert sind). Bmw.kmh ist ausserhalb der Klasse nicht erlaubt, da sie ja als private deklariert wurde.

Natürlich kann auch ein Pointer verwendet werden.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3. #include "klasse.h"
  4.  
  5. int main(void)
  6. {
  7. //rennwagen *pBmw = (new rennwagen); // ruft denn rennwagen() Konstruktor auf
  8. rennwagen *pBmw = ( new rennwagen(130) ); // ruft denn rennwagen(int kmh) Konstruktor auf
  9. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  10. pBmw->beschleunigen(100);
  11. pBmw->beschleunigen(80);
  12. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  13. pBmw->bremsen();
  14. pBmw->bremsen();
  15. pBmw->bremsen();
  16. pBmw->bremsen();
  17. pBmw->bremsen();
  18. pBmw->bremsen();
  19. pBmw->bremsen();
  20. pBmw->bremsen();
  21. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  22.  
  23. std::cin.get();
  24. return EXIT_SUCCESS;
  25. }

Klassen Vererbung

Klassenmethoden sowie Variablen (Eigenschaften) können auf andere Klassen übertragen (vererbt) werden. Im obigen Beispiel wurde die Klasse rennwagen angelegt. Diese beinhaltet Methoden wie beschleunigen(), bremsen() und geschwindigkeit(). Diese Methoden passen im Prinzip auf jedes Auto und daher kann die Klasse als art Basisklasse genutzt werden.
Im folgenden Beispiel werden wir eine neue Klasse caprio erstellen, die die Methoden von rennwagen erbt und selbst noch eine kleine Sonderausstattung hat.

Wie das funktioniert, zeigt folgendes Beispiel. Zuerst aber muss wieder eine Headerdatei Namens vererbung.h sowie eine neue Quelldatei (vererbung.cpp) angelegt werden.

Datei:  Headerdateien/vererbung.h
  1. #ifndef VERERBUNG_H
  2. #define VERERBUNG_H
  3. #include "klasse.h"
  4. class caprio : public rennwagen
  5. {
  6. public:
  7. // constructor caprio() -> parent()
  8. caprio()
  9. {
  10. this->verdeck();
  11. }
  12.  
  13. // constructor caprio(int kmh) -> parent(int kmh)
  14. caprio(int kmh) : rennwagen(kmh)
  15. {
  16. this->verdeck();
  17. }
  18.  
  19. void verdeck(void);
  20. };
  21. #endif

Mit class caprio wird die Klasse caprio deklariert. Das : public rennwagen besagt, dass die Klasse caprio alle Methoden sowie Variablen (Eigenschaften) von der Klasse rennwagen erbt. Vorraussetzung ist natürlich, das die Methoden sowie Variablen (Eigenschaften) die in rennwagen stehen und in caprio genutzt werden sollen public oder protected sind. In der Klasse caprio selbst deklarieren wir die 2 Konstruktoren sowie die Methode verdeck() in einer cpp Datei.
Interessant ist der Konstruktor caprio(int kmh). Wir haben vorhin in der Klasse rennwagen auch denn normalen Konstruktor überladen mit rennwagen(int kmh). Es wäre also von Vorteil, wenn der überladene caprio(int kmh) Konstruktor aufgerufen wird, dass dann nich der Standart parent Konstruktor sondern parent(int kmh) geladen wird. Damit sowas möglich ist, muss der entsprechende Konstruktor so in der Headerdatei definiert sein: caprio(int kmh) : rennwagen(kmh) { ... }
Hinter dem Doppelpunkt und vor dem Funktionsrumpf wird einfach der aufzurufende Konstruktor der Parent Klasse samt denn erforderlichen Parametern notiert.

Datei:  Quelldateien/vererbung.cpp
  1. #include <iostream> // std::cout, std::endl
  2. #include "vererbung.h"
  3.  
  4. void caprio::verdeck(void)
  5. {
  6. std::cout << "Das Verdeck wurde geoeffnet" << std::endl;
  7. }

In vererbung.cpp wird lediglich die Methode verdeck() definiert, da wir sonst nur Konstruktoren haben und die schon definiert sind.

Da wir jetzt eine neue Klasse haben, müssen wir natürlich den Code von main.cpp ebenfalls ändern.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3. #include "vererbung.h"
  4.  
  5. int main(void)
  6. {
  7. //caprio *pBmw = (new caprio); // ruft normalen Konstruktor caprio()
  8. caprio *pBmw = ( new caprio(120) ); // ruft überladenen Konstruktor caprio(int kmh)
  9. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  10. pBmw->beschleunigen(100);
  11. pBmw->beschleunigen(80);
  12. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  13. pBmw->bremsen();
  14. pBmw->bremsen();
  15. pBmw->bremsen();
  16. pBmw->bremsen();
  17. pBmw->bremsen();
  18. pBmw->bremsen();
  19. pBmw->bremsen();
  20. pBmw->bremsen();
  21. std::cout << "Der Wagen faehrt " << pBmw->geschwindigkeit() << " Km/h" << std::endl;
  22.  
  23. std::cin.get();
  24. return EXIT_SUCCESS;
  25. }

Beim instanzieren der neuen Klasse wird immer zuerst der Parent Konstruktor von rennwagen ausgeführt, da die Klasse caprio davon abgeleitet ist. Danach wird der Konstruktor von caprio aufgerufen, der widerrum die Methode verdeck() aufruft.

Verketteter Methodenaufruf

Das Schlüsselwort this ist der eigene Pointer der Klasse. Dieser Pointer kann ebenfalls als Rückgabetyp definiert werden. Somit können die Methoden direkt verkettet aufgerufen werden. Hier mal ein Stand-Alone Beispiel was nichts mit dem Rest der Seite zu tun hat:

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3.  
  4. class Klasse
  5. {
  6. public:
  7. Klasse(void) { std::cout << "\nKlasse geladen" << std::endl; }
  8. Klasse& rAufruf();
  9. Klasse* pAufruf();
  10. };
  11. Klasse& Klasse::rAufruf()
  12. {
  13. std::cout << "Aufruf ueber Referenz | ";
  14. return *this;
  15. }
  16. Klasse* Klasse::pAufruf()
  17. {
  18. std::cout << "Aufruf ueber Pointer | ";
  19. return this;
  20. }
  21.  
  22. int main(void)
  23. {
  24. Klasse cls;
  25. cls.rAufruf().rAufruf().rAufruf();
  26.  
  27. Klasse *pcls = (new Klasse);
  28. pcls->pAufruf()->pAufruf()->pAufruf();
  29.  
  30. std::cin.get();
  31. return EXIT_SUCCESS;
  32. }

Mit der definition Klasse& rAufruf(); wird das eigene Objekt zurück gegeben. Wenn eine Methode das eigene Objekt zurück gibt, kann direkt per Komponentenauswahl-Operator (in dem Fall der Punkt .) auf eine andere Methode zugegriffen werden. Die Methoden müssen allerdings als public definiert sein.

Mit der definition Klasse* pAufruf(); wird ebenfalls das eigene Objekt zurück gegeben, aber als Pointer. Es kann ebenfalls direkt per Komponentenauswahl-Operator (in dem Fall der Strich-Pfeil ->) auf eine andere Methode zugegriffen werden. Die Methoden müssen auch wieder public sein. Ich persönlich finde die -> Variante schöner, da ich vorher 10 Jahre PHP entwickelt habe... :)

Kommentare zu diesem Beitrag

Sie müssen angemeldet sein, um eine Nachricht zu erstellen. Anmelden »