Der Unterschied zwischen Zeigern und Array-Notationen in C/C++

Ilja Kostenko 12 Oktober 2023
  1. C++-Array
  2. C++-Zeiger
Der Unterschied zwischen Zeigern und Array-Notationen in C/C++

Zeiger und Arrays sind zweifellos einer der wichtigsten und komplexesten Aspekte von C++. Sie unterstützen verkettete Listen und dynamische Speicherzuweisung und erlauben Funktionen, den Inhalt ihrer Argumente zu ändern.

C++-Array

Ein Array ist eine Menge von Elementen desselben Typs, auf die über den Index zugegriffen wird – die Ordnungszahl des Elements im Array. Zum Beispiel:

int ival;

Es definiert ival als Variable vom Typ int und die Anweisung.

int ia[10];

Es setzt ein Array von zehn int-Objekten. Auf jedes dieser Objekte oder Array-Elemente kann zugegriffen werden, indem ein Index genommen wird.

ival = ia[2];

Er weist der Variablen ival den Wert eines Elements des Arrays ia mit dem Index 2 zu. Ähnlich

ia[7] = ival;

Es weist dem Element mit dem Index 7 den Wert ival zu.

Eine Array-Definition besteht aus einem Typbezeichner, einem Array-Namen und einer Größe. Die Größe gibt die Anzahl der Array-Elemente an (mindestens 1) und wird in eckige Klammern eingeschlossen. Die Größe des Arrays muss bereits bei der Kompilierung bekannt sein und muss daher ein konstanter Ausdruck sein, obwohl er nicht unbedingt durch ein Literal festgelegt wird.

Die Nummerierung der Elemente beginnt bei 0, daher ist der korrekte Indexbereich für ein Array mit 10 Elementen nicht 1 bis 10, sondern 0 bis 9. Hier ist ein Beispiel für das Sortieren aller Elemente des Arrays.

int main() {
  const int array_size = 10;
  int ia[array_size];
  for (int ix = 0; ix < array_size; ++ix) ia[ix] = ix;
}

Wenn Sie ein Array definieren, können Sie es explizit initialisieren, indem Sie die Werte seiner Elemente in geschweiften Klammern auflisten, die durch Kommas getrennt sind.

const int array_size = 3;
int ia[array_size] = {0, 1, 2};

Wenn wir explizit eine Werteliste angeben, dürfen wir die Größe des Arrays nicht angeben: Der Compiler selbst zählt die Anzahl der Elemente.

C++-Zeiger

Ein Zeiger ist ein Objekt, das die Adresse eines anderen Objekts enthält und eine indirekte Manipulation dieses Objekts ermöglicht. Zeiger werden normalerweise verwendet, um mit dynamisch erstellten Objekten zu arbeiten, verwandte Datenstrukturen wie verknüpfte Listen und hierarchische Bäume zu erstellen und große Objekte – Arrays und Klassenobjekte – als Parameter an Funktionen zu übergeben.

Jeder Zeiger ist einem Datentyp zugeordnet. Ihre interne Darstellung hängt nicht vom internen Typ ab: Sowohl die Größe des von einem Objekt des Typs Zeiger belegten Speichers als auch der Wertebereich sind gleich. Der Unterschied besteht darin, wie der Compiler das adressierbare Objekt wahrnimmt. Zeiger auf verschiedene Typen können den gleichen Wert haben, aber der Speicherbereich der entsprechenden Typen kann unterschiedlich sein.

Hier sind einige Beispiele:

int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;

Der Zeiger wird durch ein Sternchen vor dem Namen gekennzeichnet. Bei der Definition von Variablen durch eine Liste sollte jedem Zeiger ein Sternchen vorangestellt werden (siehe oben: ip1 und ip2). Im folgenden Beispiel ist lp ein Zeiger auf ein Objekt vom Typ long und lp2 ist ein Objekt vom Typ long.

long *lp, lp2;

Im folgenden Fall wird fp als Float-Objekt interpretiert und fp2 ist ein Zeiger darauf:

float fp, *fp2;

Gegeben sei eine Variable vom Typ int:

int ival = 1024;

Im Folgenden finden Sie Beispiele für die Definition und Verwendung von Zeigern auf int pi und pi2.

