Const (программирование)

В языках программирования C, C++, C# и D const является квалификатором типа: ключевое слово применяется к типу данных, показывая, что данные константны (неизменяемы). Это может быть использовано при объявлении (декларировании) констант. Отличительная особенность const в C-подобных языках программирования проявляется при его комбинировании с типами данных, что дает сложное поведение в сочетании с указателями, ссылками, составными типами данных и при проверке типов.

Введение 
При объявлении объектов с использованием const их значения константны (неизменяемы), в отличие от перeменных. Этот основной сценарий использования — объявление констант — имеет параллели во многих языках.

Однако в отличие от других языков в семье языков C const является частью типа, а не объекта. Например, в C const int x = 1; объявляет объект x типа const int, где const — часть типа. Это может читаться «(const int) x» — в то время как в Ada X : constant INTEGER := 1_ объявляет константу (вид объекта) X типа INTEGER, т. е. constant является частью объекта, а не типа.

Появляются два тонких момента. Во-первых, const может быть частью более сложных типов. Например, int const * const x; (выражение читается от переменной x в противоположную сторону) объявляет константный указатель на константное целое число, в то время как int const * x; объявляет переменную-указатель на константное целое, а int * const x; объявляет константный указатель на переменное целое. Во-вторых, поскольку const является частью типа, это используется при проверке типов. Например, следующий код некорректен:
void f(int& x);
//...
const int i;
f(i);
потому что аргумент, передаваемый в f, должен быть ссылочной переменной на целое, а i — константное целое. Требование такого соответствия — форма обеспечения правильности программы, также известное как const-овая правильность (). Это дает возможность контрактного проектирования, где у функций в качестве части сигнатуры типа указывается, будут ли они изменять свои аргументы или нет, и изменяемы (неконстантны) ли их возвращаемые значения. Такая проверка типов интересна прежде всего для указателей и ссылок (т. е. когда параметры передаются по ссылке) — а не для основных типов, таких как целые, — а также для составных типов данных или шаблонизированных типов, таких как контейнеры. Вследствие неявного преобразования типов при выполнении программы const может быть опущен.

Следствия 
При сохранении в памяти компьютера, константность не накладывает на значение ограничения на запись. const — это скорее конструкция времени компиляции, которую программист потенциально может использовать, однако это необязательно. Стоит обратить внимание, что в случае предопределенных строковых литералов (таких как const char *) константное значение в языке Си (и Си++) обычно неперезаписываемо, так как оно может храниться в сегменте памяти с запретом записи.

Другие варианты использования 
В дополнение к этому как const может быть объявлена функция-член (нестатическая). В этом случае указатель this внутри такой функции будет иметь тип object_type const * const вместо object_type * const. Это означает, что неконстантные по отношению к этому объекту функции изнутри такой функции вызваны быть не могут, также не могут быть модифицированы поля класса. В C++ поле класса может быть объявлено как mutable (изменяемое), что означает, что это ограничение к нему не относится. В некоторых случаях это может быть полезно, например, при кешировании, подсчёте ссылок и синхронизации данных. В этих случаях логический смысл (состояние) объекта неизменяем, но объект физически неконстантен, так как его поразрядное представление может измениться.

Синтаксис 
В C, C++, и D все типы данных, включая те, которые определены пользователем, могут быть объявлены const, и «const-овая правильность» предполагает, что все переменные или объекты должны быть объявлены таковыми, если их не нужно модифицировать. Такое предусмотрительное использование const делает значения переменных "простыми для понимания, отслеживания, и обдумывания", таким образом, читаемость и понятность увеличиваются и делают работу в командах и поддержку кода проще, потому что это предоставляет информацию о надлежащем использовании их значений. Это может помочь компилятору так же, как и разработчику при размышлениях над кодом. Это также может позволить оптимизирующему компилятору генерировать более эффективный код.

Простые типы данных 
Для простых типов данных (неуказателей) применение квалификатора const очевидно. Его можно указывать с любой стороны типа по историческим причинам (const char foo = 'a'; эквивалентно char const foo = 'a';). В некоторых реализациях использование const с двух сторон типа (например, const char const) генерирует предупреждение, но не ошибку.

Указатели и ссылки 
Для указателей и ссылок результирующий эффект const более запутан: и сам указатель, и значение, на которое он указывает, или оба могут быть объявлены как const. Более того, синтаксис также может сбивать с толку.

Указатель может быть объявлен const-указателем на перезаписываемое значение (type * const x;), или перезаписываемым указателем на const-значение (type const * x; //или: const type * x;), или const-указателем на const-значение (type const * const x;). const-указателю нельзя переприсвоить ссылку на другой объект с первоначального, но он (указатель) может быть использован для изменения значения, на которое он указывает (такое значение называется «значение по указателю» ()). Таким образом, синтаксис ссылочных переменных — альтернативный синтакс const-указателей. С другой стороны, указателю на const-объект можно переприсвоить ссылку для указания на другую область памяти (которая должна содержать объект того же или приводимого типа), но его нельзя будет использовать, чтобы менять значения в памяти, на которые он указывает. Также может быть определен const-указатель на const-объект, который нельзя использовать, чтобы изменять значение по нему, и которому нельзя переприсвоить ссылку на другой объект.

Эти тонкости иллюстрирует следующий код:
void Foo (
 int * ptr,
 int const * ptrToConst,
 int * const constPtr,
 int const * const constPtrToConst)
{
    *ptr = 0; //Порядок: меняет данные по указателю.
    ptr = NULL; //Порядок: меняет указатель.
    
    *ptrToConst = 0; //Ошибка! Нельзя менять данные по указателю.
    ptrToConst = NULL; //Порядок: меняет указатель.
    
    *constPtr = 0; //Порядок: меняет данные по указателю.
    constPtr  = NULL; //Ошибка! Нельзя менять указатель.
    
    *constPtrToConst = 0; //Ошибка! Нельзя менять данные по указателю.
    constPtrToConst  = NULL; //Ошибка! Нельзя менять указатель.
}

Соглашения по записи в языке C 
В соответствие с обычными соглашениями языка Си об объявлениях последние указываются за предполагаемым использованием, а звёздочка у указателя к нему ставится вплотную, указывая на разыменование. Например, в объявлении int *ptr разыменованная форма *ptr является целым (int), а ссылочная форма ptr — указателем на целое. Таким образом, const модифицирует имя переменной справа от себя.

Соглашение в языке Си++  наоборот, связывать * с типом (т. е. int* ptr) и читать, что const модифицирует тип слева от себя. Поэтому int const * ptrToConst можно прочитать или как «*ptrToConst  это int const» (значение по указателю неизменяемо), или как «ptrToConst  это int const *» (указатель на неизменяемое целое значение).

Таким образом:
int *ptr; //"*ptr" -- целое значение.
int const *ptrToConst; //"*ptrToConst" -- константа ("int" -- целая).
int * const constPtr; //"constPtr" -- константа ("int *" -- указатель на целое).
int const * const constPtrToConst; //"constPtrToConst" -- константа (указатель),
                                   //как и "*constPtrToConst" (значение).

Примечания 

Типы данных
Концепции языков программирования
Языки программирования семейства Си