C++ підтримує дві нотації незмінності:
- const - яка має на увазі, що значення не змінюватиметься. Насамперед це використовується для специфікації інтерфейсів, для даних які передаються у функції та методи так, щоб не побоюватися, що вони будуть змінені. Компілятор відстежує наявність специфікаторів const;
- constexp - який має на увазі обчислення константи під час компіляції. Використовується для розміщення даних у пам'яті, де вони не будуть пошкоджені, а також для покращення продуктивності.
Наприклад:
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) не является константным выражением.
Щоб функція використовувалася в константному вираженні, тобто, обчислювалася компілятором, необхідно визначити її зі специфікатором constexpr .
constexpr double square(double x) { return x∗x; }
constexpr функція має бути досить простою, щоб обчислюватися компілятором, а також повертати обчислене значення. constexpr функції можуть викликатися неконстантими аргументами в контексті яких не потрібні константні вирази.
конст
Об'єкти зі специфікатором const не можуть бути змінені, а також мають бути ініціалізовані.
const int model = 90; // model является константой const int v[] = { 1, 2, 3, 4 }; // v[i] является константой const int x; // Ошибка: значение не инициализировано
Оскільки об'єкти зі специфікаторів const не можуть бути змінені, наступний код буде помилковим:
void f() { model = 200; // Ошибка: model является константой v[2] = 3; // Ошибка: v[i] является константой }
Зверніть увагу, що const змінює тип об'єкта, а не вказівку на те, як повинна бути призначена змінна. const обмежує способи роботи з об'єктом.
void g(const X∗ p) { // Не можем изменить p здесь } void h() { X val; // Но можем модифицировать значение здесь g(&val); // ... }
При використанні покажчика використовуються два об'єкти: сам покажчик і об'єкт, на який вказує. Префіксне оголошення вказівника з const робить константним об'єкт, а не вказівник. Щоб оголосити як const вказівник, а не об'єкт, на який він вказує, необхідно помістити const після символу вказівника. Наприклад:
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; // Ошибка: указатель является константой }
Розташування const щодо базового типу не є принциповим, оскільки не існує типу даних const*. Принциповим є положення const щодо символу *. Тому можливі такі записи:
char ∗const cp; // const указатель на char char const∗ pc; // указать на const char const char∗ pc2; // указатель на const char
Об'єкт, який є константою при доступі через один покажчик, може змінюватися при доступі іншими способами. Це особливо корисно для аргументів функції. Оголошуючи аргумент покажчика як const , функції забороняється змінювати об'єкт, який вказує. Наприклад:
const char∗ strchr(const char∗ p, char c); char∗ strchr(char∗ p, char c);
Перша версія використовується для рядків, елементи яких не повинні бути змінені функцією та повертає вказівник на const, який не дозволяє змінювати результат. Друга версія використовується для змінних рядків.
Ви можете призначити адресу неконстантної змінної вказівнику на константу, тому що це не може завдати жодної шкоди. Однак адресу константи не можна призначити неконстантному покажчику, оскільки це дозволить змінити значення об'єкта. Наприклад:
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
Константний вираз є виразом, який обчислюється під час компіляції. Константні вирази не можуть використовувати значення та змінні, які не відомі під час компіляції.
Існує безліч причин, з яких комусь може знадобитися іменована константа, а не буква або значення, що зберігається у змінній:
- Іменовані константи спрощують розуміння та підтримку коду.
- Змінна може бути змінена (тому ми маємо бути більш обережними у наших міркуваннях, ніж для константи).
- Мова вимагає постійних виразів для розмірів масивів, позначок case та аргументів значень шаблону.
- Програмісти вбудованих систем люблять поміщати незмінні дані в постійне запам'ятовуючий пристрій. Тому що доступна тільки для читання пам'ять дешевша, ніж динамічна пам'ять (з точки зору витрат та споживання енергії) і часто більш численна. Крім того, дані у постійній пам'яті захищені від більшості збоїв системи.
- Якщо ініціалізація виконується під час компіляції, то багатопоточної програмі системі може бути ніяких розбіжностей даних.
- Виконання обчислень на етапі компіляції покращує продуктивність програми.
Значення constexpr обчислюється під час виконання компіляції, і якщо воно не може бути обчислене, компілятор видасть помилку.
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 // ... }
Можливості константних виразів досить великі, оскільки є можливість використовувати цілі типи даних, дані з плаваючою точкою, перерахування, а також оператори, які не змінюють значення змінних (такі як +, ? і [] , але не = або ++ )
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);
Отличное описание и примеры!