Dynamische Eigenschaften und Objekte
Dynamische instrinsische Eigenschaften
Enthalten Objekte z.B. Felder, deren Größe von Objekt zu Objekt variiert, können diese bei der Definition des Objekts dynamisch angelegt werden. Ein Beispiel hierfür ist die im Kapitel über Konstruktor und Destruktor im Beispiel erwähnte Klasse Stack. War dort die Stackgröße fest vorgegeben, kann sie durch eine entsprechende dynamische Eigenschaft variable gestaltet werden.
Für eine dynamische Eigenschaft wird innerhalb der Klasse ein entsprechender Zeiger definiert. Der für die Eigenschaft notwendige Speicherplatz wird dann in der Regel im Konstruktor der Klasse mittels new reserviert. Wird das Objekt gelöscht, wird im Destruktor der reservierte Speicher mit delete wieder freigegeben.
class CAny
{
unsigned short *pData;
int dataSize;
public:
// ctor
CAny(int _size): dataSize(_size)
{
// Speicher fuer Daten reservieren
pData = new unsigned short[dataSize];
}
~CAny() // dtor
{
// Speicher wieder freigeben!
delete [] pData;
}
};
int main()
{
// CAny Objekt mit 10 Daten
CAny myData{10};
}
Enthält eine Klasse dynamische Eigenschaften, dürfen Objekte dieser Klasse vorläufig nicht einander zugewiesen bzw. kopiert werden. Durch die Zuweisung werden alle Eigenschaften des einen Objekts dem anderen Objekt zugewiesen. Und dies gilt auch für die dynamischen Eigenschaften, d.h., beide Objekte enthalten Verweise auf den gleichen Speicherbereich! Gibt dann eines der Objekte den reservierten Speicherbereich frei, hat das andere Objekt einen Verweis auf einen ungültigen Speicherbereich, was bis zum Programmabsturz führen kann!
Dynamische Objekte
Um ein Objekt dynamisch zu erstellen, erhält der Operator new als Operand den Namen der entsprechenden Klasse. Besitzt die Klasse einen Konstruktor mit Parameter (wie im nachfolgenden Beispiel), sind die Argumente nach dem Klassennamen in Klammern anzugeben.
Wird das Objekt nicht mehr benötigt, muss es wieder gelöscht werden. Dies erfolgt ebenfalls über den Operator delete, der den von new zurückgelieferten Zeiger als Operand erhält.
Der Zugriff auf die Member des erstellten Objekts erfolgt, wie bei Zeiger üblich, über den Zeigeroperator -> oder durch dereferenzieren des Zeigers.
class CAny
{
unsigned short *pData;
int dataSize;
public:
// ctor
CAny(int _size): dataSize(_size)
{
// Speicher fuer Daten reservieren
pData = new unsigned short[dataSize];
}
~CAny() // dtor
{
// Speicher wieder freigeben!
delete [] pData;
}
// Datum abspeichern
bool SetData(int index, unsigned short data)
{
// Wenn moeglich, Datum ablegen
return true;
}
};
int main()
{
// CAny Objekt mit 10 Daten
auto ptrData = new CAny{10};
// Datum ablegen
ptrData->SetData(2,15);
// Und Objekt auch wieder loeschen
delete ptrData;
}
Dynamische Objektfelder
Das Erstellen eines dynamischen Objektfeldes erfolgt analog zum Erstellen von dynamischen intrinsischen Feldern, nur erhält new jetzt als Operand den Klassennamen anstelle eines Datentyps. Und für jedes Objekt im Feld wird jetzt auch dessen Konstruktor aufgerufen.
// Noch nicht laufen lassen, da etwas
// Wichtiges noch fehlt!
#include <print>
#include <string>
class Window
{
std::string title;
public:
Window()
{
std::println("std-ctor Window");
}
Window(std::string_view _title): title(_title)
{
std::println("title-ctor Window");
}
~Window()
{
std::println("dtor Window");
}
void Draw()
{
std::println("Fenster {}",title);
}
};
int main()
{
// 3 Fenster anlegen
auto winList = new Window[3];
}
Um Objekte in einem dynamischen Objektfeld zu initialisieren, sind die Daten in einer Initialisiererliste anzugeben. Enthält die Initialisiererliste weniger Daten als Objekte im Objektfeld vorhanden sind, werden die restlichen Objekte mit dem Standardkonstruktor (parameterloser Konstruktor) initialisiert. Enthält die Initialisiererliste mehr Daten als Objekte im Feld, meldet der Compiler beim Übersetzen einen Fehler.
// Noch nicht laufen lassen, da etwas
// Wichtiges noch fehlt!
#include <print>
#include <string>
class Window
{
std::string title;
public:
Window()
{
std::println("std-ctor Window");
}
Window(std::string_view _title): title(_title)
{
std::println("title-ctor Window");
}
~Window()
{
std::println("dtor Window");
}
void Draw()
{
std::println("Fenster {}",title);
}
};
int main()
{
// 3 Fenster anlegen
auto winList = new Window[3] {{"Fenster1"},{"Fenster2"}};
}
Beachten Sie, dass die Initialwerte in geschweiften Klammern eingeschlossen werden müssen!
Löschen des Objektfeldes
Wird ein Objektfeld nicht mehr benötigt, muss es wieder mittels delete gelöscht werden. Hierbei ist darauf zu achten, dass beim delete Operator die für Felder notwendigen eckigen Klammern angegeben werden.
Sehen wir uns zur Demonstration einmal an was passiert, wenn diese eckigen Klammern aus Versehen vergessen werden.
Zunächst der korrekte Fall. Beim Anlegen des Feldes wird für jedes Objekt dessen Konstruktor aufgerufen. Wird das Feld gelöscht, muss für jedes Objekt im Feld auch dessen Destruktor aufgerufen werden.
#include <print>
#include <string>
class Window
{
std::string title;
public:
Window()
{
std::println("std-ctor Window");
}
Window(std::string_view _title): title(_title)
{
std::println("title-ctor Window");
}
~Window()
{
std::println("dtor Window");
}
void Draw()
{
std::println("Fenster {}",title);
}
};
int main()
{
// 3 Fenster anlegen
auto winList = new Window[3] {{"Fenster1"},{"Fenster2"}};
// ...winList berarbeiten
// Objektfeld wieder freigeben
delete [] winList;
}
title-ctor Window
title-ctor Window
std-ctor Window
dtor Window
dtor Window
dtor Window
Nun der Fall, dass beim Löschen des Feldes die eckigen Klammern vergessen wurden. Da der delete Operator wegen der fehlenden eckigen Klammern nicht weiß, ob ein einzelnes Objekt oder ein Objektfeld zu löschen ist, wird nur der Destruktor für das erste Objekt im Feld aufgerufen!
#include <print>
#include <string>
class Window
{
std::string title;
public:
Window()
{
std::println("std-ctor Window");
}
Window(std::string_view _title): title(_title)
{
std::println("title-ctor Window");
}
~Window()
{
std::println("dtor Window");
}
void Draw()
{
std::println("Fenster {}",title);
}
};
int main()
{
// 3 Fenster anlegen
auto winList = new Window[3] {{"Fenster1"},{"Fenster2"}};
// ...winList berarbeiten
// Objektfeld wieder freigeben
delete winList;
}
title-ctor Window
title-ctor Window
std-ctor Window
dtor Window
Memberzugriff
Der Zugriff auf Member im Objektfeld kann auf zweierlei Arten erfolgen.
Die erste Zugriffsart verwendet Zeigerarithmetik. Wie erwähnt, wird bei einer Addition eines Werts X auf einen Zeiger vom Typ DTYP der Zeiger um X*sizeof(DTYP) erhöht.
Alternativ kann anstelle des Zugriffs über einen Zeiger der indizierte Zugriff verwendet werden.
class Window
{
// ...Definition wie vorher
};
int main()
{
// 3 Fenster anlegen
auto winList = new Window[3]{{"Fenster1"},{"Fenster2"}};
// Fenster zeichen
(winList+1)->DrawWin(); // 2. Fenster
winList[2].DrawWin(); // 3. Fenster
// Objektfeld wieder freigeben
delete [] winList;
}
title-ctor Window
title-ctor Window
std-ctor Window
Fenster Fenster2
Fenster
dtor Window
dtor Window
dtor Window
Vorwärtsdeklaration
Im Zusammenspiel von dynamischen Objekten kann es u.U. zu Problemen mit Objektabhängigkeiten kommen. Sehen Sie sich dazu das nachfolgende Beispiel an.
// 1. Klassendefinitionen
class CAny
{
Another *pAnother;
// ...weitere Member
};
// 2. Klassendefinition
class Another
{
CAny *pAny;
// ...weitere Member
};
Hier werden zwei Klassen definiert, wobei jede Klasse einen Zeiger auf die andere Klasse enthält. Da der C++-Compiler den Quellcode in einem Durchlauf übersetzt, wird er die Definition des Zeigers pAnother in der Klasse CAny mit einem Fehler quittieren, da die Klasse Another noch nicht bekannt ist.
Um dieses Problem zu lösen, wird eine sogenannte Vorwärtsdeklaration eingesetzt. Bei der Vorwärtsdeklaration wird die Klasse nicht definiert, d.h. deren Member aufgeführt, sondern nur deklariert. Durch diese Deklaration wird dem Compiler in unserem Beispiel lediglich mitgeteilt, dass Another eine später zu definierende Klasse ist. Und damit kann der Compiler die Klasse CAny richtig instanziieren.
// Vorwaertsdeklaration
class Another;
// 1. Klassendefinitionen
class CAny
{
Another *pAnother;
// ...weitere Member
};
// 2. Klassendefinition
class Another
{
CAny *pAny;
// ...weitere Member
};
Hier nochmals der Hinweis, dass für die Bearbeitung von dynamischen Feldern keine range-for-Schleife verwendet werden kann.
Übungen
dynprop_01:
Erstellen Sie eine Klasse zur Ablage einer beliebigen Anzahl von Zufallszahlen.
Die Anzahl der abzulegenden Zufallszahlen ist bei der Definition des Objekts anzugeben.
Geben Sie die erzeugten Zufallszahlen sowie deren Mittelwert aus.
Werte: 41,67,34,0,69,24,78,58,62,64,
Mittelwert: 49
dynprop_02:
Diese Übung basiert auf der Übung zum Kapitel Konstruktor und Destruktor.
Es ist eine Klasse Stack zu erstellen, die es erlaubt, mittels Push() short-Werte auf einem Stack abzulegen und mittels Pop() die abgelegten Werte wieder auszulesen.
Neu hinzu kommt, dass die Anzahl der maximal abzulegenden Werte nicht fest vorgegeben wird, sondern erst bei der Definition des Stack-Objekts festgelegt wird.
Standardmäßig soll ein Stack für 10 short-Werte angelegt werden.
Legen Sie im Programm einen Stack für 5 short-Werte an, den Sie so lange mit Werten füllen bis er voll ist. Anschließend lesen Sie alle auf dem Stack abgelegten Werte aus und geben diese zur Kontrolle aus.
Werte auf Stack:
41,67,34,0,69,
Und jetzt wieder lesen:
69,0,34,67,41,
dynprop_03:
Es ist eine Klasse für die Bearbeitung einer Tabelle zu entwickeln. Die Anzahl der Reihen und Spalten der Tabelle sind dem Konstruktor zu übergeben.
Die Methode SetCell() soll eine Zelle innerhalb der Tabelle mit einem Wert belegen. Für die Ausgabe der gesamten Tabelle ist eine Methode PrintIt() zu implementieren.
In der main() Funktion ist ein Tabellenobjekt dynamisch anzulegen, wobei die Tabellengröße über globale Konstanten festzulegen ist.
Nach dem Anlegen der Tabelle ist diese durch den Aufruf von SetCell() mit Zufallszahlen zu füllen und dann auszugeben.
Im nächsten Schritt ist die Tabellendiagonale mit dem Wert -1 zu belegen und die gesamte Tabelle nochmals auszugeben.
Tabelleninhalt
==============
41 67 34 0 69
24 78 58 62 64
5 45 81 27 61
91 95 42 27 36
91 4 2 53 92
Tabelleninhalt
==============
-1 67 34 0 69
24 -1 58 62 64
5 45 -1 27 61
91 95 42 -1 36
91 4 2 53 -1
dynprop_04:
Diese Übung ist eine Abwandlung der Übung 4 zu Klassen.
Die Datei swr1_hitparade.csv enthält (fast) alle Titel ab der Position 1000, die in der SWR1-Hitparade seit 1989 vertreten waren.
Die Datei hat folgenden prinzipiellen Aufbau:
Titel;Interpret;1989;1990;...;2025
dreadlock holiday;10cc;419;896;...;0
Eagle;abba;0;0;...;436
Die erste Zeile enthält den Kopf mit einer Beschreibung der Spalten. Die erste Spalte enthält demzufolge den Titel, die zweite den Interpreten und anschließend folgen die Spalten mit den Jahreszahlen.
Die nachfolgenden Zeilen enthalten die Titeln, Interpreten und die Platzierungen in den entsprechenden Jahren. Beachten Sie, dass der letzte Eintrag in einer Zeile nicht mit einem Semikolon abgeschlossen ist.
Ihre Aufgabe ist es, eine beliebige Anzahl von Titeln einzulesen und deren höchste Hitparadenposition sowie das dazugehörige Jahr auszugeben (siehe Ausgabe).
Für die Hitparade ist eine Klasse Chart zu erstellen, deren Konstruktor den Dateinamen der Hitparaden-Datei und die Anzahl der einzulesenden Titel erhält.
Des Weiteren sind zwei Methoden ReadChart() und PrintEntries() zu erstellen, um die Titel einzulesen und auszugeben.
Die Daten der einzelnen Titel (Titel, Interpret und Positionen) sind in einer Hilfsklasse Entry abzulegen.
Da die Anzahl der Hitparadenjahre, und damit die Anzahl der Positionen, nicht bekannt ist, müssen Sie diese aus dem Kopf (1. Zeile) der Hitparaden-Datei ermitteln. Tipp: Die Anzahl der Jahre hängt mit der Anzahl der Semikolons zusammen.
Da sowohl die Anzahl der einzulesenden Titel wie auch die Anzahl der Hitparadenjahre variabel ist, müssen diese Felder dynamisch angelegt werden. Und vergessen Sie auch das Löschen der Felder nicht!
Lesen Sie die ersten 10 Titel ein geben deren Daten wie beschrieben aus.
'25' von fantastischen vier, bester Platz (667:2015)
'79' von lo & leduc, bester Platz (486:2021)
'1973' von james blunt, bester Platz (132:2007)
'1999' von prince, bester Platz (394:1989)
'10. Jun' von bap, bester Platz (686:1990)
'(don't fear) the reaper' von blue oeyster cult, bester Platz (477:2017)
'(i can't help) falling in love' von ub40, bester Platz (236:1994)
'(you want to) make a memory' von bon jovi, bester Platz (529:2007)
'1000 gute gruende' von toten hosen, bester Platz (104:1990)
'12sati' von e.t., bester Platz (881:1994)