В программировании компьютера константа — это значение , которое не изменяется программой во время обычного выполнения . При ассоциации с идентификатором константа называется «именованной», хотя термины «константа» и «именованная константа» часто используются взаимозаменяемо. Это контрастирует с переменной , которая является идентификатором со значением, которое может быть изменено во время обычного выполнения. Для упрощения значения констант остаются, в то время как значения переменных изменяются, отсюда и оба их названия.
Константы полезны как для программистов, так и для компиляторов : для программистов они являются формой самодокументируемого кода и позволяют рассуждать о корректности , в то время как для компиляторов они позволяют проводить проверки во время компиляции и выполнения , которые подтверждают, что предположения о постоянстве не нарушаются [a] , а также позволяют или упрощают некоторые оптимизации компилятора .
Существуют различные конкретные реализации общего понятия константы с тонкими различиями, которые часто упускаются из виду. Наиболее значимыми являются: константы времени компиляции (статически оцениваемые), константы времени выполнения (динамически оцениваемые), неизменяемые объекты и типы констант ( const
).
Типичные примеры констант времени компиляции включают математические константы, значения из стандартов (здесь максимальная единица передачи ) или внутренние значения конфигурации (здесь символы в строке ), такие как эти примеры на языке C :
const float PI = 3.1415927 ; // максимальная точность одинарного числа с плавающей точкой const unsigned int MTU = 1500 ; // Ethernet v2, RFC 894 const unsigned int COLUMNS = 80 ;
Типичными примерами констант времени выполнения являются значения, вычисляемые на основе входных данных функции, как в этом примере на C++ :
void f ( std :: string s ) { const size_t l = s . length (); // ... }
Некоторые языки программирования делают явное синтаксическое различие между константными и переменными символами, например, считая присвоение константе синтаксической ошибкой, в то время как в других языках они считаются синтаксически одинаковыми (оба просто идентификаторы), а разница в трактовке является семантической (присвоение идентификатору синтаксически допустимо, но если идентификатор является константой, то оно семантически недопустимо).
Постоянное значение определяется один раз и может ссылаться на него много раз в программе. Использование константы вместо указания одного и того же значения несколько раз может упростить поддержку кода (как в don't repeat yourself ) и может быть самодокументируемым, предоставляя осмысленное имя для значения, например, PI
вместо 3.1415926.
Существует несколько основных способов выражения значения данных, которое не изменяется во время выполнения программы, которые являются согласованными в широком спектре языков программирования. Один из самых простых способов — просто записать литеральное число, символ или строку в код программы, что является простым в C, C++ и подобных языках.
В языке ассемблера литеральные числа и символы выполняются с использованием инструкций «непосредственного режима», доступных на большинстве микропроцессоров. Название «непосредственный» происходит от значений, доступных немедленно из потока инструкций , в отличие от их косвенной загрузки путем поиска адреса памяти. [1] С другой стороны, значения, длиннее длины слова микропроцессора, такие как строки и массивы, обрабатываются косвенно, и ассемблеры обычно предоставляют псевдооперацию «данные» для встраивания таких таблиц данных в программу.
Другой способ — определение символического макроса . Многие языки программирования высокого уровня и многие ассемблеры предлагают макрос, с помощью которого программист может определить, как правило, в начале исходного файла или в отдельном файле определения, имена для различных значений. Затем препроцессор заменяет эти имена соответствующими значениями перед компиляцией, что приводит к чему-то функционально идентичному использованию литералов, с преимуществами скорости непосредственного режима. Поскольку может быть сложно поддерживать код, в котором все значения записаны буквально, если значение используется каким-либо повторяющимся или неочевидным образом, оно часто именуется макросом.
Третий способ — объявление и определение переменной как «константы». Глобальная переменная или статическая переменная может быть объявлена (или символ определен в сборке) с ключевым словом-квалификатором, таким как const
, constant
или final
, что означает, что ее значение будет установлено во время компиляции и не должно быть изменено во время выполнения. Компиляторы обычно помещают статические константы в текстовый раздел объектного файла вместе с самим кодом, в отличие от раздела данных, где хранятся неконстантные инициализированные данные. Некоторые компиляторы могут создавать раздел, специально предназначенный для констант. К этой области может быть применена защита памяти, чтобы предотвратить перезапись таких констант ошибочными указателями.
Эти константы отличаются от литералов несколькими способами. Компиляторы обычно помещают константу в одну ячейку памяти, идентифицированную символом, а не распределяют ее по всему исполняемому файлу, как в случае макроса. Хотя это исключает преимущества в скорости непосредственного режима, есть преимущества в эффективности памяти, и отладчики могут работать с этими константами во время выполнения. Кроме того, хотя макросы могут быть случайно переопределены конфликтующими заголовочными файлами в C и C++, конфликтующие константы обнаруживаются во время компиляции.
В зависимости от языка константы могут быть нетипизированными или типизированными. В C и C++ макросы обеспечивают первое, а const
второе:
#определить ПИ 3.1415926535константа float pi2 = 3.1415926535 ;
в то время как в Аде существуют универсальные числовые типы, которые можно использовать при желании:
Пи : константа := 3,1415926535 ;pi2 : константа с плавающей точкой := 3.1415926535 ;
при этом нетипизированный вариант неявно преобразуется в соответствующий тип при каждом использовании. [2]
Помимо статических констант , описанных выше, многие процедурные языки, такие как Ada и C++, расширяют концепцию константности на глобальные переменные, которые создаются во время инициализации, локальные переменные, которые автоматически создаются во время выполнения в стеке или в регистрах, на динамически выделяемую память, доступ к которой осуществляется с помощью указателя, и на списки параметров в заголовках функций.
Динамически значимые константы не определяют переменную как находящуюся в определенной области памяти, а также не устанавливают значения во время компиляции. В коде C++, таком как
float func ( const float ANYTHING ) { const float XYZ = someGlobalVariable * someOtherFunction ( ANYTHING ); ... }
выражение, которым инициализируется константа, само по себе не является константой. Использование константности здесь не является необходимым для законности программы или семантической корректности, но имеет три преимущества:
Динамически оцениваемые константы появились как языковая функция в ALGOL 68. [ 3] Исследования кода Ada и C++ показали, что динамически оцениваемые константы используются нечасто, как правило, для 1% или менее объектов, тогда как их можно было бы использовать гораздо чаще, поскольку около 40–50% локальных объектов, не являющихся классами, фактически инвариантны после создания. [3] [4] С другой стороны, такие «неизменяемые переменные» имеют тенденцию быть значениями по умолчанию в функциональных языках, поскольку они отдают предпочтение стилям программирования без побочных эффектов (например, рекурсии) или делают большинство объявлений неизменяемыми по умолчанию, например, ML . Чисто функциональные языки даже полностью запрещают побочные эффекты.
Константа часто используется в объявлениях функций как обещание того, что при передаче объекта по ссылке вызываемая функция не изменит его. В зависимости от синтаксиса константой может быть как указатель, так и объект, на который она указывает, однако обычно желательно последнее. Особенно в C++ и C дисциплина обеспечения того, чтобы соответствующие структуры данных были постоянными на протяжении всей программы, называется const-correctness .
В C/C++ можно объявить параметр функции или метода как константу. Это гарантирует, что этот параметр не может быть непреднамеренно изменен после его инициализации вызывающей стороной. Если параметр является предопределенным (встроенным) типом, он вызывается по значению и не может быть изменен. Если это пользовательский тип, переменная является адресом указателя, который также не может быть изменен. Однако содержимое объекта может быть изменено без ограничений. Объявление параметров как констант может быть способом сообщить, что это значение не должно изменяться, но программист должен помнить, что проверки на предмет изменения объекта не могут быть выполнены компилятором.
Помимо этой возможности, в C++ также возможно объявить функцию или метод как const
. Это не позволяет таким функциям или методам изменять что-либо, кроме локальных переменных.
В C# ключевое слово const
существует, но не имеет того же эффекта для параметров функции, как в C/C++. Однако есть способ «заставить» компилятор выполнить проверку, хотя это и немного сложно. [5]
Постоянная структура данных или объект называется « неизменяемым » в объектно-ориентированном жаргоне. Неизменяемость объекта дает некоторые преимущества при проектировании программы. Например, его можно «скопировать», просто скопировав его указатель или ссылку, избежав при этом трудоемкой операции копирования и сэкономив память.
Объектно-ориентированные языки, такие как C++, расширяют константность еще больше. Отдельные члены структуры или класса могут быть сделаны константными, даже если класс не является таковым. И наоборот, mutable
ключевое слово позволяет изменять член класса, даже если объект был создан как const
.
Даже функции могут быть константными в C++. Здесь имеется в виду, что только константная функция может быть вызвана для объекта, инстанцированного как константа; константная функция не изменяет никакие неизменяемые данные.
В C# есть как квалификатор a, const
так и квалификатор a readonly
; его const предназначен только для констант времени компиляции, тогда как readonly
может использоваться в конструкторах и других приложениях времени выполнения.
В Java есть квалификатор final
, который предотвращает изменение ссылки и гарантирует, что она никогда не будет указывать на другой объект. Это не предотвращает изменения самого объекта, на который она ссылается. В Java final
это в основном эквивалентно const
указателю в C++. Он не предоставляет других возможностей const
.
В Java квалификатор final
указывает, что затронутый член данных или переменная не могут быть назначены, как показано ниже:
final int i = 3 ; i = 4 ; // Ошибка! Невозможно изменить "конечный" объект
Он должен быть разрешим компиляторами, где final
инициализируется переменная с маркером, и он должен быть выполнен только один раз, иначе класс не скомпилируется. Ключевые слова Java final
и C++ const
имеют одинаковое значение при применении с примитивными переменными.
const int i = 3 ; // Декларация C++ i = 4 ; // Ошибка!
Рассматривая указатели, final
ссылка в Java означает нечто похожее на const
указатель в C++. В C++ можно объявить «константный тип указателя».
Foo * const bar = mem_location ; // константный тип указателя
Здесь, bar
должен быть инициализирован во время объявления и не может быть изменен снова, но то, на что он указывает, можно изменить. То есть является допустимым. Он просто не может указывать на другое место. Конечные ссылки в Java работают так же, за исключением того, что они могут быть объявлены неинициализированными.*bar = value
final Foo i ; // объявление Java
Примечание: Java не поддерживает указатели. [6] Это связано с тем, что указатели (с ограничениями) являются способом доступа к объектам по умолчанию в Java, и Java не использует звездочки для их обозначения. Например, i в последнем примере является указателем и может использоваться для доступа к экземпляру.
В C++ также можно объявить указатель на данные, доступные «только для чтения».
константа Foo * bar ;
Здесь bar
можно изменить значение так, чтобы оно указывало на что угодно и когда угодно; просто указанное значение нельзя изменить с помощью bar
указателя.
В Java нет эквивалентного механизма. Таким образом, нет и const
методов. В Java нельзя обеспечить константную корректность, хотя с помощью интерфейсов и определения интерфейса только для чтения для класса и его передачи можно гарантировать, что объекты могут передаваться по системе таким образом, что их нельзя будет изменить.
Каркас коллекций Java предоставляет способ создания неизменяемой оболочки для Collection
переходов и подобных методов.Collections.unmodifiableCollection()
Метод в Java может быть объявлен «final», что означает, что его нельзя переопределить в подклассах.
В C# квалификатор readonly
имеет тот же эффект на элементы данных, что и final
в Java, а const
модификатор const
имеет эффект, аналогичный (но типизированный и в области действия класса) эффекту #define
в C++. Другой, подавляющий наследование эффект Java final
при применении к методам и классам, вызывается в C# с помощью ключевого слова sealed
.
В отличие от C++, C# не позволяет помечать методы и параметры как const
. Однако можно также передавать подклассы только для чтения, а .NET Framework предоставляет некоторую поддержку для преобразования изменяемых коллекций в неизменяемые, которые можно передавать как оболочки только для чтения.
Обработка констант существенно различается в зависимости от парадигмы программирования . Корректность констант является проблемой в императивных языках, таких как C++, поскольку по умолчанию привязки имен обычно создают переменные , которые могут меняться, как следует из названия, и, таким образом, если кто-то хочет пометить привязку как константу, это требует некоторого дополнительного указания. [b] В других парадигмах языков программирования возникают связанные проблемы, при этом найдены некоторые аналоги корректности констант.
В функциональном программировании данные обычно являются константами по умолчанию, а не переменными по умолчанию. Вместо того, чтобы присваивать значение переменной (пространству хранения с именем и потенциально переменным значением), создается привязка имени к значению, например, с помощью конструкции let
во многих диалектах Lisp . В некоторых функциональных языках, особенно многопарадигменных, таких как Common Lisp , изменение данных является обычным делом, в то время как в других его избегают или считают исключением; так обстоит дело в Scheme (другом диалекте Lisp), который использует set!
конструкцию для изменения данных, с восклицательным знаком !, привлекающим внимание к этому. Такие языки достигают целей константной корректности по умолчанию, привлекая внимание к модификации, а не к константности.
В ряде объектно-ориентированных языков существует концепция неизменяемого объекта , которая в частности используется для базовых типов, таких как строки; яркими примерами являются Java, JavaScript, Python и C#. Эти языки различаются тем, могут ли определяемые пользователем типы быть помечены как неизменяемые, и могут позволять помечать как неизменяемые отдельные поля (атрибуты) объекта или типа.
В некоторых многопарадигменных языках, которые допускают как объектно-ориентированный, так и функциональный стили, обе эти функции могут быть объединены. Например, в OCaml поля объектов по умолчанию неизменяемы и должны быть явно помечены ключевым словом mutable
, чтобы быть изменяемыми, в то время как в Scala привязки явно неизменяемы, если определены с помощью val
for "value" и явно изменяемы, если определены с помощью var
for "variable".
Соглашения об именовании констант различаются. Некоторые просто называют их так же, как и любые другие переменные. Другие используют заглавные буквы и подчеркивания для констант способом, похожим на их традиционное использование для символических макросов, например SOME_CONSTANT
. [7] В венгерской нотации префикс "k" обозначает константы, а также макросы и перечислимые типы .
Одно из обязательных соглашений заключается в том, что в Ruby любая переменная, начинающаяся с заглавной буквы, считается константой, включая имена классов.
[...] Затем вы можете объявить методы, тип параметра которых "сообщает", планирует ли он изменять переменную или нет:. [...] Это имитирует проверки во время компиляции, похожие на проверки на константность в C++. Как правильно заметил Эрик Липперт, это не то же самое, что неизменяемость. Но как программист на C++, я думаю, вы это знаете.