kennen:klassen
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
— | kennen:klassen [2020-07-27 09:51] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ====== Klassen ====== | ||
+ | > Wir versuchen Dinge zu verstehen, um mit ihnen interagieren zu können. | ||
+ | >> --- Rebecca Wirfs-Brock | ||
+ | ===== Klassen vereinbaren ===== | ||
+ | Klassen sind nutzerdefinierte, | ||
+ | Objekte eine Klasse weisen bestimmte gemeinsame Merkmale | ||
+ | (Datenkomponenten, | ||
+ | und verhalten sich gleichartig. | ||
+ | Sie verfügen über eigene [[# | ||
+ | zur Bearbeitung ihrer eigenen Daten. | ||
+ | |||
+ | ==== Deklaration ==== | ||
+ | Eine Klasse muss zumindest deklariert werden, | ||
+ | d.h. ein Bezeichner muss als Klassenname bekannt gemacht werden, | ||
+ | bevor Referenzen und Zeiger auf Objekte dieser Klassen gebildet werden können. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > '' | ||
+ | <code cpp> | ||
+ | class Bruch; | ||
+ | void ausgabe(Bruch const& x); | ||
+ | </ | ||
+ | |||
+ | ==== Definition ==== | ||
+ | Die Klassendefinition (öffentliche Schnittstelle) | ||
+ | ist eine Typvereinbarung, | ||
+ | die Daten-[[# | ||
+ | und [[# | ||
+ | dieses Typs zusammenfasst. | ||
+ | Bis auf die [[# | ||
+ | sind [[.: | ||
+ | [[.: | ||
+ | Einer abgeschlossenen Typdefinition kann nichts hinzugefügt werden, | ||
+ | jedoch können durch [[# | ||
+ | Typen abgeleitet werden. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > '' | ||
+ | > '' | ||
+ | >> | ||
+ | >> | ||
+ | >> | ||
+ | > '' | ||
+ | <code cpp> | ||
+ | class Bruch | ||
+ | { | ||
+ | public: | ||
+ | Bruch(int zaehler, int nenner); | ||
+ | Bruch(); | ||
+ | |||
+ | Bruch& add(Bruch const& b); // schreibende Methoden | ||
+ | int zaehler() const; | ||
+ | int nenner() const; | ||
+ | private: | ||
+ | Bruch& kuerzen(); | ||
+ | int z, n; // Attribute | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | ===== Attribute ===== | ||
+ | Die Festlegung, welche Daten zu einem Typ gehören, erfolgt | ||
+ | bei der [[# | ||
+ | der Klasse. Die [[# | ||
+ | enthalten jeweils unabhängig voneinander einen Satz von Datenkomponenten (Attributen). | ||
+ | Ihre Startwerte können durch [[# | ||
+ | festgelegt werden. | ||
+ | C++11 erlaubt auch die Zuweisung von Anfangswerten bei der Definition: | ||
+ | <code cpp> | ||
+ | class Bruch | ||
+ | { | ||
+ | // ... | ||
+ | int z = 0, n = 1; | ||
+ | }; | ||
+ | </ | ||
+ | ===== Methoden ===== | ||
+ | Methoden einer Klasse werden in der [[# | ||
+ | der Klasse deklariert. | ||
+ | |||
+ | ==== definieren ==== | ||
+ | Der Methodenrumpf kann getrennt von Klassendefinition festgelegt werden. | ||
+ | Die Methode hat Zugriff auf alle Komponenten ihrer Klasse. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > Rückgabetyp Klassenname ''::'' | ||
+ | > '' | ||
+ | >> | ||
+ | > '' | ||
+ | Innerhalb der Klassendefinition aufgeführte Methodenrümpfe gelten als | ||
+ | [[.: | ||
+ | |||
+ | ==== const-(Lese)-Methoden ==== | ||
+ | Folgt der Parameterliste [[.: | ||
+ | und nur lesende Methoden aufrufen. | ||
+ | Andererseits dürfen bei konstanten Objekten nur lesende Methoden aktiviert werden. | ||
+ | |||
+ | <code cpp> | ||
+ | int Bruch:: | ||
+ | { | ||
+ | return z; | ||
+ | } | ||
+ | </ | ||
+ | ===== Besondere Methoden ===== | ||
+ | Jede Klasse enthält, wenn nichts anderes festgelegt wurde, | ||
+ | einige Methoden, ohne die keine Klasse auskommt: | ||
+ | Konstruktoren, | ||
+ | Diese [[spezielle_Methoden|speziellen Methoden]] werden vom Compiler generiert, | ||
+ | solange der Programmierer nichts anderes festlegt. | ||
+ | |||
+ | |||
+ | |||
+ | ==== Konstruktoren ==== | ||
+ | Konstruktoren (Erzeuger-Methoden) erledigen Routineaufgaben beim Erschaffen von Objekten. | ||
+ | Eine Klasse kann mehrere Konstruktoren mit unterschiedlichen Parametern besitzen. | ||
+ | Für Konstruktoren wird kein Rückgabetyp deklariert. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > Klassenname '' | ||
+ | |||
+ | Vor der Ausführung des Konstruktorrumpfes können (und sollten) die Attribute Startwerte erhalten | ||
+ | (Initialisiererliste). | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > Klassenname ''::'' | ||
+ | > '':'' | ||
+ | > '' | ||
+ | <code cpp> | ||
+ | Bruch:: | ||
+ | : z(zaehler), n(nenner) | ||
+ | { // n = nenner | ||
+ | kuerzen(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Standardkonstruktor ==== | ||
+ | Besitzt eine Klasse nutzerdefinierte Konstruktoren mit Parametern, | ||
+ | wird der Standardkonstruktor nicht mehr automatisch erzeugt. | ||
+ | Ein Standardkonstruktor ist u.a. notwendig, | ||
+ | um Felder von Objekten dieser Klasse anzulegen. | ||
+ | |||
+ | <code cpp> | ||
+ | Bruch:: | ||
+ | : z(0), n(1) | ||
+ | { | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Konstruktor-Delegation ==== | ||
+ | Ein Konstruktor kann seine Aufgaben an einen anderen delegieren und nach dessen Abarbeiten weitere Anweisungen im Konstruktorrumpf ausführen (C++11): | ||
+ | <code cpp> | ||
+ | Bruch:: | ||
+ | : Bruch(0, 1) | ||
+ | { | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Destruktor ==== | ||
+ | Der Destruktor wird aufgerufen, wenn ein Objekt verschwindet. | ||
+ | Vernichten ist das Gegenteil von Erzeugen, | ||
+ | deshalb wird die Tilde '' | ||
+ | vor dem Namen der zerstörenden Methode geschrieben. | ||
+ | |||
+ | Syntax: | ||
+ | > '' | ||
+ | |||
+ | Eine Klasse besitzt nur einen (parameterlosen) Destruktor. | ||
+ | Der Standard-Destruktor vernichtet die im [[# | ||
+ | Soll beim Vernichten eines Objektes etwas Besonderes geschehen, | ||
+ | so muss der Destruktor umdefiniert werden. | ||
+ | |||
+ | Syntax: | ||
+ | > Klassenname'':: | ||
+ | |||
+ | In der Klasse '' | ||
+ | zu erledigen. Daher müsste er nicht definiert werden. | ||
+ | Sollen von der Klasse jedoch Untertypen [[# | ||
+ | ist er als [[# | ||
+ | zu deklarieren((Vor C++11 wurde '' | ||
+ | <code cpp> | ||
+ | class Bruch | ||
+ | { | ||
+ | // ... | ||
+ | virtual ~Bruch() = default; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Zugriffsrechte ===== | ||
+ | Zugriffsrechte auf Attribute und Methoden werden von der Klasse verliehen. | ||
+ | In der Klassendefinition stehende Abschnitte markieren | ||
+ | nachfolgend aufgeführte Komponenten als | ||
+ | |||
+ | | [[.: | ||
+ | | [[.: | ||
+ | | [[.: | ||
+ | |||
+ | Bei Strukturen ist das Zugriffsrecht '' | ||
+ | bei Klassen '' | ||
+ | |||
+ | Eine Klasse kann andere Klassen oder Funktionen zu Freunden | ||
+ | ([[.: | ||
+ | die dann Zugriff auf private und geschütze Komponenten haben.((Only friends are allowed to handle your private parts. ;-) )) | ||
+ | |||
+ | ===== Objekte ===== | ||
+ | Objekte sind Variablen eines zusammengesetzten Typs (Klasse oder Struktur). | ||
+ | Jedes Objekt hat einen eigenen, | ||
+ | von den anderen Objekten unabhängigen Satz von Attributwerten. | ||
+ | Beim Anlegen der Objekte können Startwerte entsprechend den Konstruktorparametern | ||
+ | angegeben werden. | ||
+ | Fehlen Startwerte, wird der [[# | ||
+ | Die Startwerte sind in runden Klammern (oder ab [[begriffe# | ||
+ | |||
+ | <code cpp> | ||
+ | Bruch a; | ||
+ | Bruch b(1, 2); | ||
+ | Bruch c{3, 4}; | ||
+ | Bruch f(); // Funktion, die einen Bruch liefert | ||
+ | Bruch* zeiger = &b; | ||
+ | </ | ||
+ | Bei der Objektdefinition mit dem Standardkonstruktor werden keine runden Klammern geschrieben. | ||
+ | Leere runde Klammern werden als Funktionsdeklaration interpretiert. | ||
+ | Geschweifte statt runder Klammern haben dieses Problem nicht.((Siehe auch | ||
+ | [[begriffe# | ||
+ | und | ||
+ | [[begriffe# | ||
+ | Besitzt die Klasse einen Konstruktor mit [[Initialisiererliste]], | ||
+ | |||
+ | Zugängliche Attribute und Methoden von existierenden Objekten | ||
+ | werden mittels Punkt- bzw. | ||
+ | Pfeil-[[.: | ||
+ | |||
+ | Syntax: | ||
+ | > Variablenname'' | ||
+ | > Objektzeiger'' | ||
+ | |||
+ | <code cpp> | ||
+ | cout << b.zaehler() | ||
+ | cout << zeiger-> | ||
+ | // b.n = 0; // nicht erlaubt, da n nicht öffentlich | ||
+ | // zeiger-> | ||
+ | </ | ||
+ | |||
+ | ===== Statische Komponenten ===== | ||
+ | Als [[.: | ||
+ | definierte Klassenvariablen bilden | ||
+ | einen gemeinsam nutzbaren Speicherbereich (" | ||
+ | Sie sind im Namensraum der Klasse vorhanden, | ||
+ | unabhängig davon, ob und wieviele Objekte dieser Klasse existieren. | ||
+ | Statische Klassenfunktionen haben nur Zugriff auf statische Klassendaten. | ||
+ | |||
+ | <code cpp> | ||
+ | class Objektzaehler | ||
+ | { | ||
+ | inline static int anzahl = 0; | ||
+ | public: | ||
+ | static int instanzen() { return anzahl; } | ||
+ | |||
+ | Objektzaehler() { ++anzahl; } | ||
+ | | ||
+ | }; | ||
+ | </ | ||
+ | Statische Komponenten können auch ohne Vorhandensein von Objekten | ||
+ | als Klassenname''::'' | ||
+ | Statische Klassenvariablen ohne '' | ||
+ | einmal im Programm global angegeben werden | ||
+ | und sollten dabei initialisiert werden. | ||
+ | |||
+ | <code cpp> | ||
+ | int Objektzaehler:: | ||
+ | </ | ||
+ | |||
+ | ===== Vererbung ===== | ||
+ | ==== Basisklassen und abgeleitete Klassen ==== | ||
+ | Definierte Klassen können als | ||
+ | [[.: | ||
+ | für die Bildung | ||
+ | [[.: | ||
+ | [[# | ||
+ | der Schnittstellen und [[# | ||
+ | von [[# | ||
+ | verfeinern das Typensystem. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > '' | ||
+ | > '' | ||
+ | >> | ||
+ | > '' | ||
+ | |||
+ | <code cpp> | ||
+ | class GezaehlterBruch : public Bruch | ||
+ | { | ||
+ | public: | ||
+ | GezaehlterBruch(int zaehler, int nenner); | ||
+ | GezaehlterBruch(); | ||
+ | private: | ||
+ | Objektzaehler anzahl; | ||
+ | }; | ||
+ | </ | ||
+ | Die Basisklassenliste kann mehrere Klassen enthalten | ||
+ | ([[# | ||
+ | Vor jedem Basisklassennamen sollte die Vererbungsart durch | ||
+ | eines der Wörter | ||
+ | [[.: | ||
+ | [[.: | ||
+ | [[.: | ||
+ | angegeben werden. | ||
+ | Die Vererbungsart kann das Zugriffsrecht | ||
+ | auf Basisbestandteile in der abgeleiteten Klasse einschränken: | ||
+ | |||
+ | | | '' | ||
+ | | sind bei | im Erben | | ||
+ | | '' | ||
+ | | '' | ||
+ | | '' | ||
+ | |||
+ | Konstruktoren übergeben am Beginn der Initialisiererliste Parameter | ||
+ | an Basiskonstruktoren, | ||
+ | |||
+ | <code cpp> | ||
+ | GezaehlterBruch:: | ||
+ | : Bruch(zaehler, | ||
+ | { | ||
+ | } | ||
+ | |||
+ | GezaehlterBruch:: | ||
+ | { // Basis-Standardkonstruktor genutzt | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Erweitern ==== | ||
+ | (Hinzufügen von Attributen und Methoden) | ||
+ | Abgeleitete Klassen besitzen alle Daten-Komponenten und Methoden der Basisklasse | ||
+ | und können auf diese zugreifen, | ||
+ | sofern sie geschützt oder öffentlich zugänglich sind. | ||
+ | Zusätzlich können in der Definition der abgeleiteten Klasse | ||
+ | weitere Attribute und Methoden vereinbart werden. | ||
+ | |||
+ | ==== Spezialisieren ==== | ||
+ | (Überschreiben von Methoden) | ||
+ | Abgeleitete Klassen können Methoden der Basisklasse | ||
+ | durch erneute Definition überschreiben | ||
+ | und damit das Verhalten der Klasse ändern. | ||
+ | Der Prototyp der redefinierten Methode muss | ||
+ | mit dem in der Basisklasse übereinstimmen. | ||
+ | |||
+ | |||
+ | ==== Virtuelle Methoden ==== | ||
+ | Als [[.: | ||
+ | markierte Methoden der Basisklasse sind dafür vorgesehen, | ||
+ | in abgeleiteten Klassen überschrieben zu werden. | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > '' | ||
+ | > '' | ||
+ | >> | ||
+ | > '' | ||
+ | |||
+ | Wird ein abgeleitetes Objekt dann | ||
+ | über einen Zeiger oder eine Referenz auf den Basistyp | ||
+ | aufgefordert, | ||
+ | so wird die tatsächliche (engl. virtual) abgeleitete Methode aufgerufen | ||
+ | und nicht die Basismethode. | ||
+ | Die von der Basis abgeleiteten Objekte verhalten sich | ||
+ | [[.: | ||
+ | |||
+ | <code cpp> | ||
+ | class Basis | ||
+ | { | ||
+ | public: | ||
+ | virtual ~Basis() = default; | ||
+ | virtual char const* ich_bin() const { return " | ||
+ | }; | ||
+ | |||
+ | class Abgeleitet : public Basis | ||
+ | { | ||
+ | public: | ||
+ | char const* ich_bin() const override { return " | ||
+ | }; | ||
+ | |||
+ | void test() | ||
+ | { | ||
+ | Basis* p = new Abgeleitet(); | ||
+ | cout << p-> | ||
+ | delete p; // ruft p-> | ||
+ | } | ||
+ | </ | ||
+ | Da polymorphe Objekte zumeist auch unterschiedliche Ressourcen nutzen, | ||
+ | sollte jede Basisklasse mit virtuellen Methoden | ||
+ | einen virtuellen Destruktor besitzen, | ||
+ | um die per Zeiger übernommene Instanz korrekt und vollständig abbauen zu können, | ||
+ | auch wenn dessen Rumpf vorerst leer bleibt. | ||
+ | Sonst besteht die Gefahr eines Ressourcenlecks. | ||
+ | |||
+ | Überschriebene virtuelle Methoden können in C++11 durch [[.: | ||
+ | Als [[.: | ||
+ | Von als [[.: | ||
+ | |||
+ | ==== Abstrakte Methoden ==== | ||
+ | Abstrakte Methoden sind [[# | ||
+ | für die in der Basisklasse noch keine " | ||
+ | angegeben werden kann. | ||
+ | Da der Methodenrumpf fehlt, wird in der Klassendefinition | ||
+ | ein Nullzeiger '' | ||
+ | |||
+ | Syntax: | ||
+ | |||
+ | > '' | ||
+ | > '' | ||
+ | >> | ||
+ | > '' | ||
+ | |||
+ | <code cpp> | ||
+ | class Process | ||
+ | { | ||
+ | public: | ||
+ | virtual ~Process() {} | ||
+ | virtual void run() = 0; | ||
+ | }; | ||
+ | </ | ||
+ | Klassen mit abstrakten Methoden (abstrakte Klassen) | ||
+ | vereinbaren einheitliche Schnittstellen | ||
+ | und damit Wurzeln in Klassenhierarchien. | ||
+ | Instanzen mit dieser Schnittstelle können nur von abgeleiteten Klassen | ||
+ | gebildet werden, die alle abstrakten Methoden | ||
+ | [[# | ||
+ | |||
+ | ==== Mehrfachvererbung ==== | ||
+ | Eine Klasse kann gleichzeitig von mehreren Basisklassen erben. | ||
+ | Konstruktoren müssen die Basisobjekte in der Reihenfolge initialisieren, | ||
+ | die durch die Basisklassenliste vorgegeben ist. | ||
+ | |||
+ | <code cpp> | ||
+ | class GezaehlterBruch : public Bruch, private ObjektZaehler | ||
+ | { | ||
+ | public: | ||
+ | GezaehlterBruch(int zaehler, int nenner); | ||
+ | GezaehlterBruch(); | ||
+ | }; | ||
+ | </ | ||
+ | Mehrfachvererbung bereitet Probleme, | ||
+ | * weil die relativen Adressen (Offsets) der Datenfelder und virtuellen Methodentabellen von der Reihenfolge in der Basisklassenliste abhängen, | ||
+ | * wenn Basisklassen gleichnamige Elemente haben. Dann müssen diese mit ihrem Basisklassennamen qualifiziert werden. Gleichnamige Methoden können überschrieben werden, um den Konflikt aufzulösen. | ||
+ | * wenn Basisklassen von einem gemeinsamen Ahnen abstammen (Vererbungsgraph in Rautenform). In den Objekten der abgeleiteten Klasse sind dann mehrere Ahnen-Instanzen vorhanden, die gesondert angesprochen werden müssen (doppelte Buchführung). | ||
+ | |||
+ | <code cpp> | ||
+ | void konflikt() | ||
+ | { | ||
+ | struct A { int x; }; | ||
+ | struct B1 : A {}; | ||
+ | struct B2 : A {}; | ||
+ | struct C : B1, B2 {} c; | ||
+ | |||
+ | c.B1::x = 1; | ||
+ | c.B2::x = 2; | ||
+ | } | ||
+ | </ | ||
+ | Virtuelle Basisklassen erzeugen nur eine Instanz, | ||
+ | auch wenn sie über mehrere Basisklassen vererbt werden. | ||
+ | Falls es keinen Standardkonstruktor für den Ahnen '' | ||
+ | muss bei '' | ||
+ | und vor den Konstruktoren von '' | ||
+ | {{ : | ||
+ | |||
+ | <code cpp> | ||
+ | void virtuelle_Basis() | ||
+ | { | ||
+ | struct A { int x; }; | ||
+ | struct B1 : virtual A {}; | ||
+ | struct B2 : virtual A {}; | ||
+ | struct C : B1, B2 {} c; | ||
+ | |||
+ | c.x = 1; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In anderen Programmiersprachen wird wegen der bei Mehrfachvererbung | ||
+ | auftretenden Probleme nur eine eingeschränkte Form | ||
+ | ([[begriffe# | ||
+ | Implementierung von rein abstrakten [[begriffe# | ||
+ | erlaubt. Dennoch kann Mehrfachvererbung mit einer Rautenstruktur | ||
+ | auch in realen Problemstellungen vorkommen (siehe nebenstehende Abbildung). |
kennen/klassen.txt · Zuletzt geändert: 2020-07-27 09:51 von 127.0.0.1