Abgeleitete Klassen
Eine abgeleitete Klasse wird eingesetzt, wenn:
- der Anwender eine vorgegebene Klasse um Eigenschaften und/oder Methoden erweitern möchte, ohne den Quellcode der Ausgangsklasse zu modifizieren.
- Eigenschaften und Methoden, die in mehreren Klassen benötigt werden, in eine eigene Klasse ausgelagert werden soll.
Beispiel zum ersten Punkt:
Sie haben eine Grafikbibliothek erworben, die eine Klasse zur Darstellung eines einfarbig ausgefüllten Rechtecks enthält. In der Anwendung benötigen Sie aber ein Rechteck, das mit einem Muster auszufüllen ist. Dazu kann die vorgegebene Klasse für das einfarbig ausgefüllte Rechteck als Basis verwendet werden und um die Eigenschaften des Musters erweitert werden. Und für diese Erweiterung wird nicht einmal den Quellcode der Ausgangsklasse benötigt sonden lediglich deren Klassendefinition.
Beispiel zum zweiten Punkt:
Eine Bibliothek zur Darstellung von grafischen Elementen enthält eine allgemeine Klasse mit den für jedes Grafikelement notwendigen Eigenschaften und Methoden, wie z.B. die Position und Farbe. Alle Grafikelemente verwenden dann diese allgemeine Klasse und fügen zu deren Member ihre spezifischen Member hinzu, wie z.B. den Radius für einen Kreis.
Weiter vorne haben Sie ebenfalls eine Möglichkeit kennengelernt, eine Klasse in ihrer Funktionalität zu erweitern, nämlich durch Einschließen von Objekten in eine Klasse. Und damit stellt sich die Frage, wann ein Objekt einer Klasse in eine andere Klasse eingeschlossen wird und wann eine Klasse abgeleitet wird. Als Faustregel können Sie sich folgenden Satz merken:
Ein Objekt einer Klasse wird in eine andere Klasse eingeschlossen, wenn zwischen ihnen eine hat-eine (has-a) Beziehung besteht und abgeleitet wird, wenn eine ist-eine (is-a) Beziehung besteht.
Beispiel:
Gegeben sei eine Klasse Color mit Farbinformationen und eine Klasse Win zur Darstellung eines Fensters. Da ein Fenster eine Farbe hat, wird die Klasse Color in Win eingeschlossen. Nun soll für eine abweichende Fensterdarstellung eine neue Fensterklasse SpecWin erstellt werden. In diesem Fall wird SpecWin von Win abgeleitet, da SpecWin ein spezielles Fenster ist.
Beispiele für Ableitung
Ausgangsbeispiel
Nachfolgend sind zwei Klassen für die Darstellung von Grafikelementen definiert. Die Klasse Frame soll zur Darstellung eines Rahmens dienen und die Klasse Bar zur Darstellung eines ausgefüllten Rechtecks. Beide Klassen besitzen Gemeinsamkeiten. So haben beide Klassen die Eigenschaft, dass sie eine Position und eine Größe besitzen. Außerdem stehen in beiden Klassen Methoden zur Verfügung, um diese Eigenschaften verändern zu können.
!! Nur fiktives Beispiel !!
class Frame
{
short xPos, yPos;
short width, height;
...
public:
Frame(...);
void Draw() const;
void SetPosition(...);
void SetSize(...);
...
};
class Bar
{
short xPos, yPos;
short width, height;
short fillColor;
...
public:
Bar(...);
void Draw() const;
void SetPosition(...);
void SetSize(...);
...
};
Die in beiden Klassen vorhandene Methode Draw() eignet sich eher nicht zum Auslagern, da die Objekte sicherlich unterschiedlich dargestellt werden.
Auslagern von Member
Laut vorheriger Aussage lassen sich die gemeinsamen Eigenschaften und Methoden in eine eigene Klasse auslagern. Im nachfolgenden Beispiel wurden die Gemeinsamkeiten in die Klasse GBase ausgelagert. Um diese ausgelagerten Eigenschaften und Methoden wieder zu den Klassen Frame und Bar 'hinzuzufügen', werden die beiden Klassen nachher von der Klasse GBase abgeleitet. D.h. eine Ableitung vererbt (fast) alle Member der Basisklasse an die von ihr abgeleitete Klasse.
!! Nur fiktives Beispiel !!
// Basisklasse fuer alle Grafikobjekte
class GBase
{
short xPos, yPos;
short width, height;
public:
GBase(...);
void SetPosition(...);
void SetSize(...);
}
class Frame // hier die GBase Ableitung definieren
{
...
public:
Frame(...);
void Draw() const;
...
};
class Bar // hier die GBase Ableitung definieren
{
short fillColor;
...
public:
Bar(...);
void Draw() const;
...
}
Klasse erweitern
Sehen wir uns das Erweitern einer Klasse durch Ableitung an. Angenommen, wir haben die vorherigen Klassen für Grafiken in einer Klassenbibliothek erworben. Diese Bibliothek enthält u.a. die Klasse Bar für ein einfarbig ausgefülltes Rechteck. Wir benötigen aber ein Rechteck, das mit einem Muster ausgefüllt ist. In diesem Fall leiten wir eine neue Klasse PBar von der vorgegebenen Klasse Bar ab und fügen ihr die Eigenschaften für das Muster sowie die entsprechenden Methoden hinzu.
!! Nur fiktives Beispiel !!
class PBar // hier die Bar Ableitung definieren
{
short pattern;
public:
PBar(...);
void SetPattern(...);
void Draw() const;
}
Im Zusammenhang mit Ableitungen sind zwei neue Begriffe zu klären:
- Basisklasse oder Superklasse
- Abgeleitete Klasse oder Subklasse
Die Basisklasse ist diejenige Klasse, die ihre Eigenschaften und Methoden an die abgeleitete Klasse weitergibt (vererbt). Die abgeleitete Klasse erbt alle Eigenschaften und Methoden der Basisklasse, d.h., die abgeleitete Klasse verhält sich so als wären die geerbten Eigenschaften und Methoden innerhalb der abgeleiteten Klasse definiert.
Und eine Klasse kann gleichzeitig sowohl Basisklasse als auch abgeleitete Klasse sein.
Zugriffsrecht protected
Bevor wir näher auf das Ableiten eingehen, sehen wir uns noch das fehlende Zugriffsrecht protected an. Das Zugriffsrecht protected ist eine Mischung aus den Zugriffsrechten public und private und kann überall dort stehen, wo Zugriffsrechte erlaubt sind. Auf die protected-Member einer Klasse kann nur von abgeleiteten Klassen (und der Klasse selbst) aus zugegriffen werden, d.h. sie verhalten aus der Sicht der abgeleiteten Klasse wie public-Member. Der Zugriff auf protected-Member über Objekte ist nicht möglich. Hier verhalten sich die protected-Member wie private-Member.
Der Einsatz von protected-Eigenschaften ist stets zu hinterfragen, da sie die Komplexität einer Klasse erhöhen. Jede abgeleitet Klasse kann diese Eigenschaften direkt verändern und somit eine eventuell in der Basisklasse vorhandene Plausibilitätsprüfung umgehen.
Definition der Ableitung
Das Ableiten einer Subklasse von einer Basisklasse erfolgt mit folgender Syntax:
class CSub: [ACCESS] CSuper
{...};
CSub ist der Name der abgeleiteten Klasse und CSuper der Name der Basisklasse. ACCESS gibt an, mit welchem Zugriffsrecht aus Sicht eines Objekts der abgeleiteten Klasse oder weiteren von CSub abgeleiteten Klassen die Member der Basisklasse in die abgeleitete Klasse übernommen werden.
Für ACCESS können alle Zugriffsrechte (public, protected, private) eingesetzt werden. Wird ACCESS nicht spezifiziert, wird standardmäßig bei einer Ableitung von einer Klasse vom Typ class das Zugriffsrecht private angenommen und bei einer Ableitung von einer Klasse vom Typ struct das Zugriffsrecht public.
Wie erwähnt, ist der einzige Unterschied zwischen class und struct das standardmäßige Zugriffsrecht auf die Member.
Die abgeleitete Klasse erbt nicht nur die Eigenschaften und die 'normalen' Methoden ihrer Basisklasse, sondern auch deren überladene Operatoren, mit Ausnahme zweier Besonderheiten:
- Überlädt eine Basisklasse den Zuweisungsoperator '=', wird dieser überladene Operator nicht an die abgeleitete Klasse vererbt.
- Wird in einer abgeleiteten Klasse der Operator auto operator <=>(const Sub& rh) const = default; definiert, muss er in der Basisklasse ebenfalls definiert werden.
Um die Auswirkung des Zugriffsrechts ACCESS zu verdeutlichen, sollen folgende Ableitungen definiert sein:
// Basisklasse
class Base
{...};
// Sub-Klasse von Base
class Sub: ACCESS Base
{...};
// Sub-Sub-Klasse von Base
class SubSub: public Sub
{...};
Von der Klasse Base ist eine Klasse Sub abgeleitet und von der wiederum eine Klasse SubSub.
ACCESS = public
Bei einer public-Ableitung können Member der abgeleiteten Klasse Sub sowie Member der weiter abgeleiteten Klasse SubSub auf alle public- und protected-Member der Basisklasse Base zugreifen.
Objekte vom Typ der abgeleiteten Klassen Sub und SubSub haben nur Zugriff auf die public-Member der Basisklasse Base.
ACCESS = protected
Auch hier können die Member der abgeleiteten Klassen Sub und SubSub auf die public- und protected-Member der Basisklasse Base zugreifen.
Objekte vom Typ der von der abgeleiteten Klassen Sub und SubSub haben nun aber keinen Zugriff mehr auf die Member der Basisklasse Base.
ACCESS = private
Member der abgeleiteten Klasse Sub haben weiterhin Zugriff auf die public- und protected-Member der Basisklasse Base. Aber Member der weiter abgeleiteten Klasse SubSub haben jetzt keinen Zugriff mehr auf die Member der Basisklasse Base.
Für die Objekte vom Typ Sub und SubSub gilt das gleiche wie bei der protected-Ableitung: keinen Zugriff auf die Member der Basisklasse Base.
Die nachfolgende Tabelle zeigt nochmals in einer Übersicht, wie sich das Zugriffsrecht beim Ableiten auf die Basisklassen-Member auswirkt:
| ACCESS | Member Basisklasse | Wird zu ... der Subklasse |
|---|---|---|
| privat | private | kein Zugriff |
| protected | private Member | |
| public | private-Member | |
| protected | private | kein Zugriff |
| protected | protected-Member | |
| public | protected-Member | |
| public | private | kein Zugriff |
| protected | protected-Member | |
| public | public-Member |
Konstruktor und Destruktor
Ausgangspunkt ist wieder die Klasse GBase sowie die von ihr abgeleitete Klasse Frame. Beide Klassen wurden um einen Konstruktor erweitert.
// Basisklasse fuer alle Grafikobjekte
class GBase
{
short xPos, yPos; // Position
short width, height; // Ausdehnung
public:
// ctor
GBase(short xp, short yp, short w, short h)
{
xPos = xp; yPos = yp;
width = w; height = h;
}
// ... Weitere Methoden
};
// Klasse fuer Rahmen
class Frame: public GBase
{
unsigned long lineColor; // Linienfarbe
public:
// ctor: was passiert mit den GBase Eigenschaften?
Frame(short xp, short yp, short w, short h,
unsigned long lColor)
{
lineColor = lColor;
}
// ... weitere Methoden
};
int main()
{
// Frame Objekt definieren
Frame frameObj(10,10,640,480,0xFF00FFUL);
}
Der Konstruktor der Klasse Frame erhält die Position, Größe und Linienfarbe des Rahmens übergeben. Da Frame aber nur die Eigenschaft für die Linienfarbe enthält, müssen die anderen Daten an die Basisklasse GBase weitergegeben werden. D.h., der Konstruktor von Frame muss den Konstruktor von GBase aufrufen.
Konstruktor der abgeleiteten Klasse
Der Aufruf des Konstruktors der Basisklasse erfolgt bei der Definition des Konstruktors der abgeleiteten Klasse mit folgender Syntax:
CSub(P1, P2,...): CSuper(Pa, Pb,...)
{...}
CSub ist wieder der Name der abgeleiteten Klasse und CSuper der Name der Basisklasse.
D.h. der Konstruktors der Basisklasse wird in der Initialisiererliste der abgeleiteten Klasse aufgerufen und erhält so die entsprechenden Daten.
// Basisklasse fuer alle Grafikobjekte
class GBase
{
short xPos, yPos; // Position
short width, height; // Ausdehnung
public:
// ctor
GBase(short xp, short yp, short w, short h)
{
xPos = xp; yPos = yp;
width = w; height = h;
}
// ... Weitere Methoden
};
// Klasse fuer Rahmen
class Frame: public GBase
{
unsigned long lineColor; // Linienfarbe
public:
Frame(short xp, short yp, short w, short h,
unsigned long lColor):
GBase(xp, yp, w, h)
{
lineColor = lColor;
}
// ... weitere Methoden
};
int main()
{
// Frame Objekt definieren
Frame frameObj(10,10,640,480,0xFF00FFUL);
}
Der Aufruf des Konstruktors der Basisklasse erfolgt nur bei der Definition des Konstruktors der abgeleiteten Klasse. An der Deklaration des Konstruktors der abgeleiteten Klasse ändert sich nichts.
Konstruktor bei mehrstufigen Ableitungen
Doch wie sehen bei einer mehrstufigen Ableitung (GBase -> Frame -> Bar) die Definitionen der Konstruktoren aus? Bei einer mehrstufigen Ableitung ruft der Konstruktor einer abgeleiteten Klasse nur den Konstruktor seiner Basisklasse auf. D.h, der Konstruktor von Bar ruft nur den Konstruktor von Frame auf und dieser dann wiederum den seiner Basisklasse GBase auf.
Dieser Aufrufmechanismus funktioniert auch dann, wenn für eine Basisklasse kein Konstruktor oder nur der Standardkonstruktor definiert ist. In diesem Fall braucht der Konstruktor der abgeleiteten Klasse nichts tun. Es wird immer automatisch der 'nächst höhere' Konstruktor aufgerufen.
Aufruf-Reihenfolge der Konstruktoren
Wird ein Objekt einer abgeleiteten Klasse definiert, wird zuerst der Konstruktor der Basisklasse ausgeführt, damit sichergestellt ist, dass beim Eintritt in den Konstruktor der abgeleiteten Klasse alle Eigenschaften der Basisklasse initialisiert sind. Im Beispiel werden die Klassen GBase, Frame und Bar mit den bekannten Ableitungen definiert. Da GBase die 'oberste' Basisklasse ist, wird deren Konstruktor als Erstes ausgeführt, anschließend der Konstruktor von Frame und zum Schluss der Konstruktor der 'untersten' Klasse Bar.
#include <print>
// Basisklasse
class Base
{
int val;
public:
// ctor
Base(int param)
{
val = param;
std::println("ctor Base");
}
};
// 1. Ableitung
class Sub: public Base
{
public:
// ctor
Sub(int param): Base(param)
{
std::println("ctor Sub");
}
};
// 2. Ableitung
class SubSub: public Sub
{
int var;
public:
SubSub(int param1, int param2):
Sub(param2)
{
var = param1;
std::println("ctor SubSub");
}
};
int main()
{
// SubSub Objekt definieren
SubSub obj(1,2);
}
ctor Base
ctor Sub
ctor SubSub
Aufruf-Reihenfolge der Destruktoren
Und was für die Konstruktoren gilt, gilt auch für die Destruktoren. Nur ist hier die Sache einfacher, da ein Destruktor (fast) niemals direkt aufgerufen wird. Bei der Definition eines Destruktors muss also keine Rücksicht darauf genommen werden, ob die Klasse von einer anderen Klasse abgeleitet ist oder nicht. Wird ein Objekt, das in der Ableitungshierarchie ganz unten steht, entfernt, wird zuerst dessen Destruktor ausgeführt, anschließend der Destruktor seiner Basisklasse und dann der Destruktor der nächsthöheren Basisklasse. Die Aufruf-Reihenfolge ist hier genau umgekehrt wie bei den Konstruktoren. Damit würde sich folgende Aufruf-Reihenfolge der Destruktoren ergeben:
dtor SubSub
dtor Sub
dtor Base
Vorab ein kleiner Hinweis: Der Destruktor einer Klasse sollte entweder public und virtuell sein oder aber protected und nicht-virtuell. Das Warum sehen wir uns später bei der Behandlung von virtuellen Methoden genauer an (Virtuelle Methoden).
Zugriff auf Basisklassen-Member
Wie erwähnt, können Methoden in abgeleitete Klassen auf die nicht-private-Member der Basisklasse zugreifen. Für den Zugriff reicht im Normalfall aus, wenn der Membername angegeben wird. Nicht-private-Member von Basisklassen verhalten sich für abgeleitete Klassen ja prinzipiell wie eigene Member. Im Beispiel wird aus der Methode DoAnything() der abgeleiteten Klasse auf die Eigenschaft val der Basisklasse zugegriffen und deren Methode SetVal() aufgerufen.
class Base
{
protected:
short val;
public:
Base(...)
void SetVal(...);
};
class Sub: public Base
{
...
public:
Sub(...);
void DoAnything();
};
// Zugriff auf Member der Basisklasse
void Sub::DoAnything()
{
val -= 10; // Dies sind Member der
SetVal(100); // der Basisklasse!
...
}
Inwieweit der direkte Zugriff auf Basisklassen-Eigenschaften einer sauberen Programmierung entspricht, sei dahingestellt. Das Beispiel dient nur zur Demonstration, wie auf Member einer Basisklasse zugegriffen werden kann.
Gleiche Member in Basis- und Sub-Klasse
Dieser direkte Zugriff funktioniert aber nur so lange, wie die abgeleitete Klasse kein Member mit gleichem Namen besitzt wie die Basisklasse. Besitzt die abgeleitete Klasse Member mit gleichem Namen wie die Basisklasse, wird standardmäßig auf das Member der eigenen Klasse zugegriffen. Um auf das Member der Basisklasse zuzugreifen, ist vor dem Membernamen der Name der Basisklasse anzugeben, gefolgt vom Zugriffsoperator ::.
class Base
{
protected:
short val;
public:
Base(...)
void SetVal(...);
};
class Sub: public Base
{
short val;
public:
Sub(...);
void DoAnything();
};
// Zugriff auf Member der Basisklasse
void Sub::DoAnything()
{
val -= 10; // Sub Member
Base::val = 33; // Base Member
...
}
Zugriff auf Basisklassen-Member über Objekte
Beim Zugriff über Objekte einer abgeleiteten Klasse auf die Basisklassen-Member reicht im Regelfall die Angabe des Membernamens aus. Der erste Zugriff unten über ein Objekt der Klasse Sub ruft die Methode SetVal() der Klasse Base auf. Dieser Zugriffsmechanismus funktioniert über mehrere Ableitungsebenen hinweg.
int main()
{
// Objekt der abgeleiteten Klasse
Sub subObj{...};
// Ruft SetVal() der Basisklasse Base auf
subObj.SetVal(...);
}
Verdeckte Basisklassen-Member
Im Zusammenhang mit Aufrufen von Methoden einer Basisklasse soll auf eine kleine Stolperfalle hingewiesen werden. Sehen wir uns zunächst einmal die nachfolgend dargestellten Klassen an. Sowohl Base als auch Sub besitzen jeweils eine Methode SeVal(). Die Methode der Klasse Base besitzt jedoch einen Parameter und die der Klasse Sub zwei Parameter.
// Basisklasse
class Base
{
int val1 = 0;
public:
void SetVal(int param)
{
val1 = param;
}
};
// 1. Ableitung
class Sub: public Base
{
int val1 = 0;
int val2 = 0;
public:
void SetVal(int param1, int param2)
{
val1 = param1; val2 = param2;
}
};
int main()
{
// Objekte definieren
Base baseObj;
Sub subObj;
// Methoden SetVal() aufrufen
baseObj.SetVal(10); // Base::SetVal()
subObj.SetVal(10,20); // Sub::SetVal()
subObj.SetVal(10); // !! Fehler
subObj.Base::SetVal(10); // So geht's: Base::SetVal()
}
Bei den ersten beiden Aufrufen wird die zum Objekt gehörige Methode aufgerufen. Einen Fehler verursacht dagegen der dritte Aufruf (Zeile 31). Was hier versucht wird, ist, die Methode SetVal() der Klasse Base über ein Sub-Objekt aufzurufen, was zu einem Fehler führt. Member einer abgeleiteten Klasse verdeckt standardmäßig gleichnamige Member ihrer Basisklasse(n). Um trotzdem auf die Methode SetVal() der Klasse Base über ein Sub-Objekt zuzugreifen, ist, wie im letzten Aufruf angegeben, nach dem Punktoperator der Klassenname, dann der Zugriffsoperator :: und danach die Methode anzugeben.
Einblenden von Basisklassen-Methoden
Nun kann es manchmal aber durchaus sinnvoll sein, dass gleichnamige Methoden der Basisklasse auch für Objekte der abgeleiteten Klasse verwendet werden. Hierzu ist in der abgeleiteten Klasse die Methode der Basisklasse mittels einer using-Anweisung einzublenden. Die allgemeine Syntax dieser using-Anweisung ist wie folgt:
using BASISKLASSE::MEMBER;
Erweitern wir dazu das vorherige Beispiel:
// Basisklasse
class Base
{
int val1 = 0;
public:
void SetVal(int param)
{
val1 = param;
}
};
// 1. Ableitung
class Sub: public Base
{
int val1 = 0;
int val2 = 0;
public:
using Base::SetVal;
void SetVal(int param1, int param2)
{
val1 = param1; val2 = param2;
}
};
int main()
{
// Objekte definieren
Base baseObj;
Sub subObj;
// Methoden SetVal() aufrufen
baseObj.SetVal(10); // Base::SetVal()
subObj.SetVal(10,20); // Sub::SetVal()
subObj.SetVal(10); // Jetzt geht's: Base::SetVal()
subObj.Base::SetVal(10); // So geht's: Base::SetVal()
}
Hierbei ist zu beachten, dass zum einen die using-Anweisung innerhalb der public-Sektion der abgeleiteten Klasse steht. Und zum anderen wird nur der Name der Methode angegeben, also ohne deren Parameter. Daraus folgt: Enthält eine Basisklasse mehrere Methoden mit gleichem Namen, aber mit unterschiedlichen Parametern, werden alle Methoden mit diesem Namen eingeblendet.
Basisklassenzeiger
Im Verbindung mit abgeleiteten Klassen sei an dieser Stelle vorab auf folgenden wichtigen Sachverhalt hingewiesen: Ein Zeiger vom Typ der Basisklasse kann Zeiger vom Typ einer abgeleiteten Klasse aufnehmen. Diese Eigenschaft spielt später bei den virtuellen Methoden eine entscheidende Rolle.
#include <print>
// Basisklasse
class Base
{
int val1 = 0;
public:
// ctor
Base(int param): val1{param}
{
std::println("ctor Base");
}
// dtor
~Base()
{
std::println("dtor Base");
}
};
// 1. Ableitung
class Sub: public Base
{
int val2 = 0;
public:
// ctor
Sub(int param1, int param2):
Base{param1}, val2{param2}
{
std::println("ctor Sub");
}
// dtor
~Sub()
{
std::println("dtor Sub");
}
};
int main()
{
// Basisklassen-Zeiger definieren
Base *pObj;
// Objekt der abgeleiteten Klasse erstellen
pObj = new Sub{10,20};
// ... weitere Anweisung
// Und Objekt auch wieder loeschen!!
delete pObj;
}
ctor Base
ctor Sub
dtor Base
In Zeile 40 wird ein Zeiger auf die Basisklasse Base definiert, dem dann in Zeile 42 ein Objekt der abgeleiteten Klasse Sub zugewiesen wird.
Wenn Sie sich die Ausgabe des Programms ansehen werden Sie feststellen, dass der Destruktor der abgeleiteten Klasse Sub fälschlicherweise nicht aufgerufen wird. Das wird sich aber im übernächsten Kapitel noch ändern.
Übungen
abgel_01:
Es sollen die Daten von Elektroautos und Verbrenner abgespeichert werden. Das Elektoauto soll die Eigenschaften Modell, Reichweite und Batteriekapazität besitzen und der Verbrenner Modell, Reichweite und Tankkapazität.
Entwickeln Sie entsprechende Klassen für die beiden Typen von Fahrzeugen.
Zur Ausgabe der Daten ist der Operator << zu verwenden.
Definieren Sie je ein Objekt vom Typ Elektroauto und Verbrenner und geben deren Daten aus.
Modell: RX3, Reichweite: 500, Tankinhalt: 80 ltr.
Modell: CLEAN, Reichweite: 450, Batteriekapazitaet: 78 kWh
abgel_02:
Zum Abspeichern von zwei verschiedenen Artikel ist eine Anwendung zu erstellen.
Der erste Artikeltyp ist ein Monitor mit den Eigenschaften Artikelnummer, Monitorhersteller und Monitordiagonale.
Der zweite Artikeltyp ist eine Tastatur mit den Eigenschaften Artikelnummer, Hersteller der Tastatur und das Tastaturlayout (deutsch oder englisch).
Die Artikelnummer ist unabhängig vom Artikeltyp und soll beim Ablegen eines Artikels fortlaufend inkrementiert werden.
Definieren Sie ein Feld für 3 Artikel, in dem beide Artikeltypen abgelegt werden können. Legen Sie in dem Feld zwei Artikel vom Typ Monitor und einen Artikel vom Typ Tastatur ab.
Tipp: Sie müssen die Artikel dynamisch erstellen und die Zeiger darauf im Feld ablegen.
Die Ausgabe der Artikel soll mithilfe eines Ausgabestreams erfolgen, d.h., der Operator << ist entsprechend zu überladen.
Hinweis: Der Operator << ist für jeden Artikeltyp getrennt zu definieren. Damit eindeutig ist, für welchen Artikeltyp der Operator auszuführen ist, muss der im Feld abgelegte Zeiger auf den entsprechenden Artikeltyp konvertiert werden. D.h. der Artikel muss seinen Artikeltyp kennen.
Geben Sie die Artikel zunächst auf die Standardausgabe aus und legen sie anschließend in einer Datei ab.
Art-Nr.: 1, Monitor: ASUS, Diagonale: 27
Art-Nr.: 2, Tastatur: Microsoft, Typ: deutsch
Art-Nr.: 3, Monitor: Alienware, Diagonale: 32