Введение
Квалификатор типа, обозначающий данные как предназначенные только для чтения.
В некоторых языках программирования `const` является квалификатором типа (ключевым словом, применяемым к типу данных), который указывает, что данные доступны только для чтения. Хотя его можно использовать для объявления констант, в языках семейства C он отличается от аналогичных конструкций в других языках тем, что является частью типа, и, следовательно, демонстрирует сложное поведение при использовании с указателями, ссылками, составными типами данных и проверкой типов. В других языках данные не хранятся в одном месте памяти, а копируются во время компиляции при каждом использовании. Языки, использующие `const`, включают C, C++, D, JavaScript, Julia и Rust.
Введение
Примененный в объявлении объекта, он указывает, что объект является константой: его значение нельзя изменять, в отличие от переменной. Это основное применение – для объявления констант – имеет аналоги во многих других языках. Однако, в отличие от других языков, в семействе языков C `const` является частью типа, а не частью объекта. Например, в C `int const x;` объявляет объект `x` типа `int const` – `const` является частью типа, как если бы выражение было разобрано как `(int const) x` – в то время как в Ada `X : INTEGER;` объявляет константу (вид объекта) `X` типа `INTEGER`: константа является частью объекта, но не частью типа. Это имеет два тонких следствия. Во-первых, `const` может применяться к частям более сложного типа – например, `int const * const x;` объявляет константный указатель на константное целое число, в то время как `int const * x;` объявляет переменный указатель на константное целое число, а `int * const x;` объявляет константный указатель на переменное целое число. Во-вторых, поскольку `const` является частью типа, он должен совпадать при проверке типов. Например, следующий код недопустим:
```c++
void f(int& x);
int const i;
f(i);
```
потому что аргумент функции `f` должен быть переменным целым числом, а `i` является константным целым числом. Это совпадение является формой корректности программы и известно как const-корректность. Это позволяет использовать программирование по контракту, когда функции указывают в своей сигнатуре типа, изменяют ли они свои аргументы или нет, и является ли возвращаемое значение изменяемым или нет. Эта проверка типов особенно важна для указателей и ссылок – не для базовых типов значений, таких как целые числа, – но также для составных типов данных или шаблонных типов, таких как контейнеры. Это скрывается тем фактом, что `const` часто можно опустить из-за приведения типов (неявного преобразования типов) и того, что C использует передачу по значению (C++ и D используют либо передачу по значению, либо передачу по ссылке).
void f(int& x);
//
int const i;
f(i);
because the argument to f must be a variable integer, but i is a constant integer. This matching is a form of program correctness, and is known as const correctness. This allows a form of programming by contract, where functions specify as part of their type signature whether they modify their arguments or not, and whether their return value is modifiable or not. This type checking is primarily of interest in pointers and references – not basic value types like integers – but also for composite data types or templated types such as containers. It is concealed by the fact that the const can often be omitted, due to type coercion (implicit type conversion) and C being call by value (C++ and D are either call by value or call by reference).
Последствия
Идея константности не подразумевает, что переменная, как она хранится в компьютерной памяти, является неизменяемой. Скорее, константность – это конструкция времени компиляции, которая указывает программисту, что следует делать, а не обязательно то, что он может делать. Однако следует отметить, что в случае предопределенных данных (например, строковых литералов типа `char const *`), `const` в C часто делает данные неизменяемыми.
Отличие от констант
В то время как константа не изменяет своего значения во время выполнения программы, объект, объявленный как `const`, на самом деле может изменить свое значение во время выполнения программы. Типичным примером служат регистры только для чтения во встраиваемых системах, такие как текущее состояние цифрового входа. Регистры данных для цифровых входов часто объявляются как `const` и `volatile`. Содержимое этих регистров может изменяться без какого-либо действия со стороны программы (`volatile`), но попытка записи в них со стороны программы будет некорректной (`const`).
Другие применения
Кроме того, функция-член (нестатическая) может быть объявлена как `const`. В этом случае указатель `this` внутри такой функции имеет тип `const объект *`, а не просто `объект *`. Это означает, что изнутри такой функции нельзя вызывать не-`const` функции для этого объекта, а также изменять переменные-члены. В C++ переменную-член можно объявить как `mutable`, указывая, что это ограничение к ней не применяется. В некоторых случаях это может быть полезно, например, для кэширования, подсчета ссылок и синхронизации данных. В этих случаях логическое значение (состояние) объекта остается неизменным, но объект не является физически константным, поскольку его битовое представление может изменяться.
Синтаксис
В C, C++ и D все типы данных, включая определяемые пользователем, могут быть объявлены как `const`. Принцип "const-корректности" требует, чтобы все переменные или объекты объявлялись как `const`, если в их изменении нет необходимости. Такое упреждающее использование `const` делает значения "более понятными, отслеживаемыми и удобными для анализа", что повышает читаемость и ясность кода, упрощает совместную работу и поддержку, поскольку передает информацию о предполагаемом использовании значения. Это может помочь как компилятору, так и разработчику при анализе кода, а также дать оптимизирующему компилятору возможность генерировать более эффективный код.
Простые типы данных
Для простых типов данных, не являющихся указателями, применение квалификатора `const` достаточно просто. По историческим причинам он может располагаться с обеих сторон некоторых типов (например, `const char foo = 'a';` эквивалентно `char const foo = 'a';`). В некоторых реализациях повторное использование `const` (например, `const char const` или `char const const`) вызывает предупреждение, но не приводит к ошибке.
Параметры и переменные
const может быть объявлена как для параметров функции, так и для переменных (статических или автоматических, включая глобальные или локальные). Интерпретация зависит от контекста использования. const статическая переменная (глобальная переменная или статическая локальная переменная) является константой и может использоваться для хранения данных, таких как математические константы, например, double const PI = 3.14159 – и даже более точное значение, или общих параметров времени компиляции. const автоматическая переменная (нестатическая локальная переменная) означает, что ей присваивается значение только один раз, хотя это значение может быть разным при каждом вызове, например, int const x_squared = x * x. Параметр, переданный как const ссылка, гарантирует, что переданное значение не будет изменено – это часть соглашения о вызове функции. В то время как параметр, переданный как const по значению (или сам указатель при передаче по ссылке), не добавляет ничего к интерфейсу функции (поскольку значение было скопировано), но указывает, что внутри функции локальная копия параметра не будет изменена (ей присваивается значение только один раз). По этой причине некоторые предпочитают использовать const только для параметров, передаваемых по ссылке, поскольку это влияет на соглашение о вызове функции, но не для параметров, передаваемых по значению, поскольку это раскрывает детали реализации.
Проблемы
Использование системы типов для выражения константности приводит к различным сложностям и проблемам и, следовательно, подвергалось критике и не получило распространения за пределами узкого семейства языков C, C++ и D. Java и C#, находящиеся под сильным влиянием C и C++, явно отказались от квалификаторов типа в стиле `const`, предпочитая выражать константность с помощью ключевых слов, применяемых к идентификатору (`final` в Java, `const` и `readonly` в C#). Даже внутри C и C++ использование `const` значительно различается: одни проекты и организации используют его последовательно, а другие избегают.
История
const был введен Бьярном Строструпом в C with Classes, предшественнике C++, в 1981 году, и первоначально назывался readonly. О мотивации Строструп пишет: "На заседании комитета X3J11 в декабре 1987 года был предложен дополнительный спецификатор, noalias, но он был отклонен; его цель в конечном итоге была реализована ключевым словом restrict в C99". Ритчи не очень приветствовал эти дополнения, считая, что они не оправдывают затраченных усилий, но в итоге не настаивал на их исключении из стандарта. Язык D впоследствии унаследовал const от C++, где он рассматривается как конструктор типов (а не спецификатор типа), и добавил два дополнительных конструктора типов – immutable и inout – для обработки схожих сценариев использования.
Другие языки
Другие языки не следуют примеру C/C++ в том, что константность является частью типа, хотя они часто имеют поверхностно похожие конструкции и могут использовать ключевое слово `const`. Обычно это используется только для констант (константных объектов). В C# есть ключевое слово `const`, но с радикально иной и более простой семантикой: оно означает константу времени компиляции и не является частью типа. Nim имеет ключевое слово `const`, похожее на C#: оно также объявляет константу времени компиляции, а не формирует часть типа. Однако в Nim константу можно объявить из любого выражения, которое может быть вычислено во время компиляции. В C# только встроенные типы C# могут быть объявлены как `const`; пользовательские типы, включая классы, структуры и массивы, не могут быть `const`. В Java нет `const` – вместо этого есть `final`, который может быть применен к локальным объявлениям "переменных" и применяется к идентификатору, а не к типу. Он имеет иное объектно-ориентированное применение для членов объекта, что является источником названия. Спецификация языка Java рассматривает `const` как зарезервированное ключевое слово – то есть, которое нельзя использовать в качестве идентификатора переменной – но не присваивает ему никакой семантики: это зарезервированное слово (не может использоваться в идентификаторах), но не ключевое слово (не имеет специального значения). Считается, что ключевое слово было зарезервировано, чтобы позволить расширение языка Java для включения методов в стиле `const` из C++ и указателей на тип `const`. В процессе сообщества Java существует запрос на улучшение для реализации корректности `const`, но он был закрыт в 2005 году на основании невозможности реализации в обратной совместимости. Современный Ada 83 независимо имел понятие постоянного объекта и ключевого слова `const`, при этом входные и параметры цикла были неявно постоянными. Здесь константа является свойством объекта, а не типа. В JavaScript есть объявление `const`, которое определяет переменную с областью видимости блока, которую нельзя переназначить или переобъявить. Оно определяет ссылку только для чтения к переменной, которую нельзя переопределить, но в некоторых ситуациях значение самой переменной может потенциально измениться, например, если переменная ссылается на объект и изменяется его свойство.