Зачем нужны константы
Константа — это значение, которое вы обещаете никогда не менять после того, как задали его. Пометка чего-либо как const делает сразу две вещи: документирует ваше намерение для всех, кто читает код, и позволяет компилятору обеспечить это намерение — любая строка, пытающаяся изменить значение, становится ошибкой компиляции, а не тихим багом во время выполнения.
Если ключевое слово auto позволяет компилятору вывести тип переменной, то const ограничивает то, что вы можете делать с этой переменной. Они свободно сочетаются: const auto limit = 100; — это int только для чтения.
Объявление const-значения
Поставьте const перед типом. Переменную const обязательно инициализировать в той же строке, потому что нет более позднего момента, когда вам было бы разрешено присвоить ей значение.
Раскомментируйте присваивание — и программа не скомпилируется: компилятор сообщит «assignment of read-only variable». В этом и весь смысл: ошибка ловится ещё до того, как программа запустится.
Распространённая привычка новичков, перенесённая из C, — это #define MAX_USERS 100. Избегайте её. Макрос — это слепая текстовая подстановка без типа и без учёта области видимости, поэтому его нельзя осмотреть в отладчике, и он порождает запутанные сообщения об ошибках. Переменная const (или constexpr) проходит проверку типов и имеет область видимости, как и всё остальное.
const vs constexpr
Оба ключевых слова дают вам значение, которое нельзя изменить, но они отвечают на разные вопросы. const говорит: «это никогда не меняется после установки». constexpr говорит более сильное: «это можно вычислить во время компиляции» — и всё, что constexpr, автоматически является и const.
Практическое правило: используйте constexpr, когда значение — это фиксированный литерал или вычисление, которое может выполнить компилятор (размеры массивов, длины буферов, метки switch, аргументы шаблонов). Используйте простой const, когда значение определяется во время выполнения, но не должно меняться после этого — например, const-копия аргумента функции.
Начиная с C++20 есть также consteval, применяемый к функциям, которые обязаны выполняться во время компиляции:
consteval int square(int x) { return x * x; }
constexpr int area = square(8); // вычислено во время компиляции
Функция constexpr может выполняться во время компиляции; функция consteval всегда обязана это делать, иначе это ошибка.
Указатели и const: читайте справа налево
Именно здесь const сбивает людей с толку, потому что ключевое слово может стоять по любую сторону от *, и эти два значения противоположны. Хитрость в том, чтобы читать объявление справа налево.
Читайте int* const p2 справа налево: «p2 — это константный указатель на int». Читайте const int* p1 как «p1 — это указатель на const int». Ошибётесь здесь — и потеряете реальное время в недоумении из-за ошибки, которая говорит, что вы не можете изменить то, что считали изменяемым.
Практическая ловушка: никогда не берите адрес const и не отбрасывайте const с помощью приведения, чтобы изменить нижележащий объект. Это неопределённое поведение, если исходный объект действительно был const, и компилятор вправе считать, что значение никогда не меняется — ваша «запись» может быть просто проигнорирована.
const-ссылки как параметры функций
Самое частое повседневное применение const — передача больших объектов по ссылке без копирования. Параметр const& избегает копии и обещает, что функция не изменит аргумент вызывающей стороны.
Передача по const& — выбор по умолчанию для любого параметра больше пары байтов (строки, векторы, ваши собственные классы). Обратите внимание, что это также позволяет функции принимать временный объект вроде "Grace" — обычная неконстантная ссылка не может связаться с временным объектом, поэтому, убрав здесь const, вы отклонили бы этот второй вызов.
const-методы
Когда вы пишете класс, помечайте любой метод, который не изменяет объект, завершающим const. Именно это делает метод вызываемым на const-экземплярах и const&-параметрах — без этого вы не сможете прочитать собственный объект через const-дескриптор.
Дисциплину пометки методов только для чтения как const называют const correctness. Делайте это правильно с самого начала — метод, который должен был быть const, легко добавить, но дооснащать const в большой кодовой базе позже мучительно, потому что от этого зависит каждый вызывающий через const-ссылку.
Далее: операторы
Теперь, когда ваши значения можно зафиксировать с помощью const, следующий шаг — что-то с ними делать. Страница про операторы охватывает арифметические, сравнения, логические операторы и операторы присваивания — включая подводные камни целочисленного деления, приоритет операторов и то, как const взаимодействует с операторами присваивания, которые вам не разрешено использовать.
Часто задаваемые вопросы
В чём разница между const и constexpr в C++?
const означает, что значение нельзя изменить после инициализации, но оно может вычисляться во время выполнения. constexpr строже: он гарантирует, что значение может быть вычислено во время компиляции, поэтому его можно использовать там, где требуется константа времени компиляции (размеры массивов, аргументы шаблонов, метки switch). Каждый объект constexpr также является const, но не каждый объект const является constexpr.
Как объявить константу в C++?
Поставьте const перед типом и задайте значение: const int maxUsers = 100;. Переменную const нужно инициализировать при объявлении, потому что присвоить ей значение позже уже не получится. Для констант времени компиляции предпочтительнее constexpr int maxUsers = 100;. Избегайте старого макроса #define в стиле C — у него нет типа и он игнорирует область видимости.
Что означает константный указатель в C++?
Это зависит от того, где стоит const. const int* p — это указатель на константу: вы можете перенаправить p, но не можете изменить *p. int* const p — это константный указатель: вы можете изменить *p, но не можете перенаправить p. Читайте объявление справа налево: int* const — это «константный указатель на int».