Zeiger in C++
Bedeutung des Wortes „Zeiger“
Der Begriff „Zeiger“ wird in der C++ -Norm nicht explizit definiert. In „The C programming language “ von Kernighan und Ritchie findet sich aber die folgende Definition.
- The C programming language - Brian Kernighan (1988), Chapter 5
- A pointer is a variable that contains the address of a variable.
- Aussprachehinweis
- Kernighan ˈkɛrnɪhæn (ohne „g“!)
Jedoch wird in jenem Buch der Ausdruck “pointer ” dann doch auch für einen Adreßwert verwendet, der nicht in einer Variablen enthalten ist. In Ermangelung einer offiziellen Definition können wir festhalten, daß der Begriff „Zeiger“ praktisch als Synonym zu „Adresse“ gebraucht wird, entweder für einen Adreßwert oder auch für eine Adreßvariable.
Wenn A die Adresse eines Objektes O ist, dann sagt man auch, daß A auf das Objekt O zeige. Genauso auch, wenn A eine Variable ist, welche die Adresse eines Objektes O enthält. Wir nennen das Objekt O dann auch das Zielobjekt oder den Referenten des Zeigers A.
Bei Zeigern kann man auch den Typ des Objektes angeben, dessen Adresse der Zeiger ist oder enthält. Ein Zeiger auf ein Objekt des Typs T wird also auch als „T -Zeiger“ bezeichnet. In dem obenstehenden Programm ist also »p« ein „int-Zeiger“.
Wenn »p« ein Zeiger auf ein Objekt ist, dann ist »*p« das Objekt, auf das dieser Zeiger zeigt.
Beispielprogramm zu Zeigern
Das folgende Beispielprogramm definiert einen Zeiger »p«, der auf das Objekt »*p« (gleich »i«) zeigt.
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto i { 27 };
auto p { &i };
::std::cout << *p << "\n"s; }Protokoll
27
»p« ist der Name eines Objekts, welches die Adresse eines int-Objekts enthalten kann und dann auch enthält. »p« ist der Name eines int-Zeigers (»p« ist ein int-Zeiger).
Bildliche Darstellung von Zeigern
Zeiger werden bildlich dargestellt, indem eine bildliche Darstellung des Zeigers durch einen Pfeil mit einer bildlichen Darstellung seines Zielobjektes verbunden wird. Dabei zeigt jener Pfeil vom Zeiger zu seinem Zielobjekt.
- Der int-Zeiger »p« zeigt auf das Objekt »i«
p -------------> i
Änderung des Referenten
In dem folgenden Programm steht »*p« für den Referenten des Zeigers »p«. Daher ändert die Zuweisung »*p = 40;« den Wert von »i«.
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto i { 27 };
auto p { &i };
*p = 40;
::std::cout << i << "\n"s; }Protokoll
40
Aliaseffekte
Wir haben schon in dem vorigen Beispiel einen Aliaseffekt kennengelernt: Dort war »*p« nur ein anderer Ausdruck für »i«.
Das folgende Programm zeigt einen weiteren Aliaseffekt. Zwei Zeiger »p« und »q« werden so definiert, daß »*p« dieselbe Bedeutung hat wie »*q«, nämlich »i«. Dann ändert eine Zuweisung an das Objekt »*p« auch das Objekt »*q« beziehungsweise »i«, und beim Lesen aus dem Objekt »*q« wird auch aus dem Objekt »*q« beziehungsweise »i« gelesen (da alle diese Ausdrücke dasselbe Objekt bezeichnen).
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto i { 27 };
auto p { &i };
auto q { &i };
*p = 40; ::std::cout << i << "\n"s;
*q = 20; ::std::cout << i << "\n"s;
i = 10;
::std::cout << *p << "\n"s;
::std::cout << *q << "\n"s; }Protokoll
40
20
10
10
Zuweisungen an Zeiger
Der Wert eines Zeigers kann in Laufe der Zeit auch durch Zuweisungen verändert werden. Im folgenden Beispielprogramm zeigt der Zeiger »p« zunächst auf das Objekt »i«, so daß »*p« synonym zu »i« ist, und dann auf das Objekt »k«, so daß dann »*p« synonym zu »k« ist.
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto i { 27 };
auto k { 40 };
auto p { &i }; ::std::cout << *p << "\n"s;
p = &k; ::std::cout << *p << "\n"s; }Protokoll
27
40
Zuweisungen an Zeiger (1)
Wir sehen unten »p = q« – eine Zuweisung von einem Zeiger zu einem anderen Zeiger – und »p = &i« – eine Zuweisung eines Wertes des Adressoperators zu einem Zeiger.
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto i { 27 };
auto j { 22 };
auto p { &i };
auto q { &j };
*p = 40; ::std::cout << i << "\n"s;
p = q; *p = 20; ::std::cout << j << "\n"s;
p = &i; *p = 10; ::std::cout << i << "\n"s; }Protokoll
40
20
10
Der Zeiger »p« zeigt zunächst auf das Objekt »i«, dann auf das Objekt »j« und schließlich wieder auf das Objekt »i«.
Hängende Zeiger
Im folgenden Programm wird eine Adresse eines Objektes abgespeichert und das Objekt dann vernichtet (am Ende der vorletzten Zeile).
Danach darf der Zeiger nicht mehr verwendet werden. Der Zeiger „hängt“, er zeigt nicht mehr auf ein Objekt. Der Zugriff in der letzten Zeile hat also undefiniertes Verhalten.
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ int * p;
{ auto i { 27 }; p = &i; }
::std::cout << *p << "\n"s; }- Der Zeiger »p« „hängt“ (oder „baumelt“)
p ----.
;
|
v
Übungsfragen
? Übungsfrage
Sagen Sie die Ausgabe des folgenden Programm voraus, ohne das Programm zu starten!
main.cpp
#include <iostream>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ auto x = 10 /* x wird auf 10 gesetzt */;
auto y = 2 /* y wird auf 2 gesetzt */;
auto p = &y /* Nun enthaelt p die Adresse von y */;
auto z = x/*p /* z erhaelt das Divisionsergebnis */;
::std::cout << z << "\n"s /* z wird schließlich ausgegeben */; }