Dr. Erhard Henkes, Stand: 03.11.2003

Wie funktionieren in C++ Arrays und Pointer?

Arrays

Stellen Sie sich ein Array zunächst als eine Straße mit Häusern nur auf einer Seite vor. In jedem Haus wohnt eine Variable (z.B. Zeichen oder Zahl). Man kann ein Array auch als aneinander gereihte Kästchen sehen, die man mit Daten füllen kann. Nachfolgend finden Sie ein Array mit zehn Kästchen, von denen fünf mit den Buchstaben des Wortes HALLO belegt sind:
 
 H   A   L   L   O  \0

[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]

Die ersten fünf Kästchen (Zählweise: 0 bis 4) sind klar, aber wer bewohnt denn da das sechste (Index: 5) Kästchen? Dieses Zeichen, nämlich die binäre Null (ASCII-Code 0), beendet in C/C++ einen klassischen C-String. Ebenso könnten z.B. auch Zahlen bzw. Zahlen und Buchstaben im Array stehen. Die "Bewohner" der "Kästchen" nennt man auch die Elemente des Arrays. Arrays bezeichnet man auch als Felder oder Vektoren (eindimensional) bzw. Tabellen (mehrdimensional).

Ein Array ist also ein Feld mit einer festgelegten Anzahl von Elementen des gleichen Typs (bool, char, int, float, double etc.). Der Typ der Elemente und damit der Speicherbedarf pro Element wird bei der Deklaration, z.B. char eingabe[80], festgelegt. In der eckigen Klammer steht die Gesamtzahl der Elemente.


Wichtig:
Die Zählung der Elemente eines Arrays in C++ beginnt nicht mit [1], sondern mit [0].
Das erste Element von achtzig ist hier z.B. eingabe[0] und das letzte Element eingabe[79].

Beispiel: Bei der Programmierung von Schleifen durchläuft man 80 Elemente, indem man den Schleifenzähler i nicht von 1 bis 80, sondern von 0 bis 79 zählt.

In den nachfolgenden kleinen Programmen lesen wir mittels der Funktion cin einen Zeichenstring ein. Diese Strings bestehen aus einzelnen Zeichen. Bei Worten sind dies Buchstaben, die als Elemente in das Array eingabe[80] eingehen. Dieses Array umfasst gemäß Deklaration Speicherplatz für 81 Elemente des Typs char. Wir werden den Umgang mit den Strings, die in dem Array gespeichert sind, nun trainieren, damit die Wirkung von Adressen und Zeigern klarer wird.

Als Beispiel benutzen wir das Wort "Jahrhundert". Der C++-Stream cin liest den String ein und speichert ihn im Array eingabe [80].
Im Speicher befinden sich daraufhin folgende Daten des Typs char:

|J|a|h|r|h|u|n|d|e|r|t|\0|

Der String wird durch "\0" abgeschlossen, das heißt wir können im Array eingabe[80] mit seinen insgesamt 80 Elementen des Typs char maximal 79 Zeichen (character) gefolgt von der binären Null ablegen. Die einzelnen Elemente des Strings (also die einzelnen Zeichen) können direkt über den Namen eingabe[i] angesprochen werden. In eingabe[0] findet sich z.B. das Zeichen 'J'. Hier werden gezielt Hochkommas benutzt, da Anführungszeichen ("...") für Zeichenketten (Strings) eingesetzt werden, und diese beinhalten an ihrem Ende "\0".

Jedes Element hat im Array eine Nummer, z.B. die [0] für 'J'. Die Variable eingabe[0] hat jedoch auch eine Adresse im Speicher des Computers. Diese Speicheradresse des "Kästchens" eingabe[0] findet man über &eingabe[0]. Das vorangestellte & (Adress-Operator) vor dem Element des Arrays bewirkt, dass wir uns nicht auf den Inhalt, sondern auf die Speicheradresse des "Kästchens" beziehen. Zusätzlich erlaubt C++ hier eine gleichwertige Schreibweise, die jedoch am Anfang eher zur Verunsicherung beiträgt. Durch Weglassen der eckigen Klammern wird der Name des (eindimensionalen) Arrays zur Anfangsadresse:

&eingabe[0] ist die Speicheradresse von eingabe[0] und kann äquivalent als eingabe geschrieben werden.

Zusätzlich bringen wir an dieser Stelle noch den Inhalts-Operator * (auch indirection-Operator oder Dereferenzierungs-Operator genannt),
der uns den Inhalt einer Adresse übergibt:

*&eingabe[0] ist der Inhalt der Adresse und daher gleichwertig mit eingabe[0].

Jetzt betrachten wir dies in der Programmierpraxis. Das nachstehende Programm (array001) kann uns helfen, den Aufbau von Arrays zu verstehen. Wir geben z.B. den Zeichenstring "Jahrhundert" in eingabe[80] ein und belegen damit inclusive \0 die ersten 12 Plätze also die Elemente 0 ... 11 des Arrays. Die for-Schleife gibt nun beginnend (i=0) mit eingabe[0] nacheinander die einzelnen Elemente eingabe[i] aus.
Die Bedingung eingabe[i]!='\0' beendet die for-Schleife, sobald das Zeichen '\0' gefunden wird.
'\0' wird nicht mehr ausgegeben.
 
//Programm array001.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  char eingabe[80];
  cin >> eingabe;
  for ( i=0; eingabe[i] != '\0'; i++ )  // != bedeutet ungleich
  cout << eingabe[i];
}

Jetzt ändern wir eingabe[i] in *&eingabe[i] ab, um die Wirkung von "Inhalt der Adresse ..." zu überprüfen.
 

//Programm array002.cpp

...

cout << *&eingabe[i];  // identisch mit eingabe[i]

...
 

Bei beiden Programmen erhalten wir folgende Bildschirmausgabe:

Jahrhundert (eingegebener String)
Jahrhundert (ausgegebener String)

Sie haben damit Grundfunktionen kennen gelernt, um einen String in ein Array einzulesen und aus diesem wieder vollständig auszulesen.

Ändern Sie das Programm jetzt wie folgt ab:
 
//Programm array003.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  char eingabe[80];
  cin >> eingabe;
  for ( i=0; eingabe[i] != '\0'; i++ )
  cout << *eingabe;  
}

Jahrhundert
JJJJJJJJJJJ

Was bedeutet *eingabe noch einmal genau? eingabe war identisch mit &eingabe[0].
Damit wird *eingabe zu *&eingabe[0]. Der Inhalt an der Speicheradresse des nullten Elements ist das nullte Element und damit 'J'.
Dieses Element wird im Programmablauf solange ausgegeben, bis die for-Schleife auf das String-Ende '\0' stößt.

Fassen wir kurz zusammen, was wir bisher mittels cout in der for-Schleife ausgegeben haben:
 
eingabe[i]  Elemente des Arrays
 
*&eingabe[i]   Inhalte der Adressen der Elemente des Arrays 
( = Elemente des Arrays )
*eingabe   Inhalt der Startadresse des Arrays 
( = nulltes Element des Arrays )

Jetzt testen wir den Adress-Operator ohne Aufhebung durch den Inhalts-Operator:
 
// Programm array004.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  char eingabe[80];
  cin >> eingabe;
  for ( i=0; eingabe[i] != '\0'; i++ )
  cout << &eingabe[i];
}

Jahrhundert
Jahrhundertahrhunderthrhundertrhunderthundertundertndertdertertrtt

Ja, das sieht ja lustig aus! Eigentlich hatten wir unter Benutzung des Adress-Operators erwartet,
dass die Speicheradressen der Elemente des Arrays nacheinander ausgegeben werden.
Stattdessen erhält man den gesamten String ab der übergebenen Adresse.
Interessant, aber zunächst unlogisch. Sie vermuten, dass dies mit einer Sonderbehandlung von
Strings durch C++ zusammenhängt? Es war ja bereits merkwürdig, dass wir mit cin unter Angabe der Startadresse,
also eingabe (gleichbedeutend mit &eingabe[0]) den gesamten String übernehmen konnten.

