Einleitung: Warum dieses Thema selbst erfahrene C++-Entwickler in den Wahnsinn treibt?
\n\nWenn du jemals fremden C++-Code geöffnet und etwas wie const int * const data gesehen hast, hattest du sicherlich den Wunsch, den Editor zu schließen und Tee trinken zu gehen. Diese seltsame Syntax mit const, das auf beiden Seiten des Sternchens verteilt ist, ist ein echter Stolperstein. Die Frage „What is the difference between const int*, const int * const, and int * const?“ hat auf Stack Overflow Hunderttausende von Aufrufen gesammelt. Und das ist kein Zufall.
Das Verständnis dieser Konstruktionen ist nicht nur die Marotte eines Pedanten. Es ist der Schlüssel zum Schreiben von sicherem, selbst dokumentierendem Code und, nicht unwichtig, zum Bestehen von Vorstellungsgesprächen. Heute werden wir diese Verwirrung ein für alle Mal klären, indem wir eine einfache Regel verwenden, die immer funktioniert.
\n\nDie drei Säulen: Was werden wir vergleichen?
\n\nIn C++ können Zeiger und Konstanz auf vier Arten kombiniert werden (drei davon sind die gängigsten). Bezeichnen wir sie:
\n\n- \n
- Variante 1:
const int* ptr(oderint const* ptr– das ist dasselbe). \n - Variante 2:
int * const ptr. \n - Variante 3:
const int * const ptr(Kombination der ersten beiden). \n
Die Kernidee: const bezieht sich immer auf das, was links davon steht. Wenn links nichts steht, auf das, was rechts steht. Klingt einfach, aber in der Praxis rettet diese Regel.
\n\nVariante 1: const int* ptr (Zeiger auf eine konstante ganze Zahl)
\n\nVon rechts nach links gelesen: „ptr ist ein Zeiger auf eine ganze Zahl, die konstant ist“.
\n\nWas bedeutet das in der Praxis?
\n- \n
- Du kannst den Wert der Variablen, auf die ptr zeigt, nicht über diesen Zeiger ändern. \n
- Du kannst den Zeiger selbst ändern (ihn auf eine andere Variable zeigen lassen). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n const int* ptr = &a; // ptr – Zeiger auf const int\n\n // *ptr = 15; // KOMPILIERUNGSFEHLER! Wert kann nicht über const int* geändert werden\n std::cout << "*ptr = " << *ptr << std::endl; // Gibt 10 aus\n\n ptr = &b; // OK! Zeiger selbst wird geändert\n std::cout << "*ptr = " << *ptr << std::endl; // Gibt 20 aus\n\n return 0;\n}\n\nDieser Typ wird oft verwendet, um Daten an eine Funktion zu übergeben, wenn du garantieren möchtest, dass die Funktion die Originaldaten nicht verändert. Zum Beispiel ist void print(const int* data) ein Versprechen für „Nur-Lesen“.
Variante 2: int * const ptr (Konstanter Zeiger auf eine ganze Zahl)
\n\nVon rechts nach links gelesen: „ptr ist ein konstanter Zeiger auf eine ganze Zahl“.
\n\nHier ist es genau umgekehrt:
\n- \n
- Du kannst den Wert der Variablen, auf die ptr zeigt, ändern. \n
- Du kannst den Zeiger selbst nicht ändern (er muss immer auf denselben Speicherbereich zeigen). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n int * const ptr = &a; // ptr – konstanter Zeiger auf int\n\n *ptr = 15; // OK! Wert über den Zeiger ändern\n std::cout << "*ptr = " << *ptr << std::endl; // Gibt 15 aus\n\n // ptr = &b; // KOMPILIERUNGSFEHLER! Zeiger selbst kann nicht geändert werden\n\n return 0;\n}\n\nStell dir vor, int * const ist ein „gebundener“ Zeiger. Er wird oft für die Arbeit mit Hardware-Registern oder in Fällen verwendet, in denen ein Zeiger auf einen Puffer nicht an eine andere Stelle „entweichen“ darf.
Variante 3: const int * const ptr (Konstanter Zeiger auf eine konstante ganze Zahl)
\n\nVon rechts nach links gelesen: „ptr ist ein konstanter Zeiger auf eine ganze Zahl, die konstant ist“.