Zeiger, nullptr und Dereferenzieren
Ein Zeiger, englisch pointer, ist ein Wert, der auf ein passendes Objekt beziehungsweise eine Funktion zeigen kann. Ein Objektzeiger kann auch ein Nullzeiger sein: Er bezeichnet dann kein Objekt. Verwende dafür das Schlüsselwort nullptr.
Zeiger, nullptr und Dereferenzieren#
Referenzen, Zeiger und Speicher · Lektion 34 von 60
Voraussetzungen: Speicher, Referenzen. Lernziel: Du greifst kontrolliert über eine Adresse auf ein Objekt zu.
Ein Zeiger, englisch pointer, ist ein Wert, der auf ein passendes Objekt beziehungsweise eine Funktion zeigen kann. Ein Objektzeiger kann auch ein Nullzeiger sein: Er bezeichnet dann kein Objekt. Verwende dafür das Schlüsselwort nullptr.
1#include <iostream>2 3int main() {4 int zahl{42};5 int andereZahl{7};6 int* zeiger = &zahl;7 if (zeiger != nullptr) {8 *zeiger = 50;9 std::cout << zahl << '\n';10 }11 zeiger = &andereZahl;12 std::cout << *zeiger << '\n';13 zeiger = nullptr;14}Die Ausgabe ist 50 und 7. int* bedeutet „Zeiger auf int“. &zahl liefert die Adresse von zahl. *zeiger dereferenziert den Zeiger: Du greifst auf das Zielobjekt zu. Die Zuweisung zeiger = &andereZahl ändert dagegen den gespeicherten Zeigerwert, nicht die alte Zahl.
Schritt für Schritt im Speicher#
Stell dir zahl und andereZahl als zwei beschriftete Speicherplätze vor. Der Zeiger speichert, welchen Platz du gerade meinst. Die Namen in dieser Skizze stehen für Adressen; echte Adresswerte sehen je nach Ausführung anders aus.
1Nach int* zeiger = &zahl:2 3zeiger ───────► zahl [42] andereZahl [7]4 5Nach *zeiger = 50:6 7zeiger ───────► zahl [50] andereZahl [7]8 9Nach zeiger = &andereZahl:10 11 zahl [50] andereZahl [7] ◄────── zeigerAnweisung | Zeiger zeigt danach auf | Wert von zahl | Wert von andereZahl |
|---|---|---|---|
int* zeiger = &zahl; | zahl | 42 | 7 |
*zeiger = 50; | zahl | 50 | 7 |
zeiger = &andereZahl; | andereZahl | 50 | 7 |
zeiger = nullptr; | kein Objekt | 50 | 7 |
zeiger = nullptr zerstört keine der beiden Zahlen. Es verändert nur den Zeiger. Ebenso reserviert int* zeiger = &zahl keinen neuen Speicher für eine weitere Zahl: zahl existiert bereits.
Ein Zeichen, mehrere Bedeutungen#
Das Sternchen in int* p gehört zur Deklaration eines Zeigers. Das Sternchen in *p ist ein Zugriff auf dessen Ziel. In 3 * 4 ist es Multiplikation. Ebenso bedeutet & in int& r Referenz und in &zahl Adressbildung. Entscheidend ist der Kontext.
Schreibe vorzugsweise eine Variable pro Definition. Bei int* a, b; ist nur a ein Zeiger; b ist ein int. Eine klarere Schreibweise vermeidet diese Überraschung.
const und Zeiger#
Schreibweise | Was bleibt unveränderbar? |
|---|---|
const int* p | Das Ziel darf nicht über p verändert werden |
int* const p | Der Zeigerwert darf nach Initialisierung nicht geändert werden |
const int* const p | Beides gilt gleichzeitig |
Ein Zeiger auf konstante Daten bedeutet nicht zwingend, dass das Ziel auch über alle anderen Wege konstant ist. Er begrenzt zunächst diesen Zugriff.
Wann brauchst du überhaupt einen Zeiger?#
Situation | Passender Ausgangspunkt |
|---|---|
Eine eigene kleine Zahl speichern | Ein Wert, etwa int |
Ein vorhandenes Objekt unter einem zweiten Namen verwenden | Eine Referenz, etwa int& |
Ein vorhandenes Ziel ist optional oder kann wechseln | Ein Zeiger, etwa int*, zunächst nullptr |
Ein dynamisch erzeugtes Objekt besitzen | Meist std::unique_ptr, siehe Smart Pointer |
Mehrere Werte speichern | Meist std::vector, siehe vector |
Ein roher Zeiger beschreibt von sich aus weder eine Länge noch Besitz. Bei einer Funktion mit int* musst du deshalb zusätzlich wissen, ob null erlaubt ist, wie lange das Ziel lebt und ob die Funktion es verändern darf.
Übung#
Was ist der Unterschied zwischen p = nullptr; und *p = 0;?
Lösung
Die erste Anweisung entfernt die Zielzuordnung des Zeigerwerts. Die zweite schreibt null in ein gültiges Zielobjekt und darf nur mit einem passenden lebenden Ziel ausgeführt werden.
Ausgabe vorhersagen#
Welcher Wert wird jeweils ausgegeben? Rechne erst auf Papier, bevor du das vollständige Programm ausführst.
1#include <iostream>2 3int main() {4 int a{4};5 int b{9};6 int* p{&a};7 *p += 2;8 p = &b;9 *p = a;10 std::cout << a << ' ' << b << '\n';11}Lösung: Werte und Ziel getrennt verfolgen
Die Ausgabe ist 6 6. Zunächst wird a über *p von 4 auf 6 erhöht. p = &b ändert danach nur das Ziel. *p = a kopiert den Wert 6 in b. Es kopiert weder eine Adresse nach b noch bindet es p wieder an a.
Selbstprüfung#
Ich kann p, &p und *p unterscheiden: Zieladresse, Adresse der Zeigervariablen und Zugriff auf das Ziel.
Ich prüfe nicht nur auf nullptr, sondern kenne auch die Lebensdauer des Zielobjekts.
Ich kann erklären, weshalb ein gewöhnlicher Zeiger nicht automatisch delete ausführen darf.
Weiterlernen#
Zurück: Referenzen: ein zweiter Name für ein Objekt · Kursübersicht · Weiter: Zeigerfallen: Grenzen, C-Strings und dangling
Kommentare 0
Kommentare sind für diese Seite deaktiviert.