Um diesen Zusammenhang vollständig zu verstehen, wiederholen wir das Ganze in gleicher Form mit einem Integer-Array.
 
// Programm array005.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  int eingabe[80];
  cin >> eingabe;
  for ( i=0; eingabe[i] != '\0'; i++ )
  cout << &eingabe[i]; 
}
 

Hier meldet der Compiler bei cin>>eingabe eine
"Illegal structur operation" oder
(MSVC++) "error C2679: Binaerer Operator '>>' :
Kein Operator definiert, der einen rechtsseitigen Operator vom Typ 'int [80]' akzeptiert (oder keine geeignete Konvertierung moeglich)."

Da wir wissen, dass eingabe die Kurzform für &eingabe[0] ist, tauschen wir dies aus und erhalten:

// Programm array006.cpp

...

cin >> &eingabe[0];

...

Auch hier meldet der Compiler an der Stelle cin>>eingabe eine "Illegal structur operation".
Dies ist beruhigend, da wir ja nur den gleichwertigen Programm-Code angewandt haben.
Nun wird es klarer: Die Fähigkeit von cin (oder scanf in C), einen String unter Angabe
einer Adresse der Reihe nach als Character-Variablen in ein Array des Typs char einzulesen,
ist etwas besonderes, aber leider nicht von glasklarer Logik geprägt.

Da wir jetzt endlich Adressen sehen möchten,
streichen wir den Adress-Operator und geben separat die ersten drei Elemente des Arrays ein:
 
// Programm array007.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  int eingabe[80];
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  for ( i=0; i<3; i++ )
  cout << "\n" << &eingabe[i] <<"\t"<< eingabe[i]; 
}

Wenn Sie jetzt z.B. drei Integer-Zahlen eingeben, dann werden links die Speicheradressen und
rechts daneben die zuvor eingegebenen Zahlen auf dem Bildschirm dargestellt
(die Speicheradressen können bei Ihnen natürlich andere sein):

0x0066FCB4       333
0x0066FCB8       444
0x0066FCBC       555

Der Speicherbedarf für int beträgt bei modernen Compilern 4 Byte (32 bit), bei älteren Compilern 2 Byte (16 bit).

Beachten Sie bitte, dass wir einiges verändert haben, um ein vernünftiges Programm zu erhalten.
Insbesondere haben wir die Zahlen einzeln nacheinander abgefragt (kein String),
und der Abbruch-Trick unter Prüfung des Elementes auf '\0' in der for-Schleife ist hier sinnlos.

Wir sind bewusst den Weg vom String im Character-Array zu Zahlen im Integer-Array gegangen, damit deutlich wird,
dass Besonderheiten bei der String-Ein- und -Ausgabe in Verbindung mit & und *& bestehen.
Leider gehen diese bezüglich des Adress-Operators auf Kosten der Logik.

Zunächst verbleiben wir in der Welt der Zahlen und experimentieren mit dem Inhalts-Operator,
indem wir eingabe[i] durch *&eingabe[i] ersetzen. Zusätzlich geben wir aus Neugier einfach drei weitere
Speicherplätze des Arrays aus, die wir nicht selbst vorher mit Zahlen beschicken:
 
// Programm array008.cpp

#include <iostream>
using namespace std;

int main()
{
  short i;
  short eingabe[80];
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  for ( i=0; i<6; i++ )
  cout << "\n" << &eingabe[i] << "\t" << *&eingabe[i];
}

Hier funktioniert der Adress-Operator wie erwartet. Er gibt die Speicheradresse der Elemente eingabe[i] aus.
Insbesondere sehen wir an den Abständen zwischen den Hexadezimal-Adressen, dass eine short-Integer-Variable 2 Bytes Speicherplatz benötigt.

