C++ Kurs

Operatoren new und delete

Die Themen:

new Operator
delete Operator
Beispiel und Übung

new und delete sind Operatoren (so wie + oder *) und benötigen daher keine zusätzlich einzubindende Header-Datei.

new dient zur Reservierung eines Speicherbereichs und delete dient zur Freigabe des so reservierten Speichers.

new Operator

new für einfache Daten und Felder

new erhält als Operand den Datentyp, für den der Speicher reserviert werden soll. Als Ergebnis liefert new einen Zeiger auf den reservierten Speicherbereich zurück. Dieser zurückgelieferte Zeiger besitzt immer den richtigen Datentyp, so dass keine Typkonvertierung notwendig ist.


// main() Funktion
int main()
{
   long *ptr;           // Zeiger auf long-Datum
   ...
   // Speicher für ein long Datum reservieren
   ptr = new long;
   ...
}

Konnte new aus irgend welchen Gründen nicht den angeforderten Speicher reservieren, so wird eine Exception (Ausnahme) vom Typ bad_alloc ausgelöst und das Programm (bis jetzt) beendet. Was Exceptions sind und wie Sie auf eine solche Exception reagieren können, das erfahren Sie später noch ausführlich.

new kann aber nicht nur Speicher für "einfache" Daten reservieren, sondern auch für Felder. Soll Speicher für einfache Felder (nicht Objektfelder, das folgt gleich noch) reserviert werden, so erfolgt die Angabe der Feldgröße nach dem Datentyp des Feldes innerhalb von eckigen Klammern. Im nachfolgenden Beispiel wird Speicher für ein short-Feld mit 10 Elementen reserviert.


// main() Funktion
int main()
{
   short *ptr;           // Zeiger auf short-Datum
   ...
   // Speicher für ein Feld mit 10 short reservieren
   ptr = new short[10];
   ...
}

Beachten Sie bitte, dass bei der Reservierung von Speicher für Felder, die Feldgröße in eckigen Klammern stehen muss! Wenn Sie die Feldgröße ausversehen in runden Klammern angeben, so meldet Ihnen der Compiler keinen Fehler!

// Es sollte Platz für ein short-Feld mit
// Elementen reserviert werden

short *pArray = new short(10);

Vielmehr wird folgender Vorgang ablaufen: Anstelle eines Feldes wird nur Speicher für eine einfaches Datum des entsprechenden Datentyps reserviert. Und dieses Datum wird dann mit dem Wert initialisiert, der innerhalb der runden Klammer angegeben ist. Sie haben damit sozusagen einen Konstruktor für eine einfaches Datum aufgerufen! Im obigen Beispiel wird also Platz für ein short-Datum reserviert und der reservierte Speicher mit 10 initialisiert.

new für mehrdimensionale Felder

Soll für mehrdimensionale Felder Speicher reserviert werden, so muss dieses in mehreren Schritten erfolgen. Der 'Trick' dabei besteht darin, dass zuerst ein Zeigerfeld (also ein Feld welches nur Zeiger enthält) reserviert wird. Dieses Zeigerfeld repräsentiert die 'Zeilen'. Anschließend werden dann in diesem Zeigerfeld die Speicheradressen für die Spaltenfelder abgelegt. Sehen wir uns diesen Vorgang nun Schritt für Schritt an:

  1. Als erstes wird ein Zeiger auf einen Zeiger des gewünschten Datentyps des Feldes definiert.
  2. Anschließend wird mittels new ein Zeigerfeld mit so vielen Elementen reserviert, wie das Feld Zeilen haben soll (1. Dimension).
  3. Zum Schluss werden in einer Schleife die 'Spaltenfelder' reserviert. Die von new zurückgelieferten Adressen werden dann in dem im 2. Schritt reservierten Zeigerfeld abgelegt.

Der Zugriff auf die einzelnen Elemente des mehrdimensionalen Feldes erfolgt in der üblichen Art und Weise (mehrfache Indizierung).


// Feld mit 3x2 Elementen für die Aufnahme von float-Zahlen reservieren
const int ROW=3;
const int COLUMN=2;
// Schritt 1: Zeiger auf Zeiger definieren
float **ppArray;

// main() Funktion
int main()
{
   // Schritt 2: Zeigerfeld für 3 float-Zeiger reservieren
   ppArray = new float*[ROW];
   // Schritt 3: Spaltenfelder reservieren
   for (int i=0; i<ROW; i++)
      ppArray[i] = new float[COLUMN];
   ...
   // Zugriff dann wie gewohnt
   ppArray[iRow][iCol] = ...;
}

Aber Achtung! Der für das mehrdimensionale Feld reservierte Speicher muss nun nicht mehr zwingend zusammenhängend im Speicher liegen.

Damit genug der zur Reservierung von Speicher. Sehen wir uns jetzt an, wie Sie den reservierten Speicher wieder freigeben wenn Sie ihn nicht mehr benötigen.

delete Operator

delete für einfache Daten und Felder