[//] pi is initialized with the null address
int *pi = 0;
[//] pi2 is initialized with the address ival
int *pi2 = &ival;
[//] correct: pi and pi2 contain the ival address
pi = pi2;
[//] pi2 contains the null address
pi2 = 0;

Einem Zeiger kann kein Wert zugewiesen werden, der keine Adresse ist.

[//] error: pi cannot take the value int
pi = ival

Ebenso können Sie einem Zeiger eines Typs, der die Adresse eines Objekts eines anderen Typs ist, keinen Wert zuweisen, wenn die folgenden Variablen definiert sind.

double dval;
double *ps = &dval;

Dann führen beide unten angegebenen Zuweisungsausdrücke zu einem Kompilierungsfehler.

[//] compilation errors
[//] invalid assignment of data types: int* <== double*
pi = pd
pi = &dval;

Es ist nicht so, dass die Variable pi nicht die Adressen des Objekts dval enthalten kann - die Adressen von Objekten unterschiedlichen Typs haben die gleiche Länge. Solche Adressmischoperationen werden absichtlich verboten, da die Interpretation von Objekten durch den Compiler von der Art des Zeigers abhängt.

Natürlich gibt es Fälle, in denen uns der Wert der Adresse selbst interessiert, nicht das Objekt, auf das sie verweist (sagen wir, wir möchten diese Adresse mit einer anderen vergleichen). Um solche Situationen zu lösen, können wir einen speziellen ungültigen Zeiger einführen, der auf jeden Datentyp zeigen kann, und die folgenden Ausdrücke werden korrekt sein:

[//] correct: void* can contain
[//] addresses of any type
void *pv = pi;
pv = pd;

Der Typ des Objekts, auf das void* zeigt, ist unbekannt, und wir können dieses Objekt nicht manipulieren. Alles, was wir mit einem solchen Zeiger tun können, ist, seinen Wert einem anderen Zeiger zuzuweisen oder ihn mit einem Adresswert zu vergleichen.

Um auf ein Objekt mit seiner Adresse zuzugreifen, müssen Sie eine Dereferenzierungsoperation oder indirekte Adressierung anwenden, die durch ein Sternchen (*) gekennzeichnet ist. Zum Beispiel,

int ival = 1024;
, ival2 = 2048;
int *pi = &ival;

Wir können den Wert von ival lesen und speichern, indem wir die Dereferenzierungsoperation auf den Zeiger pi anwenden.

[//] indirect assignment of the ival variable to the ival2 value
*pi = ival2;
[//] value indirect use of variable value and pH value value
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

Wenn wir die Operation zum Nehmen einer Adresse (&) auf ein Objekt vom Typ int anwenden, erhalten wir ein Ergebnis vom Typ int*

int *pi = &ival;

Wenn die gleiche Operation auf ein Objekt vom Typ int* (Zeiger auf den Typ int C) angewendet wird und wir einen Zeiger auf einen Zeiger auf den Typ int und erhalten, d.h. Typ int**. int** ist die Adresse eines Objekts, das die Adresse eines Objekts vom Typ int enthält.

Durch die Dereferenzierung von ppi erhalten wir ein int*-Objekt, das die ival-Adresse enthält. Um das ival-Objekt selbst zu erhalten, muss die Dereferenzierungsoperation zweimal auf das PPI angewendet werden.

int **ppi = &pi;
int *pi2 = *ppi;
cout << "ival value\n"
     << "explicit value: " << ival << "\n"
     << "indirect addressing: " << *pi << "\n"
     << "double indirect addressing: " << **ppi << "\n"
     << end;

Zeiger können in arithmetischen Ausdrücken verwendet werden. Beachten Sie das folgende Beispiel, in dem zwei Ausdrücke völlig unterschiedliche Aktionen ausführen.

int i, j, k;
int *pi = &i;
[//] i = i + 2
*pi = *pi + 2;
[//] increasing the address contained in pi by 2
pi = pi + 2;

Sie können dem Zeiger einen ganzzahligen Wert hinzufügen und auch davon subtrahieren. Das Hinzufügen von 1 zum Zeiger erhöht seinen Wert um die Größe des Speicherbereichs, der dem Objekt des entsprechenden Typs zugewiesen ist. Wenn der Typ char 1 Byte belegt, int – 4 und double - 8, dann erhöht das Hinzufügen von 2 zu den Zeigern auf das Zeichen, integer und double ihren Wert um 2, 8 bzw. 16.

Wie kann dies interpretiert werden? Befinden sich gleichartige Objekte hintereinander im Speicher, so führt eine Erhöhung des Zeigers um 1 dazu, dass er auf das nächste Objekt zeigt. Daher werden bei der Verarbeitung von >Arrays am häufigsten arithmetische Operationen mit Zeigern verwendet; andernfalls sind sie kaum gerechtfertigt.

Hier ist ein typisches Beispiel für die Verwendung von Adressarithmetik beim Durchlaufen von Array-Elementen mit einem Iterator:

int ia[10];
int *iter = &ia[0];
int *iter_end = &ia[10];
while (iter != iter_end) {
  do_the event_ with_(*iter);

Verwandter Artikel - C++ Pointer

Verwandter Artikel - C++ Array