Jetzt steigen wir um auf float-Variablen und sehen, dass solcheVariablen 4 Bytes belegen. Zusätzlich prüfen wir noch einmal bezüglich
*&eingabe[i] und eingabe[i] auf Übereinstimmung. Geben Sie bitte Zahlen mit mehr als 6 Stellen nach dem Komma ein,
um die deutlich höhere Genauigkeit von double im Vergleich zu float zu erkennen:
 
// Programm array009.cpp

#include <iostream>
using namespace std;

int main()
{
  short i;
  float eingabe[80];
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  for ( i=0; i<6; i++ )
  cout << "\n" << &eingabe[i] << "\t" << *&eingabe[i] << "\t" <<eingabe[i];  
}

Nehmen Sie sofort die Gelegenheit wahr und testen auch double (8 Byte) auf Speicherplatzbedarf und Genauigkeit.

Nachdem wir gesehen haben, dass C++ im Zahlenbereich bezüglich des Adress-Operators wie erwartet reagiert,
kehren wir zu den Strings zurück. Hier besteht nach wie vor die Aufgabe, Speicheradressen einzelner Elemente eines Strings
am Bildschirm auszugeben. Wir gehen von Programm array004 aus und wandeln die Adresse &eingabe[i] vor der Ausgabe
in einen Zahlenwert des Typs long um:
 
//Programm array010

#include <iostream>
using namespace std;

int main()
{
  int i;
  char eingabe[80];
  cin >> eingabe;
  for ( i=0; eingabe[i] != '\0'; i++ )
  cout << "\n" << long (&eingabe[i]); 
}

Wie man sieht, haben wir jetzt das Ziel erreicht, die Speicheradressen der einzelnen Zeichen auszugeben. Als nächstes ändern Sie bitte long in double ab. Der Compiler meldet jetzt z.B.: "Cannot cast from 'char*' to 'double'." Um sicher zu gehen, dass es sich hier um eine Besonderheit bei Strings handelt, kehren wir zu Programm array008 zurück und wandeln auch hier die Adressangabe in das Long-Integer-Format um (Programm array011). Die Ausgabe der Speicheradressen erfolgt wie erwartet im Dezimalzahlen-Format.

Jetzt ändern wir auf double ab und erhalten z.B. eine ähnliche Compiler-Meldung: "Cannot cast from 'int*' to 'double'."
 
// Programm array011.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  int eingabe[80];
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  for ( i=0; i<6; i++ )
  cout << "\n" << long (&eingabe[i]) << "\t" << *&eingabe[i];
}

Gehen Sie Compiler-Meldungen immer auf den Grund, denn aus Fehlern lernt man. Der Compiler hat uns mitgeteilt,
dass der Ausdruck &eingabe[i] bei Strings vom Typ char* und bei Zahlen vom Typ int* ist.

Nun werden wir erforschen, was char* und int* beschreibt und warum es bei der Ausgabe mit cout verschieden reagiert?

Nach den obigen Versuchen mit Arrays und Strings kommen wir folgerichtig zu den Zeigern. Arrays, Strings und Zeiger (Pointer) sind in C++ eng miteinander verknüpft. Daher betreten wir jetzt dieses interessante Gebiet der Zeiger, das für viele leider abschreckend wirkt.

Zuvor fassen wir kurz zusammen:
Bisher experimentierten wir mit einzelnen Array-Elementen eingabe[i], den zugehörigen Speicheradressen &eingabe[i]
(Besonderheit: eingabe ist &eingabe[0]) und den Inhalten an den Speicheradressen *&eingabe[i].
Wir fanden, dass man bei Elementen in Strings die Ausgabe der Speicheradressen durch Umwandlung in eine Integer-Variable erreicht. Bei Ausgabe der "nackten" Adresse wurde nicht die Adresse, sondern der restliche String ab dieser Adresse ausgegeben (interessant, aber zunächst nicht beabsichtigt und evtl. noch nicht völlig verstanden).


