Wird geladen...

Beziehung zwischen Arrays und Zeigern

Array-zu-Zeiger-Decay, zusammenhängendes Layout und sichere Zeigerarithmetik.

In C++ sind Arrays und Pointer eng miteinander verbunden. Das liegt daran, dass Arrays im Speicher hintereinander gespeichert werden und der Name eines Arrays tatsächlich die Adresse seines ersten Elements darstellt. Daher ist es möglich, ein Array mithilfe von Zeigerarithmetik zu durchlaufen. In diesem Artikel lernen wir die Speicherstruktur von Arrays, ihre Beziehung zu Zeigern und praktische Anwendungsbeispiele kennen.


1. Array- und Speicheranordnung

Wenn in C++ ein Array definiert wird, werden alle Elemente nacheinander im Speicher abgelegt. Beispiel:


#include <iostream>
using namespace std;

int main() {
    int zahlen[3] = {10, 20, 30};

    cout << "Adresse des 1. Elements: " << &zahlen[0] << endl;
    cout << "Adresse des 2. Elements: " << &zahlen[1] << endl;
    cout << "Adresse des 3. Elements: " << &zahlen[2] << endl;

    return 0;
}

Die ausgegebenen Adressen werden aufeinanderfolgend sein:


Adresse des 1. Elements: 0x61ff0c  
Adresse des 2. Elements: 0x61ff10  
Adresse des 3. Elements: 0x61ff14

Da jedes int 4 Byte Speicherplatz belegt, erhöhen sich die Adressen jeweils um 4.


2. Der Array-Name ist ein Pointer

Der Name des Arrays (zahlen) repräsentiert tatsächlich die Adresse des ersten Elements. Das bedeutet, dass zahlen und &zahlen[0] dieselbe Adresse bezeichnen.


int zahlen[3] = {10, 20, 30};
cout << zahlen << endl;      // Adresse des ersten Elements
cout << &zahlen[0] << endl; // gleiche Adresse

Daher ist es möglich, über ein Array mit Hilfe eines Pointers zu iterieren.


3. Zugriff auf Array-Elemente mit Zeigern

Wenn ein Pointer auf das erste Element eines Arrays zeigt, kann man mithilfe der Zeigerarithmetik auf die anderen Elemente zugreifen.


#include <iostream>
using namespace std;

int main() {
    int zahlen[4] = {5, 10, 15, 20};
    int *p = zahlen; // p = &zahlen[0]

    cout << *p << endl;      // 5
    cout << *(p + 1) << endl; // 10
    cout << *(p + 2) << endl; // 15
    cout << *(p + 3) << endl; // 20
}

Der Ausdruck *(p + i) ist identisch mit zahlen[i]. Dies bildet die Grundlage der Zeigerarithmetik.


4. Zeigerarithmetik

Zeiger bewegen sich im Speicher automatisch um die passende Menge, abhängig von ihrem Datentyp. Beispiel:


int array[3] = {1, 2, 3};
int *ptr = array;

cout << ptr << endl;       // Adresse des ersten Elements
ptr++;
cout << ptr << endl;       // Adresse des nächsten Elements

Der Ausdruck ptr++ verschiebt die Adresse um 4 Byte (int = 4 Byte). Dies wird automatisch anhand des Typs des Zeigers berechnet. Bei double sind es 8 Byte, bei char nur 1 Byte.


5. Array-Schleifen mit Zeigern

Um die Elemente eines Arrays mit Zeigern zu durchlaufen, können Schleifen verwendet werden.


int zahlen[] = {2, 4, 6, 8, 10};
int *p = zahlen;

for (int i = 0; i < 5; i++) {
    cout << *(p + i) << " ";
}

Alternativ kann der Zeiger direkt inkrementiert werden:


for (int *ptr = zahlen; ptr < zahlen + 5; ptr++) {
    cout << *ptr << " ";
}

Beide Methoden liefern dasselbe Ergebnis:


2 4 6 8 10

6. Array-Parameter in Funktionen

Wenn ein Array an eine Funktion übergeben wird, wird tatsächlich die Adresse des Arrays übergeben. Auf diese Weise kann die Funktion das Array direkt ändern.


#include <iostream>
using namespace std;

void Fuellen(int *array, int laenge) {
    for (int i = 0; i < laenge; i++)
        array[i] = (i + 1) * 5;
}

void Drucken(const int *array, int laenge) {
    for (int i = 0; i < laenge; i++)
        cout << array[i] << " ";
}

int main() {
    int zahlen[5];
    Fuellen(zahlen, 5);
    Drucken(zahlen, 5);
}

Da die Funktion Fuellen das Array direkt im Speicher ändert, werden die Werte im Array der main()-Funktion aktualisiert.


7. Mehrdimensionale Arrays und Zeiger

Mehrdimensionale Arrays werden ebenfalls hintereinander im Speicher gespeichert. Allerdings muss die Zeigerarithmetik hier mit etwas mehr Vorsicht angewendet werden.


#include <iostream>
using namespace std;

int main() {
    int matrix[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    cout << *(*(matrix + 0) + 2) << endl; // 3
    cout << *(*(matrix + 1) + 0) << endl; // 4
}

Hier gilt: matrix → ist die Adresse der ersten Zeile. *(matrix + 1) → geht zur zweiten Zeile, *(*(matrix + 1) + 0) → greift auf das erste Element dieser Zeile zu.


8. Zeiger und dynamische Arrays

Im Gegensatz zu Arrays mit fester Größe werden dynamische Arrays mit dem Schlüsselwort new erstellt. Diese Arrays werden im Heap-Speicher abgelegt, und ihre Größe kann zur Laufzeit bestimmt werden.


int *array = new int[5];

for (int i = 0; i < 5; i++)
    array[i] = (i + 1) * 10;

for (int i = 0; i < 5; i++)
    cout << array[i] << " ";

delete[] array; // Speicher freigeben

Dynamische Arrays erfordern manuelle Speicherverwaltung. Speicher, der mit new reserviert wurde, muss nach der Verwendung immer mit delete[] freigegeben werden.


9. Arrays mit const-Zeigern lesen

Wenn eine Funktion ein Array nicht verändern soll, kann der Zeigerparameter als const deklariert werden.


void Ausgeben(const int *array, int länge) {
    for (int i = 0; i < länge; i++)
        cout << array[i] << " ";
}

Diese Methode schützt die Datenintegrität und verhindert, dass das Array versehentlich verändert wird.


10. TL;DR

  • Der Array-Name (zahlen) → ist die Adresse des ersten Elements (&zahlen[0]).
  • *(p + i)array[i] sind gleichwertig.
  • Die Zeigerarithmetik erhöht die Adresse automatisch entsprechend der Datentypgröße.
  • Arrays werden als Adresse an Funktionen übergeben, nicht als Kopie.
  • Mehrdimensionale Arrays können über Zeigerketten angesprochen werden (*(*(matrix + i) + j)).
  • Dynamische Arrays sollten mit new erstellt und mit delete[] freigegeben werden.
  • Alle Beispiele können in Visual Studio 2022 oder mit dem GCC 11+ Compiler ausgeführt werden.

Ähnliche Artikel