Die Freigabe des mittels new reservierten Speicherbereichs erfolgt mit dem delete Operator. Wurde Speicher für 'einfache' Daten (keine Felder) reserviert, so erhält delete als Operanden den von new zurückgelieferten Zeiger. Wurde dagegen Speicher für ein Feld reserviert, so muss nach dem delete Operator zunächst eine leere eckige Klammer stehen und erst danach der von new zurückgelieferte Zeiger. Vergessen Sie diese leere eckige Klammer, so meldet Ihnen der Compiler keinen Fehler! Bei Felder von einfachen Datentypen, d.h. keine Objekte, wird der reservierte Speicher auch korrekt freigegeben. Warum bei der Freigabe des Speichers von Objektfeldern diese eckigen Klammern so wichtig sind, das erfahren Sie in der nächsten Lektion über dynamische Objekte. Vergessen Sie daher niemals die eckigen Klammer beim delete von Feldern.


int main()
{
   // Speicher für einen long reservieren und wieder freigeben
   long *pData = new long;
   ...
   delete pData;
   // Speicher für char-Feld reservieren und wieder freigeben
   char *pArray = new char[30];
   ...
   delete [] pArray;
}

delete für mehrdimensionale Felder

Auch hier werden wir uns noch kurz ansehen, wie Sie den vorhin reservierten Speicher für ein 2-dimensionales Feld wieder freigeben. Bei der Freigabe des Speichers müssen Sie quasi die Reihenfolge der Reservierungsanweisungen umdrehen, d.h. Sie müssen zuerst den Speicher für die Spaltenfelder freigeben und dann zum Schluss den Speicher für das Zeigerfeld. Beachten Sie beim delete Operator die leeren eckigen Klammern. Wird haben es hier mit Feldern zu tun!


// Feld mit 3x2 Elementen für die Aufnahme von float-Zahlen reservieren
const int ROW=3;
const int COLUMN=2;
// Schritt 1: Zeiger auf Zeiger definieren
float **ppArray;

// main() Funktion
int main()
{
   // Reservierung des 2-dimimensionalen Feldes wie oben dargestellt
   ...
   // Zuerst Spaltenfelder freigeben
   for (int i=0; i<ROW; i++)
      delete [] ppArray[i];
   // Und Zeigerfeld freigeben
   delete [] ppArray;
}

delete gibt nur den Speicher frei, verändert aber den Inhalt des Zeigers nicht, d.h. der Zeiger enthält weiterhin die bisherige Adresse. Sie dürfen aber nach der Ausführung von delete keine weiteren Zugriffe mehr über diesen Zeiger auf den freigegebenen Speicherbereich durchführen.

Und noch ein Hinweis:

Der C++ Standard erlaubt es, den delete Operator auch mit einem NULL-Zeiger aufzurufen, d.h. der Zeiger enthält den Wert NULL. In diesem Fall führt der delete Operator einfach gar nichts aus.

Beispiel und Übung

Beispiel:

Das Beispiel berechnet aus einer einzugebenden Anzahl von Zufallszahlen den Mittelwert, wobei die Zufallszahlen in einem Feld abgelegt werden. Da die Anzahl der Zufallszahlen nicht fest vorgegeben ist, wird hierfür ein entsprechend großes Feld reserviert. Beachten Sie bei der Freigabe des Speichers für das Feld die eckigen Klammern beim Aufruf des delete Operators.

Aus wie vielen Zufallszahlen soll
der Mittelwert berechnet werden ?100
Der Mittelwert ist: 46.48


// Beispiel zu new und delete

// Zuerst Dateien einbinden

#include <iostream>
#include <cstdlib>

// main() Funktion
int main()
{
   unsigned int noOfValues; // Anzahl der Zufallszahlen
   double sum;              // Summe aller Zufallszahlen
   unsigned int loop;       // Schleifenzähler
   int *pValues;            // Zeiger auf Feld mit Zufallszahlen

   // Anzahl der Zufallszahlen einlesen
   std::cout << "Aus wie vielen Zufallszahlen soll\n";
   std::cout << "der Mittelwert berechnet werden ?";
   std::cin >> noOfValues;

   // Feld für Zufallszahlen reservieren
   pValues = new int[noOfValues];

   // Feld mit Zufallszahlen belegen
   for (loop=0; loop<noOfValues; loop++)
      pValues[loop] = rand() % 100;

   // Nun alle Zahlen im Feld aufsummieren
   sum = 0;
   for (loop=0; loop<noOfValues; loop++)
      sum += (double)pValues[loop];

   // Mittelwert berechnen und ausgeben
   std::cout << "Der Mittelwert ist: " << sum/noOfValues << std::endl;

   // Feld auch wieder löschen
   delete [] pValues;
}

Übung:

Die Datei names.txt enthält eine Liste mit Namen (enthalten auch Leerzeichen!) die einzulesen sind. Die Anzahl der in der Datei enthaltenen Namen ist als erster Eintrag (short-Datentyp) in der Datei mit abgespeichert. Lesen Sie alle Namen in ein entsprechend großes string-Feld ein und geben Sie die Namen zu Kontrolle aus.

Legen Sie dazu zunächst die Datei names.txt mit folgendem Inhalt an:


11
Baisch, Robert
Reusch, Bernhard
Werner, Jutta
Schroff, Rosina
Stauss, Guenther
Mueller, Andrea
Herrmann, Fritz
Baumann, Marion
Maier, Klaus
Rapp, Rose
Neuhaus, Ursula

Baisch, Robert
Reusch, Bernhard
Werner, Jutta
Schroff, Rosina
Stauss, Guenther
Mueller, Andrea
Herrmann, Fritz
Baumann, Marion
Maier, Klaus
Rapp, Rose
Neuhaus, Ursula

Lösung ansehen!