Was ist ein Zeiger?

Ein Zeiger ist eine Variable, die die Speicheradresse einer anderen Variable beinhaltet.

Um Zeigervariablen von anderen Variablen schnell unterscheiden zu können, ist es üblich,
die Namen solcher Zeiger zur Unterscheidung mit einem kleinen p (pointer) zu beginnen.

C++ schafft bei der Deklaration der Zeiger leider Verwirrung: Als Kennzeichen einer Zeigervariable wird * vor dem Namen eingesetzt.
Dieses Zeichen wird bereits als Inhalts-Operator verwendet. Zusätzlich ist * auch das Zeichen für die Multiplikation.
Hier hilft nur Übung und Gewöhnung.

Als Typ des Zeigers deklariert man den Typ der Array-Elemente, auf die er zeigt.
Nachfolgend finden Sie ein Beispiel (ausgehend von Programm array008),
in dem Speicheradressen in der for-Schleife einem Zeiger zugewiesen werden:
 
// Programm array012.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  int eingabe[80];
  int *p_eingabe;   // Deklaration des Zeigers mittels *...
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  cout<< "Adresse" << "\t" << "Zeiger" << "\t" << "Array-Element";
  for ( i=0; i<6; i++ )
  { p_eingabe = &eingabe[i];
    cout << "\n" << &eingabe[i] << "\t" << p_eingabe << "\t" << *&eingabe[i];
  } 
}

Das nächste Programm demonstriert uns die Besonderheit der Pointerarithmetik:
Erhöht man den Zeigerinhalt um 1, z.B. mit p_eingabe = p_eingabe + 1 oder mit p_eingabe++,
dann wird die Speicheradresse nicht um eins erhöht, sondern um die Zahl, die dem Variablen-Speicherbedarf entspricht,
also hier bei int um 4 Byte. Man rückt den Zeiger in einem Array sozusagen von Wert zu Wert weiter.
Solche Fähigkeiten sind für den Gebrauch in Schleifen nützlich.

Wir werden dies im nächsten Programm sofort austesten:
 
// Programm array013.cpp

#include <iostream>
using namespace std;

int main()
{
  short i;
  short eingabe[80];
  short *p_eingabe;
  p_eingabe = eingabe;
  cout<< "Erste Zahl: ";
  cin >> eingabe[0];
  cout<< "Zweite Zahl: ";
  cin >> eingabe[1];
  cout<< "Dritte Zahl: ";
  cin >> eingabe[2];
  cout<< "Zeiger" << "\t" << "Array-Element";
  for ( i=0; i<3; i++, p_eingabe++ )
  cout << "\n" << p_eingabe << "\t" << *p_eingabe; 
}

Im vorstehenden Programm arbeitet p_eingabe stellvertretend für das bisherige &eingabe[i].
Durch Aufnahme von p_eingabe++ in die for-Anweisung wird der Zeiger jeweils um einen Wert
("Variablenbreite", z.B. 2 Bytes bei short oder 4 Bytes bei float etc.) weitergerückt.
*p_eingabe gibt uns den Inhalt der Variable, die an dieser Speicherstelle beginnt.
Als Zeiger beinhaltet p_eingabe diese Adresse.
Experimentieren Sie an dieser Stelle mit der Pointerarithmetik,
indem Sie short durch float etc. ersetzen:

// Programm array014.cpp

...

float eingabe[80];
float *p_eingabe;

...

Da wir den Zusammenhang zwischen Zeiger und Adresse nun kennen,
probieren wir dies sofort an unserem interessanten Beispiel mit einem String aus:
 
// Programm array015.cpp

#include <iostream>
using namespace std;

int main()
{
  int i;
  char eingabe[80];
  char *p_eingabe;
  p_eingabe = eingabe; //Zeiger auf Startadresse setzen
  cin >> eingabe;
  for (i=0;i<=80;i++,p_eingabe++)
  cout << *p_eingabe; 
}

