Warum es Konstanten gibt
Eine Konstante ist ein Wert, den du nach dem Festlegen versprichst, nie wieder zu ändern. Etwas als const zu markieren erfüllt zwei Aufgaben gleichzeitig: Es dokumentiert deine Absicht für jeden, der den Code liest, und es erlaubt dem Compiler, diese Absicht zu erzwingen – jede Zeile, die versucht, den Wert zu ändern, wird zu einem Compilefehler statt zu einem stillen Laufzeitfehler.
Während das Schlüsselwort auto den Compiler den Typ einer Variablen ableiten lässt, schränkt const ein, was du mit dieser Variablen tun darfst. Beide lassen sich frei kombinieren: const auto limit = 100; ist ein schreibgeschützter int.
Einen const-Wert deklarieren
Setze const vor den Typ. Eine const-Variable muss in derselben Zeile initialisiert werden, denn es gibt keinen späteren Moment, in dem es dir erlaubt wäre, ihr etwas zuzuweisen.
Kommentiere die Zuweisung aus, und das Programm lässt sich nicht kompilieren – der Compiler meldet „assignment of read-only variable". Genau das ist der Sinn: Der Fehler wird abgefangen, bevor das Programm überhaupt läuft.
Eine verbreitete, aus C übernommene Anfängergewohnheit ist #define MAX_USERS 100. Vermeide das. Ein Makro ist eine blinde Textersetzung ohne Typ und ohne Rücksicht auf den Gültigkeitsbereich, daher lässt es sich in einem Debugger nicht inspizieren und erzeugt verwirrende Fehlermeldungen. Eine const- (oder constexpr-)Variable wird wie alles andere typgeprüft und hat einen Gültigkeitsbereich.
const vs constexpr
Beide Schlüsselwörter geben dir einen Wert, der sich nicht ändern kann, aber sie beantworten unterschiedliche Fragen. const sagt „das ändert sich nach dem Festlegen nie mehr". constexpr sagt das Stärkere: „das kann zur Compilezeit berechnet werden" – und alles, was constexpr ist, ist automatisch auch const.
Die Faustregel: Greife zu constexpr, wann immer der Wert ein festes Literal oder eine Berechnung ist, die der Compiler durchführen kann (Array-Größen, Pufferlängen, switch-Labels, Template-Argumente). Verwende einfaches const, wenn der Wert zur Laufzeit feststeht, sich danach aber nicht ändern soll – etwa eine const-Kopie eines Funktionsarguments.
Seit C++20 gibt es außerdem consteval, das bei Funktionen verwendet wird, die zur Compilezeit ausgeführt werden müssen:
consteval int square(int x) { return x * x; }
constexpr int area = square(8); // während der Kompilierung berechnet
Eine constexpr-Funktion darf zur Compilezeit laufen; eine consteval-Funktion muss es immer, sonst ist es ein Fehler.
Zeiger und const: von rechts nach links lesen
Hier bringt const die Leute durcheinander, denn das Schlüsselwort kann auf beiden Seiten des * stehen, und die beiden Bedeutungen sind entgegengesetzt. Der Trick besteht darin, die Deklaration von rechts nach links zu lesen.
Lies int* const p2 von rechts nach links: „p2 ist ein const-Zeiger auf int". Lies const int* p1 als „p1 ist ein Zeiger auf const int". Verstehst du das falsch, verbringst du echte Zeit verwirrt mit einem Fehler, der dir sagt, dass du etwas nicht ändern darfst, das du für veränderbar gehalten hast.
Ein praktischer Fallstrick: Nimm niemals die Adresse eines const und wirf das const per Cast weg, um das zugrunde liegende Objekt zu ändern. Das ist undefiniertes Verhalten, wenn das ursprüngliche Objekt wirklich const war, und der Compiler darf annehmen, dass sich der Wert nie ändert – dein „Schreibvorgang" wird vielleicht einfach ignoriert.
const-Referenzen als Funktionsparameter
Der häufigste alltägliche Einsatz von const ist das Übergeben großer Objekte per Referenz, ohne sie zu kopieren. Ein const&-Parameter vermeidet die Kopie und verspricht, dass die Funktion das Argument des Aufrufers nicht ändert.
Die Übergabe per const& ist die Standardwahl für jeden Parameter, der größer als ein paar Bytes ist (Strings, Vektoren, deine eigenen Klassen). Beachte, dass sie der Funktion auch erlaubt, ein Temporäres wie "Grace" anzunehmen – eine einfache nicht-const Referenz kann nicht an ein Temporäres binden, das Weglassen des const würde hier also den zweiten Aufruf zurückweisen.
const-Memberfunktionen
Wenn du eine Klasse schreibst, markiere jede Methode, die das Objekt nicht ändert, mit einem nachgestellten const. Das macht die Methode auf const-Instanzen und const&-Parametern aufrufbar – ohne dies kannst du dein eigenes Objekt nicht über ein const-Handle lesen.
Die Disziplin, schreibgeschützte Methoden mit const zu markieren, nennt man const correctness. Mach es von Anfang an richtig – eine Methode, die const hätte sein sollen, lässt sich leicht ergänzen, aber const später in einer großen Codebasis nachzurüsten ist schmerzhaft, weil jeder Aufrufer über eine const-Referenz davon abhängt.
Weiter: Operatoren
Jetzt, da deine Werte mit const festgezurrt werden können, ist der nächste Schritt, etwas mit ihnen zu tun. Die Seite zu den Operatoren behandelt arithmetische, vergleichende, logische und Zuweisungsoperatoren – einschließlich der Fallstricke rund um die Ganzzahldivision, der Operatorpräzedenz und der Frage, wie const mit den Zuweisungsoperatoren interagiert, die du nicht verwenden darfst.
Häufig gestellte Fragen
Was ist der Unterschied zwischen const und constexpr in C++?
const bedeutet, dass sich der Wert nach der Initialisierung nicht mehr ändern kann, er darf aber zur Laufzeit berechnet werden. constexpr ist strenger: Es garantiert, dass der Wert zur Compilezeit berechnet werden kann, sodass er überall dort verwendet werden kann, wo eine Compilezeit-Konstante verlangt wird (Array-Größen, Template-Argumente, switch-Labels). Jedes constexpr-Objekt ist auch const, aber nicht jedes const-Objekt ist constexpr.
Wie deklariere ich eine Konstante in C++?
Setze const vor den Typ und gib ihm einen Wert: const int maxUsers = 100;. Eine const-Variable muss bei der Deklaration initialisiert werden, weil du ihr später nie etwas zuweisen kannst. Für Compilezeit-Konstanten ist constexpr int maxUsers = 100; vorzuziehen. Vermeide das alte #define-Makro im C-Stil – es hat keinen Typ und ignoriert den Gültigkeitsbereich.
Was bedeutet ein const-Zeiger in C++?
Das hängt davon ab, wo const steht. const int* p ist ein Zeiger auf const – du kannst p umbiegen, aber du kannst *p nicht ändern. int* const p ist ein const-Zeiger – du kannst *p ändern, aber du kannst p nicht umbiegen. Lies die Deklaration von rechts nach links: int* const ist „const-Zeiger auf int".