namespace cpp {}

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


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, zusammengesetzte Datentypen.
 +Objekte eine Klasse weisen bestimmte gemeinsame Merkmale
 +(Datenkomponenten, [[#Attribute]]) auf
 +und verhalten sich gleichartig.
 +Sie verfügen über eigene [[#Methoden]]
 +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:
 +
 +>  ''class'' Klassenname '';''
 +<code cpp>
 +class Bruch;
 +void ausgabe(Bruch const& x);
 +</code>
 +
 +==== Definition ====
 +Die Klassendefinition (öffentliche Schnittstelle)
 +ist eine Typvereinbarung, 
 +die Daten-[[#Attribute]]
 +und [[#Methoden]]
 +dieses Typs zusammenfasst.
 +Bis auf die [[#Zugriffsrechte]]
 +sind [[.:keywords#struct]] und
 +[[.:keywords#class]] gleichwertig.
 +Einer abgeschlossenen Typdefinition kann nichts hinzugefügt werden,
 +jedoch können durch [[#Vererbung]]
 +Typen abgeleitet werden.
 +
 +Syntax:
 +
 +>  ''class'' Klassenname 
 +>  ''{''
 +>>  Zugriffsrechte 
 +>>  Methoden- und
 +>>  Attributvereinbarungen (in beliebiger Folge)
 +>  ''};''
 +<code cpp>
 +class Bruch
 +{
 +public:
 +  Bruch(int zaehler, int nenner);  // Konstruktoren
 +  Bruch();
 +
 +  Bruch& add(Bruch const& b);      // schreibende Methoden
 +  int zaehler() const;             // lesende Methoden
 +  int nenner() const;
 +private:
 +  Bruch& kuerzen();                // versteckte Hilfsmethoden
 +  int z, n;                        // Attribute
 +};
 +</code>
 +
 +===== Attribute =====
 +Die Festlegung, welche Daten zu einem Typ gehören, erfolgt
 +bei der [[#Definition]]
 +der Klasse. Die [[#Objekte]]
 +enthalten jeweils unabhängig voneinander einen Satz von Datenkomponenten (Attributen).
 +Ihre Startwerte können durch [[#Konstruktoren]]
 +festgelegt werden. 
 +C++11 erlaubt auch die Zuweisung von Anfangswerten bei der Definition:
 +<code cpp>
 +class Bruch
 +{
 +  // ...
 +  int z = 0, n = 1;
 +};
 +</code>
 +===== Methoden =====
 +Methoden einer Klasse werden in der [[#Definition]]
 +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 ''::'' Methodenname ''('' Parameterliste '')''
 +>  ''{''
 +>>  Anweisungen
 +>  ''}''
 +Innerhalb der Klassendefinition aufgeführte Methodenrümpfe gelten als 
 +[[.:keywords#inline]] definiert.
 +
 +==== const-(Lese)-Methoden ====
 +Folgt der Parameterliste [[.:keywords#const]]
 +und nur lesende Methoden aufrufen.
 +Andererseits dürfen bei konstanten Objekten nur lesende Methoden aktiviert werden.
 +
 +<code cpp>
 +int Bruch::zaehler() const
 +{
 +  return z;
 +}
 +</code>
 +===== Besondere Methoden =====
 +Jede Klasse enthält, wenn nichts anderes festgelegt wurde,
 +einige Methoden, ohne die keine Klasse auskommt:
 +Konstruktoren, Zuweisungsoperatoren und einen Destruktor.
 +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 ''('' Parameterliste '');''
 +
 +Vor der Ausführung des Konstruktorrumpfes können (und sollten) die Attribute Startwerte erhalten
 +(Initialisiererliste).
 +
 +Syntax:
 +
 +>  Klassenname ''::'' Klassenname ''('' Parameterliste '')''
 +>  '':'' Initialisiererliste
 +>  ''{'' Anweisungen ''}''
 +<code cpp>
 +Bruch::Bruch(int zaehler, int nenner)
 +: z(zaehler), n(nenner)    // anstelle von z = zaehler,
 +{                          //              n = nenner  im Rumpf
 +  kuerzen();
 +}
 +</code>
 +
 +==== 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::Bruch()
 +: z(0), n(1)
 +{
 +}
 +</code>
 +
 +==== 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() 
 +: Bruch(0, 1) 
 +{
 +}
 +</code>
 +
 +==== 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:
 +>  ''~''Klassenname ''();''
 +
 +Eine Klasse besitzt nur einen (parameterlosen) Destruktor.
 +Der Standard-Destruktor vernichtet die im [[#Objekte|Objekt]] enthaltenen [[#Attribute]], tut aber sonst nichts.
 +Soll beim Vernichten eines Objektes etwas Besonderes geschehen,
 +so muss der Destruktor umdefiniert werden.
 +
 +Syntax:
 +>  Klassenname''::~''Klassenname ''() {'' Anweisungen ''}''
 +
 +In der Klasse ''Bruch'' hat der Destruktor keine besonderen Aufräumarbeiten
 +zu erledigen. Daher müsste er nicht definiert werden.
 +Sollen von der Klasse jedoch Untertypen [[#Vererbung|abgeleitet]] werden, 
 +ist er als [[#virtuelle_methoden|virtual]] 
 +zu deklarieren((Vor C++11 wurde ''virtual ~Bruch() {}'' [[spezielle_methoden|geschrieben]].)):
 +<code cpp>
 +class Bruch
 +{
 +  // ...
 +  virtual ~Bruch() = default;
 +}
 +</code>
 +
 +===== Zugriffsrechte =====
 +Zugriffsrechte auf Attribute und Methoden werden von der Klasse verliehen.
 +In der Klassendefinition stehende Abschnitte markieren 
 +nachfolgend aufgeführte Komponenten als
 +
 +| [[.:keywords#public]]: | von außerhalb zugreifbar, |
 +| [[.:keywords#protected]]: | nur innerhalb dieser Klasse und ihrer Erben ([[#Vererbung]]), |
 +| [[.:keywords#private]]: | nur innerhalb dieser Klasse, aber nicht für Erben zugänglich. |
 +
 +Bei Strukturen ist das Zugriffsrecht ''public'' voreingestellt,
 +bei Klassen ''private''.
 +
 +Eine Klasse kann andere Klassen oder Funktionen zu Freunden
 +([[.:keywords#friend]]) erklären,
 +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 [[#Standardkonstruktor]] aufgerufen. 
 +Die Startwerte sind in runden Klammern (oder ab [[begriffe#C++11]] auch in geschweiften Klammern) zu schreiben.
 +
 +<code cpp>
 +Bruch a;
 +Bruch b(1, 2);
 +Bruch c{3, 4};
 +Bruch f();     // Funktion, die einen Bruch liefert    
 +Bruch* zeiger = &b;
 +</code>
 +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#most_vexing_parse|most vexing parse]]
 +und 
 +[[begriffe#uniform initialization]]. 
 +Besitzt die Klasse einen Konstruktor mit [[Initialisiererliste]], wird dieser bevorzugt.))
 +
 +Zugängliche Attribute und Methoden von existierenden Objekten
 +werden mittels Punkt- bzw. 
 +Pfeil-[[.:operator#Zugriff|Operator]] angesprochen.
 +
 +Syntax:
 +>  Variablenname''.''Komponente
 +>  Objektzeiger''->''Komponente
 +
 +<code cpp>
 +cout << b.zaehler()      << endl;
 +cout << zeiger->nenner() << endl;
 +// b.n = 0;       // nicht erlaubt, da n nicht öffentlich      
 +// zeiger->n = 0; // ebenfalls nicht erlaubt
 +</code>
 +
 +===== Statische Komponenten =====
 +Als [[.:keywords#static]] 
 +definierte Klassenvariablen bilden
 +einen gemeinsam nutzbaren Speicherbereich ("shared memory").
 +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; }
 + ~Objektzaehler() { --anzahl; }
 +};
 +</code>
 +Statische Komponenten können auch ohne Vorhandensein von Objekten
 +als Klassenname''::''Komponente angesprochen werden.
 +Statische Klassenvariablen ohne ''inline''-Deklaration (vor C++17) müssen für den Linker 
 +einmal im Programm global angegeben werden
 +und sollten dabei initialisiert werden.
 +
 +<code cpp>
 +int Objektzaehler::anzahl = 0; // erlaubt, obwohl private! 
 +</code>
 +
 +===== Vererbung =====
 +==== Basisklassen und abgeleitete Klassen ====
 +Definierte Klassen können als 
 +[[.:begriffe#Basisklasse|Basisklassen]]
 +für die Bildung 
 +[[.:begriffe#abgeleitete Klasse|abgeleiteter Klassen]] dienen.
 +[[#Erweitern]]
 +der Schnittstellen und [[#Spezialisieren]]
 +von [[#abstrakte Methoden|abstrakten]] Aktionen 
 +verfeinern das Typensystem.
 +
 +Syntax:
 +
 +>  ''class'' Abgeleiteter_Klassenname '':'' Basisklassenliste
 +>  ''{''
 +>>  ...
 +>  ''};''
 +
 +<code cpp>
 +class GezaehlterBruch : public Bruch
 +{
 +public:
 +  GezaehlterBruch(int zaehler, int nenner);  // Konstruktoren
 +  GezaehlterBruch();
 +private:
 +  Objektzaehler anzahl;
 +};
 +</code>
 +Die Basisklassenliste kann mehrere Klassen enthalten
 +([[#Mehrfachvererbung]]). 
 +Vor jedem Basisklassennamen sollte die Vererbungsart durch 
 +eines der Wörter 
 +[[.:keywords#private]], 
 +[[.:keywords#protected]] oder 
 +[[.:keywords#public]]
 +angegeben werden. 
 +Die Vererbungsart kann das Zugriffsrecht 
 +auf Basisbestandteile in der abgeleiteten Klasse einschränken:
 +
 +| | ''private'' | ''protected''- | ''public''-Basiskomponenten|
 +| sind bei                   | im Erben     |
 +| ''private''-Vererbung   | unzugänglich | ''private''    | ''private''|
 +| ''protected''-Vererbung | unzugänglich | ''protected''  | ''protected''|
 +| ''public''-Vererbung    | unzugänglich | ''protected''  | ''public''|
 +
 +Konstruktoren übergeben am Beginn der Initialisiererliste Parameter
 +an Basiskonstruktoren, sofern diese Konstruktoren erfordern:
 +
 +<code cpp>
 +GezaehlterBruch::GezaehlterBruch(int zaehler, int nenner)
 +: Bruch(zaehler, nenner)
 +{
 +}
 +
 +GezaehlterBruch::GezaehlterBruch()
 +{ // Basis-Standardkonstruktor genutzt
 +}
 +</code>
 +
 +==== 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 [[.:keywords#virtual]] 
 +markierte Methoden der Basisklasse sind dafür vorgesehen, 
 +in abgeleiteten Klassen überschrieben zu werden.
 +
 +Syntax:
 +
 +>  ''class'' Basisklassenname
 +>  ''{''
 +>>  ''virtual'' Methodendeklaration
 +>  ''};''
 +
 +Wird ein abgeleitetes Objekt dann 
 +über einen Zeiger oder eine Referenz auf den Basistyp
 +aufgefordert, diese Methode auszuführen,
 +so wird die tatsächliche (engl. virtual) abgeleitete Methode aufgerufen
 +und nicht die Basismethode.
 +Die von der Basis abgeleiteten Objekte verhalten sich 
 +[[.:begriffe#Polymorphie|polymorph]].
 +
 +<code cpp>
 +class Basis
 +{
 +public:
 +  virtual ~Basis() = default;
 +  virtual char const* ich_bin() const { return "Basis"; }
 +};
 +
 +class Abgeleitet : public Basis
 +{
 +public:
 +  char const* ich_bin() const override { return "Abgeleitet";
 +};
 +
 +void test()
 +{
 +  Basis* p = new Abgeleitet();
 +  cout << p->ich_bin() << endl;  // ehrliche Antwort: Abgeleitet
 +  delete p;                      // ruft p->~Abgeleitet() 
 +}
 +</code>
 +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 [[.:beispiel:override|override]] am Ende des Methodenkopfes markiert werden.
 +Als [[.:beispiel:override#final]] markierte virtuelle Methoden lassen sich nicht weiter überschreiben.
 +Von als [[.:beispiel:override#final]] markierte Klassen können keine weiteren abgeleiteten Klassen gebildet werden.
 +
 +==== Abstrakte Methoden ====
 +Abstrakte Methoden sind [[#virtuelle Methoden]],
 +für die in der Basisklasse noch keine "vernünftige" Implementierung
 +angegeben werden kann.
 +Da der Methodenrumpf fehlt, wird in der Klassendefinition 
 +ein Nullzeiger ''= 0'' in die Methodentabelle eingetragen.
 +
 +Syntax:
 +
 +>  ''class'' Basisklassenname
 +>  ''{''
 +>>  ''virtual'' Methodenkopf ''= 0;''
 +>  ''};''
 +
 +<code cpp>
 +class Process
 +{
 +public:
 +  virtual ~Process() {}
 +  virtual void run() = 0;
 +};
 +</code>
 +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 
 +[[#Spezialisieren|überschrieben]] haben.
 +
 +==== 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);  // Konstruktoren
 +  GezaehlterBruch();
 +};
 +</code>
 +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;
 +}
 +</code>
 +Virtuelle Basisklassen erzeugen nur eine Instanz, 
 +auch wenn sie über mehrere Basisklassen vererbt werden.
 +Falls es keinen Standardkonstruktor für den Ahnen ''A'' gibt,
 +muss bei ''C'' ein Konstruktor für ''A'' explizit
 +und vor den Konstruktoren von ''B1'' und ''B2'' aufgerufen werden. 
 +{{ :kennen:raute.png|}}
 +
 +<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;
 +}
 +</code>
 +
 +In anderen Programmiersprachen wird wegen der bei Mehrfachvererbung
 +auftretenden Probleme nur eine eingeschränkte Form
 +([[begriffe#Einfachvererbung]] und 
 +Implementierung von rein abstrakten [[begriffe#Schnittstelle]]n)
 +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

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki