Skip to content

Latest commit

 

History

History
113 lines (78 loc) · 6.76 KB

File metadata and controls

113 lines (78 loc) · 6.76 KB

Aufgabe zu dynamischen Daten: Klasse DynamicIntArray

Zurück


Übersicht

Folgende C++–Sprachmittel sollen zum Einsatz kommen:

  • Umgang mit den Operatoren new und delete
  • Realisierung einer Klasse mit dynamischen Daten
  • Handhabung der Regel Rule-of-Three

Beschreibung

Der Datentyp Array steht in C++ für Felder fester Länge. Es gibt keinerlei Möglichkeit, nach dem Erzeugen eines Felds seine Länge zu ändern.

int numbers[10];

Der Wert 10 muss zur Übersetzungszeit bekannt sein.

An dieser Stelle kommt die dynamische Speicherverwaltung ins Spiel: Mit Hilfe der beiden Operatoren new und delete kann man eine Klasse DynamicIntArray realisieren, die im Prinzip dieselbe Funktionalität wie C++-Felder besitzt, nur mit dem Unterschied, dass die Längenangabe sowohl zum Erzeugungszeitpunkt als auch während der Lebenszeit eines DynamicIntArray-Objekts änderbar ist.

Diese Flexibilität wird erreicht, indem die Daten des Felds in einem Speicherbereich auf der Halde (Heap) abgelegt werden. Bei Bedarf, zum Beispiel, wenn der Datenbereich zu klein geworden ist, kann man auf der Halde ein größeres Stück Speicher reservieren.

Implementieren Sie eine Klasse DynamicIntArray, die diese Eigenschaft besitzt. Ein Objekt dieser Klasse sollte wie in Abbildung 1 gezeigt aussehen:

Abbildung 1. Instanzdatenbereich eines DynamicIntArray-Objekts mit dynamisch allokiertem Datenpuffer.

Wir erkennen in Abbildung 1 zwei Instanzvariablen in der Klasse DynamicIntArray: m_data und m_length. m_data enthält die Adresse eines Stück Speichers, das sich auf der Halde befindet und mit dem new-Operator angelegt wurde. Die Länge dieses Speicherbereichs wird in der zweiten Instanzvariablen m_length festgehalten.

Die Problematik, wenn der dynamisch allokierte Datenpuffer zu klein wird, haben wir bereits angesprochen. Abbildung 2 soll veranschaulichen, wie wir mit einem größeren Datenpuffer größere Anforderungen erfüllen können. Neben einem größeren Stück Speicher, das wieder mit dem new-Operator angelegt wird, ist zu beachten, dass der vorhandene Inhalt des alten Speicherbereichs in den neuen umzukopieren ist.

Abbildung 2. Vergrößerung des Instanzdatenbereichs eines DynamicIntArray-Objekts.

In Abbildung 3 und Abbildung 4 sprechen wir ein letztes Problem in der Realisierung der DynamicIntArray-Klasse an: Die Wertzuweisung zweier DynamicIntArray-Objekte. In einem ersten Ansatz könnte man geneigt sein zu denken, dass diese einfach mit dem Kopieren der beteiligten Instanzvariablen umzusetzen ist. Abbildung 3 versucht darzustellen, dass dies nicht zu einer Realisierung führt, die man als korrekt ansehen kann: Die beiden in Abbildung 3 dargestellten DynamicIntArray-Objekt haben einen gemeinsamen Datenbereich auf Grund des kopierten Zeigers. Dies ist nicht das, was man sich unter einer echten Kopie vorstellt.

Abbildung 3. Falscher Ansatz beim Kopieren eines DynamicIntArray-Objekts.

Abbildung 4 veranschaulicht, wie hier korrekt vorzugehen ist: Eine Kopie eines DynamicIntArray-Objekts muss einen neuen, separaten Datenbereich erhalten:

Abbildung 4. Korrekter Ansatz beim Kopieren eines DynamicIntArray-Objekts.

Eine mögliche Schnittstelle der Klasse DynamicIntArray könnte so aussehen:

Element Schnittstelle und Beschreibung
Standard-Konstruktor DynamicIntArray();
Belegt die Instanzvariablen mit datentypspezifischen Null-Werten.
Benutzerdefinierter Konstruktor DynamicIntArray(size_t size);
Initialisiert ein DynamicIntArray-Objekt mit einem Datenpuffer der Länge size.
Benutzerdefinierter Konstruktor DynamicIntArray(int* values, size_t count);
Initialisiert ein DynamicIntArray-Objekt mit den Werten eines Arrays values der Länge count.
getter size() size_t size() const;
Liefert die aktuelle Länge des Datenpuffers zurück.
at int& at (size_t i);
Zugriff auf ein Element an der Stelle i. Bei ungültigem Index wird eine Ausnahme geworfen.
Operator [] int& operator[] (size_t i);
Wie Methode at, nur ohne Gültigkeitsüberprüfung des Index.
fill void fill(int value);
Belegt alle Elemente des Datenpuffers mit dem Wert value.
resize void resize(size_t newSize);
Ändert die Länge des internen Datenpuffers. Die vorhandenen Daten im Puffer sollen dabei – soweit möglich – erhalten bleiben, sprich: Ist die neue Länge kürzer im Vergleich zur aktuellen Länge, spielen die Daten im oberen Teil des alten Puffers keine Rolle mehr. Ist die neue Länge größer, ist der aktuelle Puffer komplett umzukopieren und die zusätzlichen Elemente im oberen Bereich sind mit 0 vorzubelegen.
shrinkToFit void shrinkToFit();
Sollte auf Grund eines oder mehrerer resize-Aufrufe der Datenpuffer größer als erforderlich sein, wird ein neuer Datenpuffer mit exakt passender Länge angelegt. Natürlich sind die vorhandenen Elemente des alten Puffers umzukopieren.
minimum int minimum() const;
Liefert das minimale Element im Datenpuffer zurück.
maximum int maximum() const;
Liefert das maximale Element im Datenpuffer zurück.
indexOf int indexOf(int value) const;
Liefert die Position des gesuchten Elements value im Datenpuffer zurück. Ist das Element nicht vorhanden, wird -1 zurückgegeben.
contains bool contains(int value) const;
Liefert true oder false zurück, je nach dem, ob das Element value vorhanden ist oder nicht.
release void release();
Gibt den dynamisch allokierten Speicher frei.
print void print();
Gibt alle Elemente des Datenpuffers in der Konsole aus.
bool operator== friend bool operator== (const DynamicIntArray& left, DynamicIntArray right);
Vergleicht zwei DynamicIntArray-Objekte auf Gleichheit.
bool operator!= friend bool operator!= (const DynamicIntArray& left, DynamicIntArray right);
Vergleicht zwei DynamicIntArray-Objekte auf Ungleichheit.

Tabelle 1: Schnittstelle der Klasse DynamicIntArray.

Beachten Sie, dass neben den in Tabelle 1 aufgeführten Methoden auch noch das Regelwerk der „Rule of Three” vorhanden ist.


Quellcode der Lösungen:

DynamicIntArray.h
DynamicIntArray.cpp
DynamicIntArray_Main.cpp


Zurück