Suche…
Einführung
Ein Zeiger ist eine Adresse, die sich auf einen Speicherplatz bezieht. Sie werden häufig verwendet, um Funktionen oder Datenstrukturen zu ermöglichen, den Speicher zu kennen und zu modifizieren, ohne den betreffenden Speicher kopieren zu müssen. Zeiger können sowohl mit primitiven (integrierten) als auch benutzerdefinierten Typen verwendet werden.
Zeiger verwenden die Operatoren "dereference" *
, "address of" &
und "arrow" ->
. Die Operatoren '*' und '->' werden für den Zugriff auf den Speicher verwendet, auf den gezeigt wird, und der Operator &
dient zum Abrufen einer Adresse im Speicher.
Syntax
- <Datentyp> * <Variablenname>;
- <Datentyp> * <Variablenname> = & <Variablenname des gleichen Datentyps>;
- <Datentyp> * <Variablenname> = <Wert desselben Datentyps>;
- int * foo; // Ein Zeiger, der auf einen ganzzahligen Wert zeigt
- int * bar = & myIntVar;
- langer * Stab [2];
- long * bar [] = {& myLongVar1, & myLongVar2}; // Entspricht: long * bar [2]
Bemerkungen
Beachten Sie Probleme beim Deklarieren mehrerer Zeiger in derselben Zeile.
int* a, b, c; //Only a is a pointer, the others are regular ints.
int* a, *b, *c; //These are three pointers!
int *foo[2]; //Both *foo[0] and *foo[1] are pointers.
Zeigergrundlagen
Anmerkung: In allen folgenden nullptr
wird das Vorhandensein der C ++ 11-Konstante nullptr
angenommen. Ersetzen Sie in früheren Versionen nullptr
durch NULL
, die Konstante, die zuvor eine ähnliche Rolle gespielt hat.
Zeigervariable erstellen
Eine Zeigervariable kann mit der spezifischen *
Syntax erstellt werden, z. B. int *pointer_to_int;
.
Wenn eine Variable vom Zeigertyp ( int *
) ist, enthält sie nur eine Speicheradresse. Die Speicheradresse ist der Ort, an dem Daten des zugrunde liegenden Typs ( int
) gespeichert werden.
Der Unterschied ist deutlich, wenn Sie die Größe einer Variablen mit der Größe eines Zeigers mit demselben Typ vergleichen:
// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
long long int foo1;
long long int foo2;
long long int foo3;
} big_struct;
// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;
// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;
/* Produces:
sizeof(bar) = 24
sizeof(p_bar0) = 8
*/
Die Adresse einer anderen Variablen übernehmen
Zeiger können wie normale Variablen untereinander zugewiesen werden. In diesem Fall wird die Speicheradresse von einem Zeiger zu einem anderen kopiert, nicht die tatsächlichen Daten , auf die ein Zeiger zeigt.
Darüber hinaus können sie den Wert nullptr
der einen nullptr
darstellt. Ein Zeiger gleich nullptr
enthält einen ungültigen Speicherplatz und bezieht sich daher nicht auf gültige Daten.
Sie können die Speicheradresse einer Variablen eines bestimmten Typs abrufen, indem Sie der Variablen die Adresse des Operators &
voranstellen. Der von &
Wert ist ein Zeiger auf den zugrunde liegenden Typ, der die Speicheradresse der Variablen enthält (die gültigen Daten ist , solange die Variable nicht den Gültigkeitsbereich verlässt ).
// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;
// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;
// p_bar1 is now nullptr, p_bar2 is &bar.
p_bar0 = p_bar2;
// p_bar0 is now &bar.
p_bar2 = nullptr;
// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr
Im Gegensatz zu Referenzen:
- Durch das Zuweisen von zwei Zeigern wird der Speicher, auf den der zugewiesene Zeiger verweist, nicht überschrieben.
- Zeiger können Null sein.
- Die Adresse des Betreibers ist ausdrücklich erforderlich.
Auf den Inhalt eines Zeigers zugreifen
Wie nehmen Sie eine Adresse erfordert &
sowie den Zugriff auf Inhalte erfordert die Verwendung des Dereferenzierungsoperator *
, als Präfix. Wenn ein Zeiger dereferenziert wird, wird er zu einer Variablen des zugrunde liegenden Typs (eigentlich eine Referenz darauf). Es kann dann gelesen und geändert werden, wenn nicht const
.
(*p_bar0).foo1 = 5;
// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;
// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;
// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.
// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;
Die Kombination aus *
und dem Operator .
wird mit ->
abgekürzt:
std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " << p_bar0->foo1 << std::endl; // Prints 5
Ungültige Zeiger ableiten
Beim Dereferenzieren eines Zeigers sollten Sie sicherstellen, dass er auf gültige Daten zeigt. Das Dereferenzieren eines ungültigen Zeigers (oder eines Nullzeigers) kann zu einer Verletzung des Speicherzugriffs oder zum Lesen oder Schreiben von Speicherdaten führen.
big_struct *never_do_this() {
// This is a local variable. Outside `never_do_this` it doesn't exist.
big_struct retval;
retval.foo1 = 11;
// This returns the address of `retval`.
return &retval;
// `retval` is destroyed and any code using the value returned
// by `never_do_this` has a pointer to a memory location that
// contains garbage data (or is inaccessible).
}
In einem solchen Szenario geben g++
und clang++
die Warnungen korrekt aus:
(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc) warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]
Daher ist Vorsicht geboten, wenn Zeiger Argumente von Funktionen sind, da sie null sein könnten:
void naive_code(big_struct *ptr_big_struct) {
// ... some code which doesn't check if `ptr_big_struct` is valid.
ptr_big_struct->foo1 = 12;
}
// Segmentation fault.
naive_code(nullptr);
Zeigeroperationen
Es gibt zwei Operatoren für Zeiger: Address-of-Operator (&): Liefert die Speicheradresse seines Operanden. Operator für Inhalt (von) (*): Gibt den Wert der Variablen an der vom Operator angegebenen Adresse zurück.
int var = 20;
int *ptr;
ptr = &var;
cout << var << endl;
//Outputs 20 (The value of var)
cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)
cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr
Das Sternchen (*) wird bei der Deklaration eines Zeigers verwendet, um anzuzeigen, dass es sich um einen Zeiger handelt. Verwechseln Sie dies nicht mit dem Dereferenzierungsoperator , mit dem der Wert an der angegebenen Adresse abgerufen wird. Es sind einfach zwei verschiedene Dinge, die mit demselben Zeichen dargestellt werden.
Zeigerarithmetik
Inkrement / Dekrement
Ein Zeiger kann inkrementiert oder dekrementiert werden (Präfix und Postfix). Durch das Inkrementieren eines Zeigers wird der Zeigerwert an das Element im Array angehängt, und zwar ein Element nach dem aktuell angezeigten Element. Durch das Dekrementieren eines Zeigers wird er zum vorherigen Element im Array verschoben.
Die Zeigerarithmetik ist nicht zulässig, wenn der Typ, auf den der Zeiger zeigt, nicht vollständig ist. void
ist immer ein unvollständiger Typ.
char* str = new char[10]; // str = 0x010
++str; // str = 0x011 in this case sizeof(char) = 1 byte
int* arr = new int[10]; // arr = 0x00100
++arr; // arr = 0x00104 if sizeof(int) = 4 bytes
void* ptr = (void*)new char[10];
++ptr; // void is incomplete.
Wenn ein Zeiger auf das Endelement inkrementiert wird, zeigt der Zeiger auf ein Element nach dem Ende des Arrays. Ein solcher Zeiger kann nicht dereferenziert werden, er kann jedoch dekrementiert werden.
Durch das Inkrementieren eines Zeigers auf das einzeilige Element in dem Array oder das Dekrementieren eines Zeigers auf das erste Element in einem Array wird ein undefiniertes Verhalten erzielt.
Ein Zeiger auf ein Nicht-Array-Objekt kann für die Zeigerarithmetik behandelt werden, als wäre es ein Array der Größe 1.
Addition Subtraktion
Integer-Werte können Zeigern hinzugefügt werden. Sie fungieren als Inkrementieren, jedoch um eine bestimmte Zahl statt um 1. Ganzzahlige Werte können ebenfalls von Zeigern abgezogen werden, um als Zeigerdekrementierung zu fungieren. Wie beim Inkrementieren / Dekrementieren muss der Zeiger auf einen vollständigen Typ zeigen.
char* str = new char[10]; // str = 0x010
str += 2; // str = 0x010 + 2 * sizeof(char) = 0x012
int* arr = new int[10]; // arr = 0x100
arr += 2; // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.
Zeigerdifferenzierung
Die Differenz zwischen zwei Zeigern auf denselben Typ kann berechnet werden. Die zwei Zeiger müssen sich in demselben Arrayobjekt befinden. ansonsten undefiniertes Verhalten.
Wenn zwei Zeiger P
und Q
in demselben Array vorhanden sind, ist P - Q
, wenn P
das i
te Element in dem Array ist und Q
das j
te Element ist, dann i - j
. Der Typ des Ergebnisses ist std::ptrdiff_t
von <cstddef>
.
char* start = new char[10]; // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.