Переменные, константы и присваивание :=
Переменные и константы в Ada: почему присваивание обозначается :=, как объявить неизменяемое значение, что такое инициализация и почему язык так педантичен к объявлениям.
Объявление в Ada связывает имя с типом (и, возможно, начальным значением); переменная может меняться, константа — нет, и это решается при объявлении.
Форма объявления
Объявление переменной в Ada читается почти как предложение: «имя — это значение такого-то типа». Синтаксис: имя, двоеточие, тип, точка с запятой.
Count : Integer;
Temperature : Float;
Letter : Character;
Здесь объявлены три переменные: целое Count, число с плавающей точкой Temperature и символ Letter. Двоеточие : читается как «имеет тип». Можно объявить несколько переменных одного типа списком через запятую:
X, Y, Z : Integer;
Обратите внимание на дисциплину: в Ada вы обязаны указать тип. Нет вывода типа «угадай сам по значению», как в некоторых современных языках. Это намеренно: явный тип — часть документации и контракта. Читая объявление, вы сразу знаете природу величины, а компилятор получает информацию для проверок.
Присваивание: знак :=
А вот и одна из самых узнаваемых черт Ada (унаследованная от Algol и Pascal): присваивание обозначается составным символом :=, а не одиночным =.
Count := 10;
Temperature := 36.6;
Letter := 'A';
Читается это как «Count становится равным 10». Почему так? Потому что в Ada (как в математике) символ = зарезервирован за сравнением на равенство, а не за присваиванием. Это устраняет классический источник ошибок из языков С-семейства, где = (присвоить) и == (сравнить) легко перепутать, и опечатка вида if (x = 5) компилируется с неожиданным смыслом. В Ada такая путаница невозможна: := и = — синтаксически разные вещи, и поставить присваивание там, где ждут условие, просто не получится. Запомните накрепко: := — присвоить, = — проверить равенство. В HTML двоеточие и знак равенства не требуют экранирования, но рядом часто стоят <= и >=, которые экранировать нужно.
Символьный литерал пишется в одинарных кавычках: 'A' — это символ, тогда как "A" в двойных кавычках — это строка из одного символа. Различие строгое, перепутать типы нельзя.
Инициализация при объявлении
Объявление и первое присваивание можно совместить — это называется инициализацией:
Count : Integer := 0;
Temperature : Float := 36.6;
Greeting : String := "Привет";
Это удобно и поощряется: переменная сразу получает осмысленное значение. Здесь возникает важная для Ada тема — неинициализированные переменные. Если объявить Count : Integer; без начального значения, его содержимое не определено (мусор из памяти). Чтение такой переменной до первого присваивания — ошибка программиста. Хорошая практика Ada — инициализировать сразу либо присвоить значение до первого чтения. Более того, инструменты Ada (и тем более SPARK) умеют обнаруживать использование неинициализированных данных, потому что это классический источник коварных багов. Философия языка: лучше явно задать начало, чем полагаться на случайное содержимое памяти.
Константы: значение, которое нельзя менять
Чтобы объявить величину, которая не должна меняться после установки, добавляют слово constant между двоеточием и типом, обязательно с инициализацией:
Pi : constant Float := 3.141_592_653;
Max_Retries : constant Integer := 5;
App_Name : constant String := "FlightCtl";
После такого объявления попытка присвоить константе новое значение — Pi := 3.0; — будет отвергнута компилятором. Это не пожелание, а гарантия. Зачем это нужно? Во-первых, ради безопасности: значения, которые по смыслу постоянны (физические константы, лимиты, имена), защищены от случайного изменения. Во-вторых, ради читаемости: видя constant, читатель сразу понимает, что величина фиксирована и можно на неё опираться. В-третьих, компилятор может оптимизировать работу с константами. Подчёркивания в 3.141_592_653 снова чисто косметические — группируют цифры.
Именованные числа: константы без типа
У Ada есть тонкая, но полезная разновидность констант — именованные числа (named numbers). Если присвоить имени числовой литерал без указания конкретного типа, получится «универсальная» константа, совместимая с любым подходящим числовым типом:
Speed_Of_Light : constant := 299_792_458; -- универсальное целое
Half : constant := 0.5; -- универсальное вещественное
Поскольку тип не указан, Speed_Of_Light можно использовать там, где ожидается любой целочисленный тип, без преобразований. Это удобно для математических констант, которые должны «дружить» с разными типами. Контраст: Pi : constant Float := 3.14; привязан именно к Float, а Half : constant := 0.5; — универсален. Эта деталь отражает заботу Ada о точной работе с числами, к которой мы вернёмся в разделе про типы.
Как работает под капотом строгость объявлений
Почему Ada требует объявлять всё заранее и с типом? Потому что это даёт компилятору полную картину до начала исполнения. Зная тип каждой переменной, он проверяет совместимость каждого присваивания, каждой операции. Зная, что нечто — константа, он запрещает её менять. Зная диапазон (об этом дальше), он контролирует границы. Вся эта информация — статическая, известная при компиляции, и потому ошибки ловятся до запуска. Сравните с языками, где переменная возникает в момент первого присваивания и может в разные моменты хранить разные типы: там компилятор почти не может вам помочь, и ошибки всплывают в рантайме у пользователя. Ada выбирает противоположный полюс: максимум информации заранее, максимум проверок до запуска.
Покажем небольшую осмысленную процедуру, собирающую всё вместе:
with Ada.Text_IO;
with Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;
procedure Circle_Area is
Radius : constant Integer := 5;
Area : Integer;
begin
Area := 3 * Radius * Radius; -- грубая оценка площади
Put ("Площадь примерно: ");
Put (Area);
New_Line;
end Circle_Area;
Здесь Radius — константа (радиус не меняется), Area — переменная, которую мы вычисляем. Put из Ada.Integer_Text_IO печатает целое, New_Line переводит строку. Обратите внимание на оговорку use Ada.Text_IO, Ada.Integer_Text_IO; — через запятую можно открыть сразу несколько пакетов.
Блоки declare и время жизни переменных
Раз переменные нельзя объявлять посреди операторов, возникает естественный вопрос: а что если временная переменная нужна только в середине процедуры, для одного локального вычисления? Заводить её в общей зоне объявлений наверху — значит расширять её область жизни шире необходимого, что считается дурным тоном (чем уже область переменной, тем меньше шанс случайной ошибки). Ada решает это блоком declare — локальной областью прямо внутри тела:
begin
Put_Line ("Начало");
declare
Temp : Integer := Compute_Something; -- живёт только в этом блоке
Doubled : Integer := Temp * 2;
begin
Put_Line ("Результат:" & Integer'Image (Doubled));
end;
Put_Line ("Конец"); -- здесь Temp уже не существует
end;
Блок declare ... begin ... end; вводит собственную маленькую зону объявлений и собственное тело. Переменные Temp и Doubled рождаются при входе в блок и исчезают при выходе из него — за его пределами их имена не видны и память освобождается. Это даёт точный контроль над временем жизни: переменная существует ровно там, где нужна, и ни строкой дольше. Принцип «минимальной области видимости» — важная привычка надёжного программирования: чем короче живёт переменная, тем меньше кода может её случайно испортить и тем легче рассуждать о её значении.
Блоки declare особенно ценны вместе с инициализацией: переменная блока обычно сразу получает осмысленное значение из окружающего контекста, как Temp в примере. Это устраняет окно, в котором переменная существует, но ещё не инициализирована. Кроме того, блок может иметь имя (как именованные циклы) и собственную обработку исключений — то есть это полноценная структурная единица, а не просто косметика. В сумме константы, инициализация и блоки declare образуют связную систему управления данными: значение объявляется как можно ближе к месту использования, инициализируется сразу и защищается от изменения, если по смыслу постоянно. Эта дисциплина — не формальность, а практический заслон от целого спектра ошибок, связанных с «живущими слишком долго» и «неинициализированными» переменными, которые в менее строгих языках являются хроническим источником багов.
Полезно увидеть и то, как из этих кирпичиков складывается типичный осмысленный фрагмент: константы вверху как «настройки», рабочие переменные с инициализацией, локальные блоки для промежуточных расчётов. Опытный разработчик Ada читает такой код как хорошо организованный документ — сразу видно, что неизменно (константы), что вычисляется (переменные) и где границы каждой величины (блоки). Эта дисциплина не возникает сама собой в языках, где переменную можно объявить где угодно и менять что угодно; Ada же подталкивает к ней самой своей структурой. В результате даже простая процедура получается самодокументирующейся: расположение и форма объявлений несут смысл. Для кода, который будут читать и сопровождать годами незнакомые люди, такая встроенная организованность — не косметика, а практический вклад в надёжность, потому что понятный код легче проверять и труднее сломать незаметной правкой.
Частые ошибки и заблуждения
- Писать
=вместо:=для присваивания. В Ada=— это сравнение; присваивание всегда:=. Перепутать невозможно по синтаксису, но новички пишут по привычке. - Забыть инициализировать. Неинициализированная переменная содержит мусор; читать её до первого присваивания — ошибка. Инициализируйте сразу, где можно.
- Пытаться менять константу. Присваивание
constant-величине отвергается компилятором — это и есть смысл константы. - Путать
'A'и"A". Одинарные кавычки — символ (Character), двойные — строка (String); это разные типы. - Ожидать вывод типа. Ada требует явно указывать тип в объявлении; «угадывания» по значению нет.
Итоги
- Объявление имеет форму «Имя : Тип;» и обязательно указывает тип — вывода типов нет, это часть документации и контракта.
- Присваивание обозначается
:=, а=зарезервировано за сравнением — это устраняет классическую путаницу языков С-семейства. - Инициализация при объявлении (
X : Integer := 0;) поощряется; неинициализированные переменные опасны и отслеживаются инструментами. - Слово
constantделает величину неизменяемой, и компилятор гарантирует запрет на её изменение. - Именованные числа без типа (
constant := 0.5;) дают универсальные числовые константы, совместимые с разными типами.