Wir stellen fest, dass *p_eingabe analog zu *&eingabe[i] arbeitet.
Was passiert, wenn wir das Zeichen * weglassen und damit den Zeiger direkt ausgeben?

// Programm array016.cpp

...

cout << p_eingabe;

...

Dies ergibt das von den Strings bekannte Bild:
p_eingabe+i funktioniert hier also analog zu &eingabe[i] und gibt den gesamten String beginnend ab der im Zeiger befindlichen Adresse aus.

In unserem Experimentierfeld mit Arrays, Strings, Adressen, Inhalten und Zeigern halten wir kurz inne und betrachten im Überblick
folgende Gleichwertigkeiten von Ausdrücken bezüglich Adressen und Zeigern
(p_eingabe soll hierbei durch Zuweisung die Startadresse des Arrays enthalten):
 
&eingabe[0]
eingabe
p_eingabe
&p_eingabe[0]

Startadresse = Adresse des Elementes 0
 

&eingabe[i]
eingabe+i
p_eingabe+i
&p_eingabe[i]

Startadresse = Adresse des Elementes i
 

Durch die Vielfalt sieht das Ganze verwirrend aus. Nehmen wir dies Schritt für Schritt unter die Lupe, um die Regeln zu verstehen:

Betrachten wir zunächst die Äquivalenz folgender Paarungen von Ausdrücken:

eingabe + i <---> p_eingabe + i

&eingabe[i] <---> &p_eingabe[i]

Die erste Zeile zeigt die Äquivalenz zwischen Adresse und Zeiger, die wir bereits verwendet haben.
Wichtig ist diesbezüglich die spezielle Pointerarithmetik: Adressen oder Zeiger werden entsprechend der "Variablenbreite" im Speicher bewegt. Der Ausdruck p_eingabe+5 bedeutet somit, dass der Zeiger von der Adresse des "ersten Kästchens" (Element[0]) auf die Adresse des "sechsten Kästchens" (Element[5]) weiterrückt. Gewöhnungsbedürftig ist, dass der Name eines (eindimensionalen) Arrays gleichzeitig als Adresse von Element[0] verwendet wird. Durch Wegfall des Adress-Operators oder des Hinweises auf einen Zeiger (nur gegeben, wenn Sie p... im Namen verwenden) erscheint der einfache Array-Name wie eine gewöhnliche Variable.

Dass eingabe identisch mit &eingabe[0] ist, wissen Sie bereits.
Die Verallgemeinerung lautet:

Allgemein gilt:

Array + i  <---> &Array[i]

Infolge der obigen Regel gilt unter Verwendung des Inhalts-Operators bzw. unter Weglassen des Adress-Operators:

Allgemein gilt:

*(Array+i) <---> Array[i]

Nur der Vollständigkeit halber sei hier angemerkt, dass über die Kette 

Array[i] <---> *(Array+i) <---> *(i+Array) <---> i[Array]

folglich auch 
Array[i]  und  i[Array]  gleichwertig sind, wenn dies beim direkten Betrachten auch nicht verständlich ist.

Der Compiler schluckt dies kommentarlos, während beim Betrachter
teilweise ernsthafte Verständnisprobleme entstehen.

Auf der Ebene der Array-Elemente, am Beispiel eingabe[80], gilt daher folgende Gleichwertigkeit der Begriffe:
 
eingabe[0]
*(eingabe)
*(p_eingabe)
p_eingabe[0]

Startelement = Element 0
 

eingabe[i]
*(eingabe+i)
*(p_eingabe+i)
p_eingabe[i]

Startelement = Element i
 

Nun wollen wir das Ganze in einem Programm vergleichend testen. Das nachfolgende Programm definiert ein double-Array mit sechs Zahlen und ein char-Array mit dem String "Jahr". Anschließend werden sämtliche Begriffe angewendet, um die jeweiligen Wirkungen und die gleichartigen Verhaltensweisen zu überprüfen:
 
 
// Programm array017.cpp

#include <iostream>
using namespace std;

