C++ unterstützt zwei Unveränderlichkeitsnotationen:
- const – was impliziert, dass sich der Wert nicht ändert. Es wird hauptsächlich verwendet, um Schnittstellen für Daten zu spezifizieren, die an Funktionen und Methoden übergeben werden, ohne Angst haben zu müssen, geändert zu werden. Der Compiler verfolgt das Vorhandensein des const-Bezeichners;
- constexp - was die Berechnung einer Konstante zur Kompilierzeit impliziert. Wird verwendet, um Daten im Speicher abzulegen, wo sie nicht beschädigt werden, und um die Leistung zu verbessern.
Zum Beispiel:
const int dmv = 17; // константа с именем dmv int var = 17; // переменная var не является константой constexpr double max1 = 1.4∗square(dmv); // OK, так как square(17) является константым выражением constexpr double max2 = 1.4∗square(var); // Ошибка, так как var не является константой const double max3 = 1.4∗square(var); // OK, поскольку выражение может быть вычислено в рантайме double sum(const vector<double>&); // sum не будет модифицировать его аргументы vector<double> v {1.2, 3.4, 4.5}; // v не является константой const double s1 = sum(v); // OK, будет вычислено в рантайме constexpr double s2 = sum(v); // Ошибка, sum(v) не является константным выражением.
Damit eine Funktion in einem konstanten Ausdruck verwendet, also vom Compiler ausgewertet werden kann, muss sie mit dem Bezeichner constexpr definiert werden.
constexpr double square(double x) { return x∗x; }
Die constexpr -Funktion muss einfach genug sein, um vom Compiler ausgewertet zu werden und auch den berechneten Wert zurückzugeben. constexpr-Funktionen können mit nicht konstanten Argumenten in Kontexten aufgerufen werden, die keine konstanten Ausdrücke erfordern.
Konst
Objekte mit dem Bezeichner const können nicht geändert werden und müssen ebenfalls initialisiert werden.
const int model = 90; // model является константой const int v[] = { 1, 2, 3, 4 }; // v[i] является константой const int x; // Ошибка: значение не инициализировано
Da Objekte mit const -Bezeichnern nicht geändert werden können, ist der folgende Code fehlerhaft:
void f() { model = 200; // Ошибка: model является константой v[2] = 3; // Ошибка: v[i] является константой }
Beachten Sie, dass const den Typ des Objekts ändert, kein Hinweis darauf, wie die Variable zugewiesen werden soll. const schränkt ein, wie ein Objekt manipuliert werden kann.
void g(const X∗ p) { // Не можем изменить p здесь } void h() { X val; // Но можем модифицировать значение здесь g(&val); // ... }
Bei der Verwendung eines Zeigers sind zwei Objekte beteiligt: der Zeiger selbst und das Objekt, auf das er zeigt. Wenn Sie einer Zeigerdeklaration const voranstellen, wird das Objekt konstant, nicht der Zeiger. Um einen Zeiger selbst und nicht das Objekt, auf das er zeigt, als const zu deklarieren, platzieren Sie const nach dem Zeigerzeichen. Zum Beispiel:
void f1(char∗ p) { char s[] = "Gorm"; const char∗ pc = s; // указатель на константу pc[3] = 'g'; // Ошибка: значение объекта является константой pc = p; // OK char ∗const cp = s; // константный указатель cp[3] = 'a'; // OK cp = p; // Ошибка: cp является константой const char ∗const cpc = s; // константный указатель на константный объект cpc[3] = 'a'; // Ошибка: объект является константой cpc = p; // Ошибка: указатель является константой }
Die Position von const relativ zum Basistyp ist nicht kritisch, da es keinen Datentyp const* gibt. Die Position von const relativ zum Symbol * ist grundlegend. Daher sind folgende Eingaben möglich:
char ∗const cp; // const указатель на char char const∗ pc; // указать на const char const char∗ pc2; // указатель на const char
Ein Objekt, das konstant ist, wenn auf es über einen einzelnen Zeiger zugegriffen wird, kann veränderlich sein, wenn auf andere Weise darauf zugegriffen wird. Dies ist besonders nützlich für Funktionsargumente. Indem ein Zeigerargument als const deklariert wird, wird die Funktion daran gehindert, das Objekt zu ändern, auf das sie zeigt. Zum Beispiel:
const char∗ strchr(const char∗ p, char c); char∗ strchr(char∗ p, char c);
Die erste Variante wird für Strings verwendet, deren Elemente von der Funktion nicht verändert werden sollen und gibt einen Zeiger auf const zurück, der eine Veränderung des Ergebnisses nicht zulässt. Die zweite Version wird für veränderliche Zeichenfolgen verwendet.
Sie können einem Zeiger auf eine Konstante die Adresse einer nicht konstanten Variablen zuweisen, denn das kann nicht schaden. Die Adresse einer Konstanten kann jedoch keinem Nicht-Konstanten-Zeiger zugewiesen werden, da dies den Wert des Objekts ändern würde. Zum Beispiel:
void f4() { int a = 1; const int c = 2; const int∗ p1 = &c; // OK const int∗ p2 = &a; // OK int∗ p3 = &c; // Ошибка: инициализация int* с const int* ∗p3 = 7; // Попытка изменить значение c }
constexpr
Ein konstanter Ausdruck ist ein Ausdruck, der zur Kompilierzeit ausgewertet wird. Konstante Ausdrücke können keine Werte und Variablen verwenden, die zur Kompilierzeit nicht bekannt sind.
Es gibt viele Gründe, warum jemand eine benannte Konstante anstelle eines in einer Variablen gespeicherten Buchstabens oder Werts haben möchte:
- Benannte Konstanten erleichtern das Verständnis und die Wartung von Code.
- Eine Variable kann geändert werden (daher müssen wir bei unserer Argumentation vorsichtiger sein als bei einer Konstanten).
- Die Sprache erfordert konstante Ausdrücke für Array-Größen, Case-Labels und Template-Wert-Argumente.
- Eingebettete Programmierer legen gerne unveränderliche Daten in einen Nur-Lese-Speicher. Denn Nur-Lese-Speicher ist billiger als dynamischer Speicher (in Bezug auf Kosten und Stromverbrauch) und oft reichlicher vorhanden. Darüber hinaus sind Daten im permanenten Speicher vor den meisten Systemausfällen geschützt.
- Wenn die Initialisierung zur Kompilierzeit durchgeführt wird, dann kann es in einem mehrsträngigen Programmsystem keine Datenabweichungen geben.
- Das Durchführen von Berechnungen in der Kompilierungsphase verbessert die Programmleistung.
Der constexpr-Wert wird zur Kompilierzeit ausgewertet, und wenn er nicht ausgewertet werden kann, gibt der Compiler einen Fehler aus.
int x1 = 7; constexpr int x2 = 7; constexpr int x3 = x1; // Ошибка: инициализатор не является константным выражением constexpr int x4 = x2; // OK void f() { constexpr int y3 = x1; // Ошибка: инициализатор не является константным выражением constexpr int y4 = x2; // OK // ... }
Die Möglichkeiten konstanter Ausdrücke sind ziemlich groß, da es möglich ist, ganzzahlige Datentypen, Gleitkommadaten, Aufzählungen sowie Operatoren zu verwenden, die die Werte von Variablen nicht ändern (wie +, ? und [] , aber nicht = oder ++ * )
constexpr int isqrt_helper(int sq, int d, int a) { return sq <= a ? isqrt_helper(sq+d,d+2,a) : d; } constexpr int isqrt(int x) { return isqrt_helper(1,3,x)/2 − 1; } constexpr int s1 = isqrt(9); constexpr int s2 = isqrt(1234);
Отличное описание и примеры!