Подпрограммы
Подпрограммы
До сих пор мы использовали процедуры, в основном, чтобы расположить там кода для исполнения. Процедуры являются одним из видов подпрограмм.
В Аде есть два вида подпрограмм: функции и процедуры. Различие между ними заключается в том, что функция возвращает значение, а процедура - нет.
В этом примере показано объявление и определение функции:
Подпрограммы в Аде, конечно, могут иметь параметры. Одно важное замечание касающееся синтаксиса заключается в том, что подпрограмма, у которой нет параметров, вообще не имеет раздела параметров, например:
procedure Proc;
function Func return Integer;
Вот еще один вариант предыдущего примера:
В этом примере мы видим, что параметры могут иметь значения по умолчанию. При вызове подпрограммы вы можете опустить параметры, если они имеют значение по умолчанию. В отличие от C/C++, вызов подпрограммы без параметров не использует скобки.
Вот реализация функции, описанной выше:
В наборе инструментов GNAT
Стандарт языка Ада не регламентирует в каких файлах следует расположить
спецификацию и тело подпрограммы. Другими словами, стандарт не навязывает
какую-либо структуру организации файлов или расширения имен файлов.
К примеру, мы могли бы сохранить и спецификацию и тело указаной выше функции
Increment
в файле с названием increment.txt
.
(Мы даже могли бы поместить весь исходный код системы в один файл.)
С точки зрения стандарта это вполне допустимо.
С другой стороны, набор инструментов GNAT требует следующую схему наименования файлов:
файлы с расширением .ads содержат спецификацию, тогда, как
файлы с расширением .adb содержат реализацию.
Таким образом, для инструментария GNAT, спецификация функции
Increment
должна находиться в файле increment.ads
, а
ее реализация должна находиться в файле increment.adb
.
Это правило также применяется для пакетов, которые мы обсудим
позже.
(Отметим, однако, что это правило можно обойти.)
Дополнительные детали смотрите в курсе
Introduction to GNAT Toolchain
или в
GPRbuild User’s Guide.
Вызовы подпрограмм
Далее мы можем вызвать нашу подпрограмму следующим образом:
Ада позволяет вам выполнять указывать имена параметров при передачи их во время вызова, независимо от того, есть ли значения по умолчанию или нет. Но есть несколько правил:
Позиционные параметры должны идти первыми.
Позиционный параметр не может следовать за именованным параметром.
Как правило, пользователи используют именованные параметры во время вызова, если соответствующий параметр функции имеет значение по умолчанию. Однако также вполне приветствуется использовать вызов с именованием каждого параметра, если это делает код более понятным.
Вложенные подпрограммы
Как кратко упоминалось ранее, Ада позволяет вам объявлять одну подпрограмму внутри другой.
Это полезно по двум причинам:
Это позволяет вам получить более понятную программу. Если вам нужна подпрограмма только как «помощник» для другой подпрограммы, то принцип локализации указывает, что подпрограмма-помощник должна быть объявлена вложенной.
Это облегчает вам доступ к данным объемлющей подпрограммы и сохранить при этом контроль, потому что вложенные подпрограммы имеют доступ к параметрам, а также к любым локальным переменным, объявленным во внешней области.
Используя предыдущий пример, можно переместить часть кода
(вызов Put_Line
) в отдельную процедуру и избежать
дублирования. Вот укороченная версия с вложенной
процедурой Display_Result
:
Вызов функций
Важной особенностью вызовов функций в Аде является то, что возвращаемое значение при вызове нельзя игнорировать; то есть вызов функции не может использоваться как оператор.
Если вы хотите вызвать функцию и вам не нужен ее результат, вам все равно нужно будет явно сохранить его в локальной переменной.
В наборе инструментов GNAT
В GNAT, когда все предупреждения активированы, становится еще сложнее игнорировать результат функции, потому что неиспользуемые переменные будут выявлены. Например, этот код будет недействительным:
function Read_Int
(Stream : Network_Stream;
Result : out Integer) return Boolean;
procedure Main is
Stream : Network_Stream := Get_Stream;
My_Int : Integer;
-- Warning: in the line below, B is
-- never read.
B : Boolean := Read_Int (Stream, My_Int);
begin
null;
end Main;
Затем у вас есть два решения, чтобы отключить это предупреждение:
Либо аннотировать переменную с помощью pragma Unreferenced, таким образом:
B : Boolean := Read_Int (Stream, My_Int);
pragma Unreferenced (B);
Или дайть переменной имя, которое содержит любую из строк:
discard
dummy
ignore
junk
unused
(без учета регистра)
Виды параметров
До сих пор мы видели, что Ада - это язык, ориентированный на безопасность. Существует много механизмов реализации этого принципа, но два важных момента заключаются в следующем:
Ада позволяет пользователю как можно более точно указать ожидаемое поведение программы, чтобы компилятор мог предупреждать или отклонять при обнаружении несоответствия.
Ада предоставляет множество методов для достижения общности и гибкости указателей и динамического управления памятью, но без недостатков последнего (таких как утечка памяти и висячие ссылки).
Виды параметров - это возможность, которая помогает воплотить эти два момента на практике. Параметр подпрограммы может быть одного из следующих видов:
|
Параметр может быть только считан, но не записан |
|
Параметр можно записать, а затем прочитать |
|
Параметр может быть, как считан, так и записан |
По умолчанию вид параметра будет in
; до сих пор большинство
примеров использовали параметры вида in
.
Исторически
Функции и процедуры изначально были более разными по философии. До Ада
2012 функции могли принимать только «входящие» (in
) параметры.
Вызов процедуры
Параметры in
Первый вид параметра - это тот, который мы неявно использовали до сих пор. Параметры этого вида нельзя изменить, поэтому следующая программа вызовет ошибку:
Тот факт, что это вид используется по умолчанию, сам по себе очень важен. Это означает, что параметр не будет изменен, если вы явно не укажете ему другой вид, для которого разрешено изменение.
Параметры in out
Для исправления кода, приведенного выше, можно использовать параметр in out
.
Параметр in out
обеспечивает доступ для чтения и записи к объекту,
переданному в качестве параметра, поэтому в приведенном выше примере
видно, что значение A
изменяется после вызова функции Swap
.
Attention
В то время как параметры in out
немного похожи на ссылки в C++ или
обычные параметры в Java, которые передаются по ссылке, стандарт языка
Ада не требует передачи параметров in out
"по ссылке", за исключением
определенных категорий типов, как будет объяснено позже.
В общем, лучше думать о видах параметров как о более высоком уровне, чем о
семантике «по значению» или «по ссылке». Для компилятора это означает, что
массив, передаваемый в качестве параметра in
, может передаваться по
ссылке, поскольку это более эффективено (что ничего не меняет для
пользователя, поскольку параметр не может быть назначен). Однако
параметр дискретного типа всегда будет передаваться копией, независимо
от его вида (ведь так более эффективено на большинстве архитектур).
Параметры out
Вид «out
» применяется, когда подпрограмме необходимо выполнить запись в
параметр, который может быть не инициализирован в момент вызова.
Чтение значения выходного параметра разрешено, но оно должно
выполняться только после того, как подпрограмма присвоила значение
параметру. Параметры out
немного похожи на возвращаемые значения
функций. Когда подпрограмма возвращается, фактический параметр
(переменная) будет иметь значение параметра в точке возврата.
На других языках
Ада не имеет конструкции кортежа и не позволяет возвращать несколько значений из подпрограммы (за исключением объявления полноценного типа записи). Следовательно, способ вернуть несколько значений из подпрограммы состоит в использовании параметров out.
Например, процедура считывания целых чисел из сети может иметь одну из следующих спецификаций:
procedure Read_Int
(Stream : Network_Stream;
Success : out Boolean;
Result : out Integer);
function Read_Int
(Stream : Network_Stream;
Result : out Integer) return Boolean;
При чтении переменной out
до записью в нее в идеале должна
возникать ошибка, но, если бы было введено такое правило, то это бы привело
либо к неэффективным проверкам во время выполнения, либо к очень сложным
правилам во время компиляции. Таким образом, с точки зрения пользователя
параметр out
действует как неинициализированная в момент
вызова подпрограммы переменная.
В наборе инструментов GNAT
GNAT обнаружит простые случаи неправильного использования параметров
out
. Например, компилятор выдаст предупреждение для следующей
программы:
Предварительное объявление подпрограмм
Как мы видели ранее, подпрограмма может быть объявлена без полного определения. Это возможно в целом и может быть полезно, если вам нужно, чтобы подпрограммы были взаимно рекурсивными, как в примере ниже:
Переименование
Подпрограммы можно переименовать, используя ключевое слово renames
и объявив
новое имя для подпрограммы:
procedure New_Proc renames Original_Proc;
Это может быть полезно, например, для улучшения читаемости вашей программы, когда вы используете код из внешних источников, который нельзя изменить в вашей системе. Давайте посмотрим на пример:
Как следует из названия процедуры, мы
не можем изменить его. Однако мы можем переименовать процедуру во
что-то вроде Show
в нашем тестовом приложении и использовать это более
короткое имя. Обратите внимание, что мы также должны объявить все
параметры исходной подпрограммы, но мы можем именовать их по другому при
объявлении. Например:
Обратите внимание, что исходное имя
(A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed
)
по-прежнему остается доступно после объявления процедуры Show
.
Можно также переименовать подпрограммы из стандартной библиотеки.
Например, можно переименовать Integer'Image
в Img
:
Переименование также позволяет вводить выражения по умолчанию, которые
не были указаны в исходном объявлении. Например, можно задать
"Hello World!"
в качестве значения по умолчанию для параметра
String
процедуры Show
:
with A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;
procedure Show_Renaming_Defaults is
procedure Show (S : String := "Hello World!")
renames
A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;
begin
Show;
end Show_Renaming_Defaults;