int main()
{
  short i;
  long j;
  double zahl[6];
  double *p_zahl;
  char eingabe[]="Jahr";
  char *p_eingabe;
  p_zahl = zahl; //Zeiger auf Startadresse setzen
  p_eingabe = eingabe; //Zeiger auf Startadresse setzen

  zahl[0]= 333.333;
  zahl[1]= 45564.66666;
  zahl[2]= 0.0;
  zahl[3]= 545.347564877;
  zahl[4]= -999.9999999;
  zahl[5]= -111.000000001;

  for ( i=0; i<=5; i++ )
  {
      cout << long(&zahl[i])   << "\t" << zahl[i]     << "\n";
      cout << long(zahl+i)     << "\t" << *(zahl+i)   << "\n";
      cout << long(p_zahl+i)   << "\t" << *(p_zahl+i) << "\n";
      cout << long(&p_zahl[i]) << "\t" << p_zahl[i]   << "\n";
  }

  cout << "\1 " << "Kurze Pause" <<" \1";

  for ( j=0; j<80000000; j++ ); // Leere Warteschleife
  

  cout << "\n";

  for ( i=0; i<=3; i++ )
  {
      cout << long(&eingabe[i])   << "\t" << eingabe[i]     << "\n";
      cout << long(eingabe+i)     << "\t" << *(eingabe+i)   << "\n";
      cout << long(p_eingabe+i)   << "\t" << *(p_eingabe+i) << "\n";
      cout << long(&p_eingabe[i]) << "\t" << p_eingabe[i]   << "\n";
  } 
}
 
 

Nachdem das vorstehende Programm die Wirkung der verschiedenen Programmcodes für Array-Elemente und Array-Zeiger
sowohl für Zahlen als auch für einen String gezeigt hat, sollten Sie selbst mit diesem Programm ein wenig experimentieren:

Ändern Sie z.B. die Deklaration des Zahlenarrays von 'double zahl[6];' auf 'double zahl[5]' ab und beobachten Sie, was nach dem Start passiert.
Der Compiler akzeptiert diese zu enge Festlegung (nur fünf anstelle sechs Zahlen) problemlos. Bei verschiedenen PC-Konstellation kann es hier beim Programm-Start zu einem Systemabbruch kommen. Ein manchmal schwierig zu findender Fehler (Kleine Ursache - große Wirkung). Achten Sie bei eigenen Programmen daher immer darauf, dass bei der Deklaration eines Arrays die Zahl der Elemente ausreichend ist, um alle Werte aufzunehmen.

Spielen Sie mit der Warteschleife:
Ändern Sie 'long j;' in 'short j;' ab. Was geschieht? Die Pause hört nicht mehr auf!
Sie haben eine Endlosschleife geschaffen, da man im short-Bereich (-32768 ... 32767) nicht bis auf 80000000 zählen kann!
Die Zahl 80000000 wurde ursprünglich für einen 486er mit 50 MHz entworfen. Sollte ihr Rechner schneller sein, was ich für Sie hoffe,
erhöhen Sie die Zahl (natürlich mit 'long j;'). Sollten Sie 2147483647 überschreiten, so drehen Sie erneut in einer Endlosschleife.

Geben Sie die Adressen der sechs Zahlenwerte im Hexadezimal-Format aus.

Ändern Sie von double auf float ab, und untersuchen Sie die Auswirkungen bei mehr als sechs Nachkommastellen.

Schreiben Sie das Programm so um, dass Sie sechs verschiedene Zahlen selbst eingeben können (for-Schleife / cin).

Im Ausgabe-Teil der Zeiger und Elemente gibt es sehr viele Spielarten. Setzen Sie hier nach Belieben * und & hinzu
und beobachten Sie die Wirkung auf den Compiler oder das Programm.
Sie könnten auch Typ-Umwandlungen vornehmen
(z.B. char ---> int, String als ASCII-Code ausgeben).

Viel Spaß weiterhin mit Arrays und Zeigern.