"Адское" программирование Ada-95 -Компилятор GNAT

         

Абстрактные типы и подпрограммы



Абстрактные типы и подпрограммы

В большинстве случаев, типы которые разрабатываются как родоначальники иерархий производных типов не предназначены для непосредственного описания объектов (переменных).Такие типы называют абстрактными, а для их описания используется зарезервированное слово abstract.Например:

type Empty_Root is abstract tagged null record;type Simple_Root is abstract tagged record Simple_Field: Integer; end record;

Здесь, в первом случае, тип Empty_Root - это абстрактный тип является "пустой" записью которая не содержит никаких полей. В свою очередь, тип Simple_Root, который также описан как абстрактный, содержит единственное поле Simple_Field типа Integer.

Абстрактный тип может иметь абстрактные подпрограммы.Абстрактными называют подпрограммы которые фактически не имеют тела (то есть реализации), а это значит, что они обязательно должны быть переопределены в производных типах.Смысл использования описания абстрактного типа с абстрактными подпрограммами заключается в том, что все производные типы, в последствии,


будут вынуждены поддерживать общую логическую функциональность.

Описание абстрактного типа имеющего абстрактные подпрограммы может иметь следующий вид:

package Sets is type Set is abstract tagged null record; function Empty return Set is abstract; function Empty(Element : Set) return Boolean is abstract; function Union(Left, Right : Set) return Set is abstract; function Intersection(Left, Right : Set) return Set is abstract; procedure Insert(Element : Natural; Into : Set) is abstract; end Sets;

Это описание множества натуральных чисел взято из справочного руководства по языку Ада.Примечательно то, что компиляор не позволит описать переменную типа Set, поскольку тип Set - это абстрактный тип.Таким образом, следующий пример не будет компилироваться:

with Sets; use Sets;procedure Wont_Compile is My_Set : Set; -- НЕДОПУСТИМО!!! абстрактный типbegin null; end Wont_Compile;

Как правило, тип, производный от абстрактного, обеспечивает реализацию функциональности которая была задана в абстрактном типе предке.Например:

with Sets;package Quick_Sets is type Bit_Vector is array(55) of Boolean; pragma Pack (Bit_Vector); type Quick_Set is new Sets.Set with record Bits : Bit_Vector := (others => False); end record; -- объявление конкретной реализации function Empty return Quick_Set; function Empty(Element : Quick_Set) return Boolean; . . .

Показанный выше схематический пример описывает тип Quick_Set, производный от абстрактного типа Set.В этом примере, тип Quick_Set осуществляет реализацию функциональности которая была задана в абстрактном типе Set.В результате, после описания типа Quick_Set, мы можем описывать и использовать объекты типа Quick_Set логическая функциональность которых будет соответствовать функциональности абстрактного типа Set.

Необходимо также заметить, что Ада позволяет чтобы вновь описываемый тип, производный от абстрактного типа, также был абстрактным.Используя такие возможности можно разработать иерархию абстрактных типов, которая будет определять логическую функциональность программной подсистемы или всего программного проекта.



Агрегаты для массивов



Агрегаты для массивов

В общем случае, агрегат массива - это совокупность значений для каждого элемента массива. Использование агрегатов позволяет выполнять одновременное присваивание значений всем элементам массива в эффективной и элегантной форме.

Рассмотрим следующий пример:

Store_1_Stock := (5, 4, 300);

В данном случае, присваивание значений элементам массива Store_1_Stock выполняется с помощью агрегата. Следует учесть, что в этом примере значения в агрегате присваиваются в порядке соответствующем следованию элементов в массиве. Такая нотация называется позиционной или неименованой, а такой агрегат - позиционный или неименованый агрегат.

Кроме позиционной нотации, возможно использование именованой нотации. В этом случае именуется каждый индивидуальный элемент массива. Используя именованую нотацию, предыдущий пример можно переписать следующим образом:

Store_1_Stock := (Dog => 5, Budgie => 4, Rabbit => 300);

Такой вид агрегата называют именованым агрегатом.

Приведем еще один пример именованого агрегата:

Store_1_Stock := (Dog | Budgie => 0, Rabbit => 100);

В пределах одного агрегата, Ада допускает использовать только один вид нотации. Это означает, что комбинирование позиционной и именованой нотации в одном агрегате - не допустимо и будет вызывать ошибку компиляции. Например:

Store_1_Stock := (5, 4, Rabbit => 300); -- это недопустимо!

В агрегате может указываться диапазон дискретных значений:

Store_1_Stock := (Dog..Rabbit => 0);

Агрегаты обоих видов удобно использовать в описаниях:

Store_1_Stock: Pet_Stock := (5, 4, 300);

С агрегатами массивов разрешается использование опции others, которая практически полезна при установке всех элементов массива в какое-либо предопределенное значение. Стоит учесть, что в таких случаях часто требуется квалификация типа.

New_Shop_Stock : Pet_Stock := (others := 0);

Рассмотрим следующие описания:

declare type Numbers1 is array(0) of Integer; type Numbers2 is array(0) of Integer; A : Numbers1; B : Numbers2; begin A := (1, 2, 3, 4, others => 5); end;

Заметьте, что в данном случае опция others используется вместе с позиционной нотацией. Поэтому Ада потребует указать квалификацию типа:

A : = Numbers1'(1, 2, 3, 4, others => 5);

В общем случае, при использовании опции others совместно с любой из двух нотаций, позиционной или именованой, требуется указывать квалификацию типа.



Агрегаты для записей



Агрегаты для записей

Так же как и в случае массива, все множество значений элементов записи может присваиваться с помощью агрегата. При этом агрегат должен предоставлять значения для всех компонентов записи даже в случаях когда некоторые компоненты обеспечены значениями по-умолчанию. Для записей различают агрегаты использующие позиционную, именованную и смешанную нотацию.

Примером использования позиционного агрегата может служить следующее:

Expensive_Bike := (Aluminium, Cannondale, Cantilever, Cantilever);

При позиционной нотации порядок следования присваиваемых значений в агрегате соответствует порядку следования полей в описании типа записи.

Альтернативно позиционной нотации, показанной выше, для присваивания таких же значений полям переменной Expensive_Bike можно применить агрегат использующий именованную нотацию. В этом случае поля записи могут перечисляться в произвольном порядке:

Expensive_Bike := ( Rear_Brake => Cantilever Front_Brake => Cantilever, Manufacturer => Cannondale, Frame => Aluminium, );

Для записей допускается смешивать в одном агрегате оба варианта нотации. При этом все позиционные значения должны предшествовать именованным значениям. Такой вариант нотации будет смешанным.

Также как и в случае агрегатов для массивов, в агрегатах для записей допускается использование опции others. При этом, для опции others должен быть представлен хотя бы один компонент, а в случае когда для опции others предоставляется более одного компонента, все компоненты должны иметь один и тот же тип.

Агрегаты являются удобным средством указания значений полей при описании переменных и констант:

Expensive_Bike : Bicycle := (Aluminium, Cannondale, Cantilever, Cantilever);

Одинаковые значения могут присваиваться разным полям записи при использовании символа '|'.

Expensive_Bike := ( Frame => Aluminium, Manufacturer => Cannondale, Front_Brake | Rear_Brake => Cantilever );

В заключение обсуждения применения агрегатов для записей рассмотрим следующий обобщающий пример:

type Summary is record Field_1 : Boolean; Field_2 : Float; Field_3 : Integer; Field_4 : Integer; end record;Variable_1 : Summary := (True, 1, 1, 1); -- позиционная нотация Variable_2 : Summary := ( -- именованная нотация Field_4 => 1 Field_3 => 1, Field_2 => 1, Field_1 => True ); Variable_2 : Summary := ( -- смешанная нотация True, 1, Field_4 => 1, Field_3 => 1 );-------------------------- использование символа '|' Variable_4 : Summary := ( True, 1, Field_3|Field_4 => 1 ); Variable_5 : Summary := ( Field_1 => True, Field_2 => 1, Field_3|Field_4 => 1 );-------------------------- использование others Variable_6 : Summary := (True, 1, others => 1); Variable_7 : Summary := ( Field_1 => True, Field_2 => 1, others => 1 );



Активация задачи



Активация задачи

Для процесса активации задач приняты следующие правла: Для статических задач, активация начинается немедленно после окончания процесса элаборации описательной части в которой эти задачи описаны. Первая инструкция, следующая за описательной частью, не будет выполняться до тех пор, пока все созданные задачи не окончат свою активацию. Задаче, перед выполнением своего тела, не требуется ожидать активацию других задач, которые созданны одновременно с ней. Задача может попытаться осуществить взаимодействие с другой задачей, которая уже создана, но еще не активирована.При этом, выполнение вызывающей задачи будет задержано до тех пор, пока не произойдет взаимодействие. Если объект-задачи описан в спецификации пакета, то он начнет свое выполнение после окончания процесса элаборации описательной части тела пакета. Динамические задачи активируются немедленно после завершения обработки создавшего их аллокатора. Задача, которая выполнила инструкцию, ответственную за создание задач, будет заблокирована до тех пор, пока созданные задачи не окончат свою активацию. Если в процессе элаборации описательной части возбуждено исключение, то любая задача, созданная в процессе элаборации становится прекращенной и никогда не активируется.В виду того, что на этом этапе задача сама по себе не может обрабатывать исключения, модель языка требует, чтобы такую ситуацию обрабатывала родительская задача или владелец задачи, поскольку будет возбуждено предопределенное исключение Tasking_Error. В случае динамического создания задач, возбуждение исключения осуществляется после инструкции которая содержит аллокатор. Однако, если вызов аллокатора находится в описательной части (как часть инициализации объекта), то описательная часть, в результате неудачи, не выполняется, а возбуждение исключения происходит в объемлющем блоке (или вызвавшей подпрограмме). В случае статического создания задачи, возбуждение исключения осуществляется перед выполнением первой исполняемой инструкции, которая следует сразу после описательной части. Это исключение возбуждается после того как осуществлена активация всех созданных задач, вне зависимости от успешности их активации, и оно, в большинстве случаев, вызывается однократно. Атрибут задачи 'Callable возвращает значение True только в случае, когда указанная задача не находится в состоянии завершенная или прекращенная.Любая задача находящаяся в ненормальном (abnormal) состоянии является принудительно завершенной задачей.Атрибут задачи 'Terminated возвращает значение True когда указанная задача находится в прекращенном состоянии.



Анонимные массивы



Анонимные массивы

Массив может быть объявлен непосредственно, без использования предопределенного типа:

No_Of_Desks : array(.No_Of_Divisions) of Integer;

В этом случае массив будет называться анонимным (поскольку он не имеет явного типа) и он будет несовместим с другими массивами - даже такими, которые описаны таким же самым образом. Кроме того, такие массивы не могут быть использованы как параметры подпрограмм. В общем случае рекомендуется избегать использования анонимных массивов.



Атрибуты



Атрибуты

Ада предусматривает специальный класс предопределенных операций, которые позволяют в удобной форме определять и использовать различные характеристики типов и экземпляров объектов. Они называются атрибутами.

Указание имени требуемого атрибута производится в конце имени экземпляра объекта (переменной, константы) или имени типа с использованием символа одинарных кавычек.

имя_типа'имя_атрибута имя_экземпляра_объекта'имя_атрибута

Некоторыми из атрибутов для дискретных типов являются:

type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC);Integer'First -- наименьшее целое Integer Integer'Last -- наибольшее целое Integer Processors'Succ(M68000) -- последующее за M68000 в типе Upper_Chars'Pred('C') -- предшествующее перед 'C' в типе ('B') Integer'Image(67) -- строка " 67" -- пробел для '-' Integer'Value("67") -- целое значение 67 Processors'Pos(M68030) -- позиция M68030 в типе -- (3, первая позиция - 0)

Простым примером использования атрибутов, - для улучшения переносимости программы, - может служить следующий пример описания подтипа Positive, предоставляющего положительные целые числа:

subtype Positive is Integer range .Integer'Last;

Здесь мы получаем размер максимального положительного целого не зависимо от любых системно зависимых свойств.

В Ada83 для не дискретных типов, - таких как Float, Fixed, всех их подтипов и производных от них типов, - концепции 'Pred и 'Succ - не имеют смысла. В Ada95 - они присутствуют. Все другие атрибуты скалярных типов также справедливы и для вещественных типов.



Атрибуты массивов



Атрибуты массивов

С массивами ассоциируются следующие атрибуты:

<имя_массива>'First -- нижняя граница массива <имя_массива>'Last -- верхняя граница массива<имя_массива>'Length -- количество элементов в массиве -- <имя_массива>'Last - <имя_массива>'First + 1<имя_массива>'Range -- подтип объявленный как -- <имя_массива>'First..<имя_массива>'Last

Эти средства очень полезны для организации перебора элементов массивов.

for Count in <имя_массива>'Range loop . . .end loop

В приведенном выше примере, каждый элемент массива будет гарантированно обработан.



Атрибуты входов защищенных объектов



Атрибуты входов защищенных объектов

Входы защищенных объектов имеют атрибуты, назначение которых подобно назначению атрибутов для входов задач:

E'Caller Возвращает значение типа Task_ID, которое идентифицирует обрабатываемую в текущий момент задачу, обратившуюся на вход защищенного объекта E. Использование этого атрибута допустимо только внутри тела входа (для защищенного объекта).
E'Count  -  Возвращает значение типа Universal_Integer, показывающее число обращений на входе E, которые находятся в очереди.

Здесь, подразумевается, что E - это имя любого входа защищенного объекта.Так же как и в случае задач, при использовании этих средств следует учитывать, что по истечении некоторого времени ничто не гарантирует активность задачи или ее присутствие в области видимости.



Библиотека и компилируемые модули



Библиотека и компилируемые модули

В общем случае, программа на языке Ада представляет собой один или несколько программных модулей, которые могут компилироваться как совместно, так и раздельно.Кроме того, программные модули являются основой построения библиотек Ады, поэтому их также называют библиотечными модулями.Программные модули бывают четырех видов:

Подпрограммы - Являются основным средством описания алгоритмов. Различают два вида подпрограмм: процедуры и функции. Процедура - это логический аналог некоторой именованной последовательности действий. Функция - логический аналог математической функции - используется для вычисления какого-либо значения.

Пакет - Основное средство для определения набора логически взаимосвязанных понятий.В простейшем случае в пакете специфицируются описания типов и общих объектов.В более общем случае в нем могут специфицироваться группы взаимосвязанных понятий, включающих подпрограммы, причем, некоторые описываемые в пакете сущности могут быть "скрыты" от пользователя, что дает возможность предоставления доступа только к тем ресурсам пакета, которые необходимы пользователю и, следовательно, должны быть для него доступны.

Задача или задачный модуль -Средство для описания последовательности действий, причем, при наличии нескольких таких последовательностей они могут выполняться параллельно.Задачи могут быть реализованы на многомашинной или многопроцессорной вычислительной конфигурации, либо на единственном процессоре в режиме разделения времени.Синхронизация достигается путем обращения ко входам, которые подобно подпрограммам могут иметь параметры, с помощью которых осуществляется передача данных между задачами.

Настраиваемые модули - Средство для параметризации подпрограмм или пакетов.В ряде случаев возникает необходимость обрабатывать объекты, которые отличаются друг от друга количеством данных, типами или какими-либо другими количественными или качественными характеристиками.Если все эти изменяемые характеристики вынести из подпрограммы или пакета, то получится некоторая заготовка (или шаблон), которую можно настроить на конкретное выполнение.Непосредственно выполнить настраиваемый модуль нельзя.Но из него можно получить экземпляр настроенного модуля (подпрограмму или пакет), который пригоден для выполнения.

Каждый программный модуль обычно состоит из двух частей: спецификации и тела. Спецификация описывает интерфейс к модулю, а тело - его реализацию.Примечательно, что спецификация и тело программного модуля являются самостоятельными компилируемыми модулями, то есть, они могут компилироваться раздельно.Разбиение модуля на спецификацию и тело, а также возможность раздельной компиляции позволяют разрабатывать, кодировать и тестировать любую программу или систему как набор достаточно независимых компонентов.Такой подход полезен при разработке больших программных систем.



Блоки



Блоки

Блок содержит последовательность инструкций, перед которой может располагаться раздел описаний (все описания локальны для блока и не доступны вне блока). За последовательностью инструкций могут следовать обработчики исключений (обработка исключений в Аде рассматривается позже). В общем случае инструкция блока Ады имеет вид:

declare -- локальные описанияbegin -- последовательность инструкцийexeption -- обработчики исключенийend;

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

Some_Block: declare X : Integer; begin X := 222 * 333; Put(X); end Some_Block;



Блокировка ресурса



Блокировка ресурса

Каноническим примером использования инициализации (Initialize) и очистки (Finalize) совместно со ссылочным дискриминантом может служить следующий пример организации монопольного доступа к общему ресурсу:

type Handle(Resource: access Some_Thing) is new Finalization.Limited_Controlled with null record;procedure Initialize (H: in out Handle) is begin Lock(H.Resource); end Initialize;procedure Finalize (H: in out Handle) is begin Unlock(H.Resource); end Finalize; . . .procedure P (T: access Some_Thing) is H: Handle(T); begin . . . -- монопольная обработка T end P;

В данном случае, суть идеи заключается в том, что описание H внутри процедуры P приводит к автоматическому вызову операции Initialize которая, в свою очередь, вызывает операцию блокировки ресурса Lock, после чего, внутри процедуры P, ресурс используется монопольно.Операция очистки Finalize, которая вызывает операцию освобождения ресурса Unlock, будет гарантированно вызвана, вне зависимости от того как произошло завершение работы процедуры P (нормальным образом, в результате исключения или в результате абортирования обработки).Подобный прием удобен для гарантированной организации работы парных операций, подобных операциям открытия и закрытия файлов.



Численные и символьные типы



Численные и символьные типы

С помощью использования следующих типов, переменные или результаты выражений Ады могут быть конвертированы в форму, совместимую с языком C, и обратно:

целочисленные типы символьные типы    вещественные типы
Int    Char    C_Float
Short    Wchar_T    Double
Long         Long_Double
Size_T          

Например, для передачи целочисленного значения переменной Item как параметра для функции языка C которая ожидает получить параметр типа long double можно использовать следующее выражение Ады Long_Double(Item).



Цикл for



Цикл for

Еще одним распространенным случаем является ситуация когда необходимо выполнить некоторые действия заданное количество раз, то есть организовать счетный цикл. Для этого Ада предусматривает конструкцию цикла for.

Конструкция цикла for Ады аналогична конструкции цикла for, представленной в языке Паскаль.

Существует несколько правил использования цикла for: тип переменной-счетчика цикла for определяется типом указываемого диапазона значений счетчика, и должен быть дискретного типа, вещественные значения - недопустимы счетчик не может быть модифицирован в теле цикла, другими словами - счетчик доступен только по чтению область действия переменной-счетчика распространяется только на тело цикла

Примечательно также, что тело цикла не будет выполняться если при указании диапазона значений переменной-счетчика величина значения "нижней границы" будет больше чем величина значения "верхней границы".

for счетчик in диапазон_значений_счетчика loop -- инструкции тела циклаend loop;for Count in 0 loop Put (Count); end loop;

Возможен перебор значений диапазона в обратном порядке:

for счетчик in reverse диапазон_значений_счетчика loop -- инструкции тела циклаend loop;for Count in reverse 0 loop Put (Count); end loop;

Любой дискретный тип может использоваться для указания диапазона значений переменной-счетчика.

declare subtype List is Integer range 0;begin for Count in List loop Put (Count); end loop; end;

Здесь, тип List был использован для указания диапазона значений переменной-счетчика Count. Подобным образом также можно использовать любой перечислимый тип.



Цикл жизни задачи



Цикл жизни задачи

До сих пор, во всех примерах, мы подразумевали только то, что создание задач осуществляется на этапе элаборации, и никак не рассматривали, что происходит с задачами в процессе их существования.Однако, при построении сложных многозадачных программ, необходимо понимание из чего состоит цикл жизни задачи, то есть, как происходит создание, активация и завершение одной отдельно взятой задачи.



Цикл while



Цикл while

Во многих случаях, прежде чем выполнять действия которые описываются инструкциями тела цикла, необходимо проверить какое-либо условие. Для таких случаев Ада предусматривает конструкцию цикла while.

Цикл while идентичен циклу while в языке Паскаль. Проверка условия выполнения цикла производится до входа в блок инструкций составляющих тело цикла. При этом, если результат вычисления логического выражения будет True, то будет выполнен блок инструкций тела цикла. В противном случае, тело цикла - не выполняется.

while логическое_выражение loop -- инструкции тела циклаend loop;

Необходимо заметить, что результат вычисления логического выражения должен иметь предопределенный тип Standard.Boolean



Динамическая диспетчеризация



Динамическая диспетчеризация

При внимательном рассмотрении показанной ранее реализации процедуры Show не трудно заметить, что хотя она и использует средства тэговых типов, позволяющие определить фактический тип объекта во время выполнения программы, аналогичный результат мог бы быть получен путем использования вместо тэговых типов обычных записей с дискриминантами.

В данном случае, подразумевается, что если мы решим описать новый тип, производный от любого типа входящего в показанную иерархию (Root, Child_1, Child_2 и Grand_Child_2_1), то результатом работы такой реализации процедуры Show всегда будет сообщение "Unknown type", извещающее о том, что фактический тип параметра Self - не известен.Например, такая ситуация может возникнуть когда мы опишем новый тип Grand_Child_1_1, производный от типа Child_1, в каком-либо другом пакете.

С первого взгляда может показаться, что будет достаточно переписать процедуру Show с учетом нового типа Grand_Child_1_1.Действительно, такое решение будет работать, но это значит, что при описании каждого нового типа, входящего в эту иерархию, мы вынуждены вносить изменения в уже написанные и отлаженные модули.

Для того, чтобы избавиться от таких трудностей, реализацию процедуры Show надо действительно переписать, но так, чтобы задействовать механизм динамической диспетчеризации вызовов подпрограмм, который предоставляет использование тэговых типов.

Вспомним, что при демонстрации примера иерархии типов, был также приведен пример спецификации пакета Simple_Objects, в котором эта иерархия типов описывается.Теперь, перед непосредственным обсуждением механизма динамической диспетчеризации вызовов подпрограмм, предположим, что описание тела этого пакета имеет следующий вид:

with Ada.Text_IO;package body Simple_Objects is -- примитивные операции типа Root function The_Name (Self: in Root) return String is begin return ("Root"); end The_Name; procedure Show (Self: in Root'Class) is begin Ada.Text_IO.Put_Line ( The_Name(Self) ); end Show; -- примитивные операции типа Child_1 function The_Name (Self: in Child_1) return String is begin return ("Child_1"); end The_Name; -- примитивные операции типа Child_2 function The_Name (Self: in Child_2) return String is begin return ("Child_2"); end The_Name; -- примитивные операции типа Grand_Child_2_1 function The_Name (Self: in Grand_Child_2_1) return String is begin return ("Grand_Child_2_1"); end The_Name;end Simple_Objects;

Не сложно догадаться, что особое внимание следует обратить на реализацию процедуры Show, которая теперь, перед вызовом Ada.Text_IO.Put_Line, выдающим строку сообщения, вызывает функцию The_Name, возвращающую строку которая, собственно, содержит текст сообщения.Заметим также, что важным моментом в этой реализации является то, что процедура Show имеет формальный параметр надклассового типа, что позволяет ей принимать в качестве фактического параметра любые объекты тип которых принадлежит данной иерархии типов.

При дальнейшем внимательном рассмотрении спецификации и тела пакета Simple_Objects, следует обратить внимание на то, что функция The_Name описана для всех типов иерархии (Root, Child_1, Child_2 и Grand_Child_2_1) и является примитивной операцией для этих типов.Напомним, что в случае тэговых типов, примитивные операции являются потенциально диспетчеризуемыми операциями.

В результате, процедура Show, принимая во время выполнения программы фактический параметр, осуществляет диспетчеризацию вызова соответствующей реализации функции The_Name на основании информации о тэге фактического параметра.Это значит, что в данном случае присутствует динамическое связывание которое обеспечивает динамическую полиморфность поведения объектов.

Таким образом, для выполнения диспетчеризации вызова подпрограммы во время выполнения программы должно соблюдаться два условия: подпрограмма должна иметь формальный параметр тэгового типа при вызове подпрограммы, фактический параметр, соответствующий параметру тэгового типа должен быть объектом надклассового типа

Следовательно, если фактический надклассовый тип представляет объекты двух или более индивидуальных типов, которые имеют подпрограммы с одинаковыми параметрами (исключая параметр надклассового типа), то определение реализации подпрограммы на этапе компиляции (иначе, статическое связывание) не возможно.Вместо этого, осуществляется динамическое связывание во время выполнения программы.

Возвращаясь к проблеме с которой мы начинали обсуждение диспетчеризации, следует заметить, что использование этого механизма в процедуре Show, позволяет теперь описывать новые типы, производные от любого типа входящего в иерархию, без необходимости изменения реализации процедуры Show.Действительно, при описании нового производного типа, достаточно описать соответствующую реализацию функции The_Name для нового типа, не внося никаких изменений в ранее написанные модули.Заметим также, что если при описании нового производного типа не будет предусмотрена реализация функции The_Name, то для этого типа процедура Show, используя диспетчеризацию, будет вызывать реализацию функции The_Name унаследованную от типа предка.

Мы рассмотрели случай динамической диспетчеризации в подпрограмме при описании которой используется формальный параметр надклассового типа.Следует заметить, что вызов подпрограммы с параметром надклассового ссылочного типа также является диспетчеризуемым:

... The_Name(Any_Instance.all)... -- Any_Instance может обозначать объект который принадлежит любому -- типу класса Root'Class

Еще одним случаем диспетчеризации является использование переменной надклассового типа. В этом случае нужно обратить внимание на отличие диспетчеризуемого вызова от обычного вызова:

declare Instance_1 : Root; Instance_2 : Root'Class ... ; begin ... The_Name(Instance_1)... -- статическое связывание: компилятор знает индивидуальный тип -- Instance_1, поэтому он может определить реализацию ... The_Name(Instance_2)... -- динамическое связывание: Instance_2 может принадлежать любому -- типу класса Root'Class -- поэтому компилятор не может точно определить реализацию -- подпрограммы, она будет выбрана во время выполнения программы end ;



Динамическая передиспетчеризация



Динамическая передиспетчеризация

Известно, что производный тип всегда может переопределить примитивную операцию своего предка, то есть, предусмотреть свою собственную реализацию примитивной операции.В случае использования классов, необходимо заботиться в правильном распознавании вызова реализации для потомка от вызова реализации для предка.Рассмотрим следующий схематический вариант реализации процедуры Display для типа Root:

procedure Display (Self: in Root) is begin Put ( The_Name(Self) & ... ); -- ??? . . . end Display;

В данном случае, при вызове реализации Display для типа Child_1, которая была показана ранее, первая же инструкция выполняет вызов затененной реализации предка, то есть, реализации для типа Root: Display ( Root(Self) ).

Показанный пример реализации процедуры Display для типа Root всегда будет выполнять вызов функции The_Name полагая, что индивидуальным типом параметра Self является тип Root.Таким образом, этот вызов будет статически связан с реализацией функции The_Name для типа Root.

Следовательно, при вызове реализации процедуры Display для типа Child_1, в результате вызова затененной реализации предка будет получена та же строка имени, что и для типа Root.

Для того, чтобы процедура Display для типа Root правильно осуществила вызов той реализации функции The_Name которая соответствует фактическому индивидуальному типу параметра Self ее необходимо переписать следующим образом:

procedure Display (Self: in Root) is begin Put ( The_Name( Root'Class(Self) ) & ... ); -- передиспетчеризация . . . end Display;

В данном случае, в реализации процедуры Display для типа Root выполняется преобразование параметра Self к типу Root'Class предусматривая передиспетчеризацию вызова к корректной реализации The_Name, в зависимости от тэга параметра Self.



Динамические массивы



Динамические массивы

Ада позволяет не указывать размеры массива при написании программы. В этом случае размеры массива не фиксируются во время компиляции программы, а определяются во время ее выполнения, что во многих случаях более предпочтительно. Массивы подобного вида известны как динамические массивы. Кроме того, в отличие от многих других языков программирования, Ада позволяет использование динамических массивов в качестве значения результата, возвращаемого функцией.

declare X : Integer := Y -- значение Y описано где-то в другом месте A : array (.X) of Integer; begin for I in A'Range loop . . . end loop; end;procedure Demo(Item : String) is Copy : String(Item'First..Item'Last) := Item; Double : String( * Item'Length) := Item & Item; begin . . .

Следует заметить, что не стоит позволять вводу пользователя устанавливать размер массива, и приведенный пример (с декларативным блоком) не должен использоваться как способ решения этой задачи. Использование второго примера наиболее типично.

Динамическое создание объектов задач



Динамическое создание объектов задач

Ада предоставляет возможность динамически порождать объекты задач с помощью new, что может оказаться полезным, когда необходимо множество объектов задач одного типа.Предположим, что у нас есть следующая спецификация типа задачи:

task type Counting_Task is entry Add (Amount : in Integer); entry Current_Total (Total : out Integer); end Counting_Task;

Мы можем описать ссылочный тип Counting_Task_Ptr, который позволяет ссылаться на объекты задач типа Counting_Task.Затем мы можем описывать переменные ссылочного типа Counting_Task_Ptr и с помощью new создавать динамические объекты задач, как показано в следующем примере:

declare . . . type Counting_Task_Ptr is access Counting_Task; Task_Ptr : Counting_Task_Ptr; -- переменная ссылающаяся на объект задачи . . . begin . . . Task_Ptr := new Counting_Task; -- динамическое создание объекта задачи . . . end;

Подобным образом можно динамически создать любое необходимое количество объектов задач.Поскольку реализация Ада-системы не обязана освобождать память, занятую при динамическом создании объекта задачи, то это необходимо выполнять самостоятельно, используя предопределенную настраиваемую процедуру Ada.Unchecked_Deallocation.



Динамическое связывание и полиморфизм



Динамическое связывание и полиморфизм

Обеспечение полиморфности поведения объектов полагается на механизм который осуществляет связывание места вызова подпрограммы с конкретной реализацией (телом) подпрограммы.

Следует заметить, что механизм совмещения имен подпрограмм и знаков операций Ады, при использовании традиционных не тэговых типов, обладает свойством полиморфизма.Действительно, одно и то же имя может быть использовано для целого множества различных реализаций подпрограмм (что, собствено, и является полиморфностью).Однако, как мы знаем, все совмещаемые операции должны быть различимы по профилю.Таким образом, до настоящего момента предполагалось, что реализация подпрограммы, которую необходимо вызвать для обработки объекта, всегда точно определяется на этапе компиляции программы.В результате, в подобных случаях, и полиморфизм, предоставляемый механизмом совмещения, и связывание называют статическими, поскольку типы всех обрабатываемых объектов известны на этапе компиляции.

При использовании тэговых типов, некоторые объекты могут принадлежать одному иерархическому образованию класса и обладать некоторыми общими логическими свойствами (напомним, что понятие "класс" Ады отличается от понятия "класс", которое используется в других объектно-ориентированных языках программирования).Следовательно, выполнение обработки таких объектов по единым логическим правилам будет более предпочтительным.Однако, в такой ситуации фактический тип индивидуального объекта не может быть определен вплоть до начала выполнения программы.А это значит, что связывание места вызова подпрограммы с конкретной реализацией подпрограммы должно осуществляться в процессе выполнения программы на основе информации о фактическом типе объекта во время выполнения программы.В таких ситуациях, и связывание, и полиморфизм называют динамическими.



Директивы компилятора



Директивы компилятора

Бывают случаи, когда в исходном тексте необходимо указывать какую-либо дополнительную информацию, которая предназначена сугубо для компилятора.Например, такая информация может предоставлять компилятору дополнительные сведения о режимах трансляции программного модуля, который компилируется в текущий момент времени (оптимизация генерируемого двоичного кода, вставка отладочного кода и т.д.) или управлять распечаткой листинга трансляции.

Для Ады, как и для многих других современных языков и сред программирования, такими средствами передачи дополнительной информации компилятору являются директивы компилятора.При указании директивы компилятору Ада использует зарезервированное слово pragma. Общий вид указания директивы компилятору следующий:

pragma < имя_директивы > ( < параметры_директивы > );

Стандарт языка Ада определяет 39 директив, список которых представлен в приложении L (Annex L) руководства по языку программирования Ада (RM-95). Кроме того, конкретная реализация компилятора может обеспечивать дополнительные директивы компилятора которые, как правило, описываются в сопроводительной документации компилятора.

Выражаясь не строго, можно заметить, что директивы компилятора не изменяют общий смысл программы.

Директивы компилятора



Директивы компилятора

Стандарт Ada95 содержит следующее описание стандартных директив компилятора Import, Export, Convention и Linker_Options:

pragma Import( [Convention =>] идентификатор_соглашения, [Entity =>] локальное_имя [, [External_Name =>] строковое_выражение] [, [Link_Name =>] строковое_выражение]);pragma Export( [Convention =>] идентификатор_соглашения, [Entity =>] локальное_имя [, [External_Name =>] строковое_выражение] [, [Link_Name =>] строковое_выражение]);pragma Convention ([Convention =>] идентификатор_соглашения, [Entity =>] локальное_имя);pragma Linker_Options (строковое_выражение);

При описании синтаксиса этих директив компилятора подразумевается, что:

Convention обозначает язык или, точнее, соглашения (например, для вызова подпрограмм) используемые в конкpетном тpанслятоpе; в качестве идентификатор_соглашения могут использоваться: Ada Instrinsic C<> Fortran Cobol при этом, следует заметить, что обязательно поддерживаемыми являются только Ada и Instrinsic, кроме того, реализация конкретного компилятора может добавить любое другое значение
Entity  -  обозначает имя идентификатора (например, имя вызываемой подпpогpаммы) в Ада-пpогpамме
External_Name  -  обозначает имя идентификатора в чужом модуле (модуле написанном на другом языке программирования)
Link_Name  -  обозначает имя идентификатора с точки зpения линкера (pедактоpа связей)

Директива компилятора Import предназначена для импортирования объектов (подпрограмм или переменных), описанных на других языках программирования, в Ада-программу.С ее помощью можно вызывать подпрограммы или использовать переменные модулей которые написанны на других языках программирования.

Директива компилятора Export предназначена для экспортирования объектов (подпрограмм или переменных), написанных на Аде, для их использования в модулях, написанных на других языках программирования.Следует заметить, что в модулях, написанных на других языках программирования, может потребоваться выполнение вызовов adainit и adafinal для осуществления правильной инициализации и деструктуризации импортированного Ада-кода.

Директива компилятора Convention позволяет указать компилятору на необходимость использования определенных языковых соглашений для какого-либо объекта (подпрограммы или переменной), который впоследствии будет либо импортирован, либо экспортирован.

Директива компилятора Linker_Options предназначена для передачи дополнительных опций линкеру (редактору связей).Содержимое строкового параметра этой директивы будет зависеть от используемого линкера.



Дискриминанты



Дискриминанты

Особенность всех ранее рассмотренных записей в том, что они всегда имеют строго определенные структуры. Действительно, число полей таких записей и их размеры строго зафиксированы на этапе описания типа записи и никак не могут быть изменены впоследствии. Однако, в реальной жизни, достаточно часто возникают ситуации когда необходимо сделать структуру записи зависимой от каких-либо условий.

Для решения такого рода задач, Ада разрешает записям содержать дополнительные поля - дискриминанты. Такие дополнительные поля являются средством "параметризации" и помогают выполнять для записей требуемую настройку. В этом случае, разные экземпляры записей могут принадлежать одному и тому же типу, но при этом иметь разный размер и/или разное количество полей.

Следует заметить, что значения дискриминантов записей должны быть дискретного или ссылочного типа.



Дочерние модули (child units) (Ada95)



Дочерние модули (child units) (Ada95)

Не редко, во время интенсивной разработки большой системы, возникают случаи когда один из пакетов разрастается необычайно быстро, а его спецификация подвергается постоянным изменениям.Таким образом, ввиду частого обновления спецификации пакета, резко возрастают затраты на перекомпиляцию зависимых модулей, а увеличение размеров самого пакета усложняет его дальнейшую разработку и последующее сопровождение.

Для преодоления подобных трудностей Ада предлагает концепцию дочерних модулей, которая является основой в построении иерархических библиотек.Такой подход позволяет разделить пакет большого размера на самостоятельные пакеты и подпрограммы меньшего размера, объединенные в общую иерархию.Кроме того, эта концепция позволяет расширять уже существующие пакеты. Она предлагает удобный инструмент для обеспечения множества реализаций одного абстрактного типа и дает возможность разработки самодостаточных подсистем при использовании приватных дочерних модулей.

Дочерние модули непосредственно используются в стандартной библиотеке Ады.В частности, дочерними модулями являются такие пакеты как Ada.Text_IO, Ada.Integer_Text_IO. Следует также заметить, что концепция дочерних модулей была введена стандартом Ada9 В стандарте Ada83 эта идея отсутствует.



Дочерние настраиваемые модули



Дочерние настраиваемые модули

Настраиваемые пакеты, подобно обычным пакетам Ады, могут иметь дочерние модули.При этом, следует заметить, что такие дочерние модули также должны быть настраиваемыми модулями.

В качестве примера, предположим, что нам необходимо расширить настраиваемый пакет Stacks, который был показан в примере выше (см. ).Допустим, что нам необходимо добавить функцию Top, которая возвращает объект находящийся в вершине стека, но при этом не удаляет его из стека.Чтобы решить эту задачу, мы можем, для настраиваемого пакета Stacks, описать дочерний настраиваемый пакет Stacks.Additions.Спецификация Stacks.Additions может выглядеть следующим образом:

generic package Stacks.Additions is function Top return Element; end Stacks.Additions;

Примечательно, что дочерний настраиваемый модуль "видит" все компоненты своего родителя, включая все параметры настройки.

Тело дочернего настраиваемого модуля Stacks.Additions может иметь следующий вид:

package body Stacks.Additions is function Top return Element is . . .end Stacks.Additions;

Ниже демонстрируется пример конкретизации настраиваемых модулей Stacks и Stacks.Additions.

Конкретизация модуля Stacks формирует пакет Our_Stacks, что имеет вид:

with Stacks;package Our_Stack is new Stack(Integer);

Конкретизация модуля Stacks.Additions формирует пакет Our_Stack_Additions, что имеет вид:

with Our_Stack, Stacks.Additions;package Our_Stack_Additions is new Stacks.Additions;

Примечательно, что настраиваемый дочерний модуль рассматривается как описанный внутри настраиваемого родителя.



Дополнительные целочисленные типы системы компилятора GNAT



Дополнительные целочисленные типы системы компилятора GNAT

Стандарт языка Ада допускает определять в реализации Ада-системы собственные дополнительные целочисленные типы. Таким образом, в пакете Standard системы компилятора GNAT определены дополнительные целочисленные типы:

type Short_Short_Integer is range -(2 ** 7) .. +(2 ** 7 - 1); type Short_Integer is range -(2 ** 15) .. +(2 ** 15 - 1); type Long_Integer is range -(2 ** 31) .. +(2 ** 31 - 1); type Long_Long_Integer is range -(2 ** 63) .. +(2 ** 63 - 1);

Кроме этого, стандарт требует наличия определения дополнительных 8-, 16-, 32- и 64-битных целочисленных типов в пакете Interfaces:

type Integer_8 is range -2 ** 7 .. 2 ** 7 - 1; type Integer_16 is range -2 ** 15 .. 2 ** 15 - 1; type Integer_32 is range -2 ** 31 .. 2 ** 31 - 1; type Integer_64 is range -2 ** 63 .. 2 ** 63 - 1;type Unsigned_8 is mod 2 ** 8; type Unsigned_16 is mod 2 ** 16; type Unsigned_32 is mod 2 ** 32; type Unsigned_64 is mod 2 ** 64;



Доступ к полям записи



Доступ к полям записи

В Аде организация доступа к индивидуальным полям записи осуществляется с помощью точечной нотации, за именем переменной-записи, которое сопровождается точкой, следует имя поля записи. Например:

Expensive_Bike : Bicycle;Expensive_Bike.Frame := Aluminium; Expensive_Bike.Manufacturer := Cannondale; Expensive_Bike.Front_Brake := Cantilever; Expensive_Bike.Rear_Brake := Cantilever; if Expensive_Bike.Frame = Aluminium then ...

Это идентично организации доступа к полям записи в таких языках программирования как Паскаль или Си.



Другие использования дискриминантов



Другие использования дискриминантов

Также как и при использовании в качестве селектора выбора варианта в записи, дискриминант может быть использован для спецификации длины массива, который является компонентом записи.

type Text (Length : Positive := 20) is record Value : String(.Length); end record;

В этом случае длина массива зависит от значения дискриминанта. Как указано выше, запись может быть описана как ограниченная (constrained) или как неограниченная (unconstrained).

Обычно, такие текстовые записи используются как строки переменной длины для текстовой обработки.

Еще одним возможным способом использования дискриминантов могут быть описания различных подтипов какого-либо базового типа. В данном случае смысл идеи состоит в том, что подтипы можно использовать для создания объектов с конкретными значениями дискриминантов. Такой подход удобен при работе с типами, которые содержат большое количество дискриминантов.

Двойная диспетчеризация



Двойная диспетчеризация

Бывают случаи, когда при описании в одном пакете двух различных тэговых типов возникает желание описать подпрограмму которая принимает параметры обоих тэговых типов.В подобной ситуации получается, что такая подпрограмма будет считаться примитивной операцией для обоих типов.Однако, компилятор должен иметь какой-нибудь способ определения того, какой параметр использовать для диспетчеризации.Чтобы, яснее представить суть подобной проблемы, рассмотрим следующий (не корректный!!!) пример

type Message_Type is tagged record . . . end record;type Output_Device is tagged record . . . end record;procedure Put (M : in Message_Type; D : in Output_Device); -- записать сообщение M в устройство вывода D (не допустимо!!!) . . .

На первый взгляд, идея выглядит достаточно логично, при наличии различных типов устройств вывода иметь процедуру, которая позволяет выдать определенного типа сообщение в устройство вывода определенного типа.Каждый тип сообщения может переопределить соответствующим образом процедуру вывода Put для того чтобы осуществить свой вывод необходимым образом.Каждый тип устройства вывода переопределяет процедуру Put с целью использования средств предоставляемых устройством вывода.Однако, при вызове процедуры Put становится не понятным какую версию этой процедуры необходимо использовать, ту которая определена в типе, производном от типа Message_Type, или ту которая определена в типе, производном от типа Output_Device.

Ада решает эту проблему очень просто: использование подпрограмм которые являются примитивными операциями для двух (и более) тэговых типов, подобно процедуре Put, - не допускается.Следовательно, необходимо изменить описание процедуры таким образом, чтобы она была примитивной операцией только для одного типа.Примером такой модификации может служить следующее:

procedure Put (M : in Message_Type; D : in Output_Device'Class);

Теперь, параметр D больше не имеет тип Output_Device, а значит, процедура Put больше не является примитивной операцией для типа Output_Device.Однако, внутри процедуры, значение Output_Device'Class приведет к осуществлению диспетчеризации в случае вызова примитивной операции для типа Output_Device.Подобный прием называют двойной диспетчеризацией.Например, предположим, что тип Output_Device имеет следующую примитивную операцию:

procedure Write_Output (D : in Output_Device; S : in String);

В этом случае, тип Message_Child_Type, производный от типа Message_Type, может переопределить реализацию процедуры Put приблизительно следующим образом:

procedure Put (M : in Message_Child_Type; D : in Output_Device'Class) is begin Put (Message_Type(M), D); -- вызов версии Put предка . . . Write_Output (D, ... ); -- отображение данных сообщения . . . end Put;

Вызов процедуры Put, осуществленный с вовлечением параметра надклассового типа, который соответствует типу Message_Child_Type, и параметром с определенным типом устройства вывода приведет к вызову показанной выше версии процедуры Put.Это приведет к тому, что отображение данных сообщения будет осуществлено с помощью процедуры Write_Output, реализация которой будет выбрана в результате диспетчеризации.



Элементарные сведения: описание, создание, инициализация



Элементарные сведения: описание, создание, инициализация

Предположим, что у нас есть следующие описания:

type Person_Name is new String (1 .. 4); type Person_Age is Integer range 1 .. 150;type Person is record Name : Person_Name; Age : Person_Age; end record;

Предположим также, что нам необходимо разместить экземпляр объекта типа Person в области динамической памяти.Для этого нам надо описать ссылочный тип, значения которого будут ссылаться на значения (объекты) типа Person и переменную этого ссылочного типа (заметим, что все ссылочные типы Ады описываются с помощью использования зарезервированного слова access):

type Person_Ptr is access Person; -- описание ссылочного типа Someone : Person_Ptr; -- переменная (объект) ссылочного типа

Для индикации того, что переменная ссылочного типа (указатель) не указывает ни на один объект, Ада использует специальное значение null (это подобно тому, что используется в языке Паскаль).По умолчанию, объект ссылочного типа всегда инициализируется в null.Таким образом, после приведенных выше описаний, переменная Someone имеет значение null.

Теперь, чтобы создать объект типа Person в области динамической памяти и присвоить соответствующее ссылочное значение переменной Someone необходимо выполнить следующее:

Someone := new Person;

Примечательно, что созданный таким образом в области динамической памяти объект типа Person - никак не инициализирован.Таким образом производится простое распределение пространства, которое необходимо для размещения объекта типа Person, в области динамической памяти.

После создания объекта в области динамической памяти, мы можем проинициализировать индивидуальные поля записи типа Person, на которую ссылается переменная Someone, следующим образом:

Someone.Name := "Fred"; Someone.Age := 33;

Следует заметить, что при таком обращении к объекту в динамической области памяти производится неявная расшифровка ссылки, а внешний вид кода подобен тому, что используется при обращении к статическому объекту.

Для того, чтобы обратиться ко всему содержимому объекта, на который указывает значение ссылочного объекта (указателя), Ада использует зарезервированное слово all.Кроме того, в подобных случаях, используются квалифицированные (указывающие имя типа) агрегаты:

Someone.all := Person'("Fred", 33); -- вариант с позиционным агрегатомSomeone.all := Person'( Name => "Fred"; -- вариант с именованным агрегатом Age => 33 );

Оба варианта, показанные в примере, выполняют абсолютно одинаковые действия, и их результат ничем не отличается от показанного ранее примера инициализации путем обращения к индивидуальным полям записи типа Person.Однако, стоит обратить внимание на то, что использование агрегатов для инициализации всего объекта сразу - более удобно, а вариант с именованным агрегатом имеет лучшую читабельность (по сравнению с позиционным агрегатом), а это важно при работе со сложными структурами данных.

Ада позволяет одновременно создавать объект в динамической области памяти и выполнять его инициализацию требуемыми значениями.Например, вместо выполненных ранее раздельно создания и инициализации, мы можем использовать следующее:

Someone := new Person'("Fred", 33); -- вариант с позиционным агрегатомSomeone := new Person'( Name => "Fred"; -- вариант с именованным агрегатом Age => 33 );

Чтобы облегчить понимание синтаксиса, который используется в языке Ада при работе со ссылочными типами, для тех кто знаком с такими языками программирования как Си и Паскаль, предлагается следующая сравнительная таблица:

Таблица сравнения синтаксиса указателей/ссылочных типов
в языках Паскаль, Си и Ада:
Паскаль C Ада
Доступ к полям указываемого
объекта
a^.fieldname *a.fieldname
a->fieldname
a.fieldname
Копирование указателя b := a; b = a; b := a;
Копирование указываемого
обекта
b^ := a^; *b = *a; b.all := a.all



Файлы ввода/вывода по умолчанию



Файлы ввода/вывода по умолчанию

В тех случаях, когда для осуществления ввода/вывода нет четкого указания внешних файлов используются файлы ввода/вывода по умолчанию.При этом, соответствующие файл ввода и файл вывода по умолчанию всегда открыты перед началом выполнения программы и связаны с двумя, определяемыми реализацией системы, внешними файлами.Другие достаточно широко распространенные названия этих файлов: стандартный ввод и стандартный вывод, или стандартное устройство ввода и стандартное устройство вывода.Стандарт Ada95, кроме этого, добавил аналогичную концепцию стандартного файла ошибок, позаимствовав эту идею от Unix/C.

Как правило, файл стандартного ввода - это клавиатура, а файлы стандартного вывода и стандартного файла ошибок - это дисплей.Пакет Ada.Text_IO предоставляет средства которые позволяют программно определять и переопределять установленные по умолчанию файлы ввода/вывода.

Функции пакета Ada.Text_IO, которые позволяют определить стандартные файл ввода, файл вывода и файл ошибок, а также файл ввода, файл вывода и файл ошибок используемые в текущий момент имеют соответственные имена:

Standard_Input Standard_Output Standard_ErrorCurrent_Input Current_Output Current_Error

Такие функции возвращают значения типа File_Type или File_Access (используется механизм совмещения имен функций).

Для переопределения используемых в текущий момент файлов ввода, вывода и ошибок, пакет Ada.Text_IO обеспечивает процедуры со следующими именами:

Set_Input Set_Output Set_Error

В качестве параметра, эти процедуры используют значения типа File_Type, причем, указываемый файл должен быть предварительно открыт.



Функции



Функции

Функции во многом подобны процедурам, за исключением того, что они возвращают значение в вызвавшую их подпрограмму. Также можно сказать, что функция - это подпрограмма которая осуществляет преобразование одного или нескольких входных значений в одно выходное значение.

Общий вид описания функции выглядит следующим образом:

function имя_функции [ (формальные_параметры) ] return тип_возвращаемого_значения ; спецификация функции, определяющая имя функции, профиль ее формальных параметров (если они есть) и тип возвращаемого значения

Общий вид тела функции:

function имя_функции [ (формальные_параметры) ] return тип_возвращаемого_значения is    спецификация функции, определяющая имя функции, профиль ее формальных параметров (если они есть) и тип возвращаемого значения
      
. . .   описательная (или декларативная) часть, которая может содержать локальные описания типов, переменных, констант, подпрограмм...
      
begin     
. . .    исполнительная часть функции, которая описывает алгоритм работы функции;
обязана содержать хотя бы одну инструкцию возврата значения - return
      
end [ имя функции ];    здесь, указание имени функции опционально

Использование инструкции возврата значения - return очень похоже на то, что используется в языке Си, при этом, функция может иметь сколько угодно инструкций возврата значения.

Функция может быть вызвана как часть выражения в инструкции присваивания или как аргумент соответствующего типа при вызове другой функции или процедуры. Другими словами - функция, возвращающая значения заданного типа, может быть использована везде, где может быть использована переменная этого типа.

Хотя режимы передачи параметров в подпрограммы будут подробно рассмотрены несколько позже, здесь, необходимо сделать несколько важных замечаний, которые имеют значение для функций Ады.

Согласно традиций стандарта Ada83, для передачи параметров в функцию разрешается использовать только режим "in".Поэтому, функция, через свои параметры, может только импортировать данные из среды вызвавшей эту функцию.При этом, параметры функции не могут быть использованы для изменения значений переменных в среде вызвавшей функцию.Таким образом, в отличие от традиций языка Си, функции Ады не обладают побочными эффектами.

Стандарт Ada95 ввел новый режим для передачи параметров - access.Этот режим разрешается использовать для передачи параметров в функции.Следует заметить, что использование этого режима допускает написание функций обладающих побочными эффектами.



Идентификация задач и атрибуты



Идентификация задач и атрибуты

Каждая задача (объект) Ады имеет свой собственный уникальный идентификатор, с помощью которого она может быть идентифицирована в других задачах.Механизм, с помощью которого задача может получить свой уникальный идентификатор, обеспечивается средствами стандартного пакета Ada.Task_Identification, описанного в приложении C (Annex C) стандарта Ada95, в котором указываются требования для системного программирования.

Спецификация пакета Ada.Task_Identification имеет следующий вид:

package Ada.Task_Identification is type Task_ID is private; Null_Task_ID : constant Task_ID; function "=" (Left, Right : Task_ID) return Boolean; function Image (T : Task_ID) return String; function Current_Task return Task_ID; procedure Abort_Task (T : in out Task_ID); function Is_Terminated(T : Task_ID) return Boolean; function Is_Callable (T : Task_ID) return Boolean;private . . . -- Стандартом языка не определеноend Ada.Task_Identification;

Кроме этого пакета, в приложении C стандарта описываются атрибуты, которые могут быть использованы для идентификации задач:

T'Identity где T: любая задача.
Возвращает значение типа Task_ID которое уникально идентифицирует T.
E'Caller  -  где E: имя любого входа задачи.
Возвращает значение типа Task_ID, которое идентифицирует обрабатываемую в текущий момент задачу, обратившуюся к входу задачи E. Использование этого атрибута допустимо только внутри инструкции принятия (для задачи-сервера).

При использовании этих средств следует учитывать, что по истечении некоторого времени ничто не гарантирует активность задачи или ее присутствие в области видимости.

Кроме перечисленных средств идентификации, при работе с задачами могут быть также использованы следующие атрибуты:

T'Callable  -  Возвращает значение True, если задача T может быть вызвана.
T'Terminated  -  Возвращает True если выполнение задачи T прекращено.

Следует учесть, что T - это любая задача а результат, возвращаемый этими атрибутами, имеет предопределенный логический тип Standard.Boolean.

Для входов (и семейств входов) задач определен атрибут 'Count.Если E - имя любого входа задачи, то E'Count возвращает значение типа Universal_Integer, показывающее число обращений к входу E, которые находятся в очереди.Исползование этого атрибута допустимо только внутри тела задачи или внутри программного модуля, который расположен внутри тела задачи.

Следует заметить, что не рекомендуется, чтобы алгоритм обработки имел жесткую зависимость от значений атрибутов задач 'Callable и 'Terminated, а также атрибута 'Count для входов (и семейств входов).



Идентификаторы



Идентификаторы

Теоретически, согласно требований стандарта, идентификаторы могут быть любой длины, однако, длина может быть ограничена реализацией конкретного компилятора. Общие правила таковы: Идентификатор может состоять из букв, цифр и символов подчеркивания. Идентификатор обязан начинаться с символа. В идентификаторе нельзя использовать несколько символов подчеркивания подряд. Символ подчеркивания не может быть первым и последним символом в идентификаторе. Все идентификаторы в ADA не зависят от регистра символов.

Например:

Apple, apple, APPLE -- один и тот же идентификатор Max_Velocity_Attained Minor_Number_ -- недопустимо, завершающий символ - подчеркивание Minor__Revision -- недопустимо, последовательность подчеркиваний



Идеология концепции пакетов



Идеология концепции пакетов

Прежде чем непосредственно рассматривать примеры языковых конструкций необходимо разобраться в чем заключается и что предлагает концепция пакетов.

Пакет - это средство, которое позволяет сгруппировать логически связанные вычислительные ресурсы и выделить их в единый самостоятельный программный модуль.Под вычислительными ресурсами в этом случае подразумеваются данные (типы данных, переменные, константы...) и подпрограммы которые манипулируют этими данными.Характерной особенностью данного подхода является разделение самого пакета на две части: спецификацию пакета и тело пакета. Причем, спецификацию имеет каждый пакет, а тело могут иметь не все пакеты.

Спецификация определяет интерфейс к вычислительным ресурсам (сервисам) пакета доступным для использования во внешней, по отношению к пакету, среде.Другими словами - спецификация показывает "что" доступно при использовании этого пакета.

Тело является приватной частью пакета и скрывает в себе все детали реализации предоставляемых для внешней среды ресурсов, то есть, тело хранит информацию о том "как" эти ресурсы устроены.

Необходимо заметить, что разбиение пакета на спецификацию и тело не случайно, и имеет очень важное значение. Это дает возможность по-разному взглянуть на пакет.Действительно, для использования ресурсов пакета достаточно знать только его спецификацию, в ней содержится вся необходимая информация о том как использовать ресурсы пакета.Необходимость в теле пакета возникает только тогда, когда нужно узнать или изменить реализацию чего-либо внутри самого пакета.

Средства построения такой конструкции как пакет дают программисту мощный и удобный инструмент абстракции данных, который позволяет объединить и выделить в логически законченное единое целое данные и код который манипулирует этими данными.При этом, пакет позволяет программисту скрыть все детали реализации сервисов за развитым функциональным интерфейсом.В результате, структурное представление программного комплекса в виде набора взаимодействующих между собой компонентов облегчает понимание работы комплекса в целом и, следовательно, позволяет облегчить его разработку и сопровождение.

Необходимо также заметить, что на этапе начального проектирования системы можно предоставлять компилятору только спецификации, обеспечивая детали реализации только самых необходимых элементов.Таким образом проверка корректности общей структуры проекта осуществляется на ранней стадии, когда не потрачено много усилий на разработку реализации отдельных элементов, которые позже придется переделывать (что, к великому сожалению, в реальной жизни происходит достаточно часто).

Примечание:
В системе компилятора GNAT существует соглашение согласно которому файлы спецификаций имеют расширение ads (ADa Specification), а файлы тел имеют расширение adb (ADa Body).



Иерархия модулей как подсистема



Иерархия модулей как подсистема

Каждый программный модуль (процедура, функция пакет) должен иметь уникальное имя.Бывают случаи, когда происходит быстрое заполнение пространства имен.Например, при проектировании большой системы достаточно сложно обеспечить уникальность имен и при этом сохранить их смысловое значение, а в случаях когда разные части проекта разрабатываются разными программистами (или даже коллективами программистов) риск получения коллизии имен увеличивается еще больше.

В подобных ситуациях, используя концепцию дочерних модулей Ады, можно выделить каждую отдельно разрабатываемую подсистему в самостоятельную иерархию модулей.При этом, каждая подсистема будет иметь свой собственный корневой пакет с уникальным именем.

package Root is -- корневой пакет может быть пустымend Root;

Поскольку имя корневого пакета уникально, то имена дочерних модулей иерархии также будут уникальны, что, в результате, минимизирует вероятность коллизии имен.Кроме того, такой прием является удобным средством разделения большого проекта на логически самостоятельные составные части.

Примером использования подобного подхода может служить стандартная библиотека Ады, которая предстявляется как набор дочерних модулей трех корневых пакетов: Ada, Interfaces и System.



Именованное сопоставление



Именованное сопоставление

Для улучшения читабельности вызовов подпрограмм (а Ада разрабатывалась с учетом хорошей читабельности) Ада позволяет использовать именованное сопоставление формальных и фактических параметров. В этом случае мы можем ассоциировать имя формального параметра с фактическим параметром. Это свойство делает вызовы подпрограмм более читабельными.

procedure Demo(X : Integer; Y : Integer); -- спецификация процедуры . . .Demo(X => 5, Y => 3 * 45); -- именованное сопоставление -- формальных и фактических -- параметров при вызове

Расположение списка параметров вертикально, также способствует улучшению читабельности.

Demo(X => 5, Y => 3 * 45);

Поскольку при именованом сопоставлении производится явное ассоциирование между формальными и фактическими параметрами (вместо неявного ассоциирования, используемого в случае позиционного сопоставления), то нет необходимости строго придерживаться того же самого порядка следования параметров, который указан в спецификации подпрограммы.

Demo(Y => 3 * 45, -- при именованом сопоставлении X => 5); -- порядок следования параметров -- не имеет значения



Именованые циклы



Именованые циклы

Инструкции преждевременного выхода из цикла exit и exit when, обычно, осуществляют выход из того цикла, который непосредственно содержит данную инструкцию. Однако, мы можем именовать циклы и модифицировать инструкцию выхода из цикла так, чтобы осуществлять выход сразу из всех вложенных циклов. Во всех случаях, следующая выполняемая инструкция будет следовать сразу за циклом из которого был осуществлен выход.

outer_loop: loop -- инструкции loop -- инструкции exit outer_loop when логическое_выражение; end loop; end loop outer_loop;

Примечательно, что в случае именованого цикла end loop также необходимо именовать меткой.



Инструкции exit и exit when



Инструкции exit и exit when

Инструкции exit и exit when могут быть использованы для преждевременного выхода из цикла. При этом, выполнение программы будет продолжено в точке непосредственно следующей за циклом. Два варианта, показанных ниже, имеют одинаковый эффект:

loop -- инструкции тела цикла if логическое_выражение then exit; end if; end loop;loop -- инструкции тела цикла exit when логическое_выражение; end loop;



Инструкции, выражения и элаборация



Инструкции, выражения и элаборация

Очевидно, что исполнение инструкций осуществляется во время выполнения программы с целью выполнить какие-либо действия.Также, во время выполнения программы осуществляются вычисления различных выражений для получения значений каких-либо типов.Кроме того, во время выполнения программы происходит вычисление различных имен, которые указывают на соответствующие объекты (содержащие какие-либо значения) или другие сущности (такие как подпрограммы и типы).

Некоторые конструкции языка содержат описательные части, сопровождаемые последовательностями инструкций.Например, тело процедуры может иметь следующий вид:

procedure P( ... ) is I: Integer := 1; -- описательная часть . . . begin . . . -- последовательность инструкций I := I * 2; . . . end P;

Перед выполнением тела процедуры происходит элаборация (elaboration) всех описаний, которые указаны в описательной части.Последовательность элаборации описаний определяется порядком их следования в описательной части.Эффект от элаборации описаний заключается в создании сущностей, определенных в описаниях, и в выполнении прочих действий, которые специфичны для описаний.Например, элаборация описания переменной может осуществить инициализацию этой переменной значением, которое определяется результатом вычисления какого-либо выражения.Достаточно часто значения подобных выражений могут быть вычислены в процессе компиляции программы.

После завершения элаборации, осуществляется исполнение последовательности инструкций, в порядке их следования (за исключением случаев, когда осуществляется передача управления в какое-либо другое место, отличное от последующей инструкции).Инструкция присваивания позволяет заменить значение переменной результатом вычисления выражения того же самого типа.Обычно, присваивание осуществляется простым побитовым копированием значения, которое получено в результате вычисления выражения.Однако, в случае нелимитированных контролируемых типов, после осуществления побитового копирования, пользователь (при необходимости) может определить дополнительную последовательность действий.Инструкции case и if позволяют осуществлять выбор выполнения определенной последовательности инструкций, полагаясь на результат вычисления какого-нибудь выражения.Инструкция loop позволяет повторять выполнение последовательности каких-либо инструкций, согласно выбранной схемы итерации, или до обнаружения инструкции exit.Инструкция goto осуществляет передачу управления в место отмеченное соответствующей меткой.

Выражения могут быть использованы в различном контексте, как в описаниях, так и в инструкциях.Используемые в языке Ада выражения, во многом подобны выражениям, которые используются в большинстве современных языков программирования.Они могут содержать обращения к переменным, константам и литералам, кроме того они могут использовать любые операции, которые возвращают значения.Результатом вычисления любого выражения является значение.Каждое выражение имеет определенный тип, который известен на этапе компиляции.

Во многих случаях результаты вычисления выражений и ограничения подтипов определяются статически (существует возможность отключения некоторых динамических проверок ограничений подтипов с помощью использования соответствующих опций компилятора).Более того, достаточно часто компилятор Ады требует чтобы вычисление некоторых выражений и подтипов осуществлялось на этапе компиляции программы.Например, в общем случае вся информация об описании известна во время компиляции, следовательно, элаборация во время выполнения программы не потребует выполнения какого-либо машинного кода.Язык определяет механизмы согласно которых Ада-компиляторы могут осуществлять предварительную элаборацию некоторых модулей, то есть, реальные действия, которые необходимы для осуществления элаборации, выполняются однократно, на этапе компиляции программы, вместо того, чтобы выполнять их при каждом запуске программы.



Инструкции задержки выполнения (delay statements)



Инструкции задержки выполнения (delay statements)

При внимательном рассмотрении исходного текста последнего примера можно обнаружить, что после инструкции вывода сообщения приветствия располагается инструкция "delay ;", с помощью которой в данном случае осуществляется задержка выполнения задачи на секунды.

Инструкции задержки выполнения могут быть использованы для приостановки выполнения тела задачи (или программы) на некоторое время.Рзличают два вида инструкций задержки выполнения: относительная задержка выполнения и абсолютная задержка выполнения.

Общий вид инструкции относительной задержки выполнения следующий:

delay время_задержки;

Здесь результат выражения время_задержки, имеющего предопределенный вещественный тип с фиксированной точкой Duration (описан в пакете Standard), указывает длительность задержки выполнения в секундах, на которую необходимо задержать выполнения задачи (или программы).При этом отсчет времени задержки выполняется относительно текущего момента времени.Наглядным примером использования инструкции относительной задержки, для задержки выполнения задачи на одну секунду, может служить следующее:

delay ;

Общий вид инструкции абсолютной задержки выполнения следующий:

delay until время_задержки;

В этом случае результат выражения время_задержки, любого неограниченного типа, указывает момент времени, до наступления которого необходимо задержать выполнение задачи (или программы).Следующий пример демонстрирует использование инструкции абсолютной задержки выполнения задачи:

delay until Time_Of(2010, 1, 1, ); -- задержка выполнения до 1 января 2010 года

Следует также заметить, что описание предопределенного типа времени Time и ассоциируемых с ним операций предусматривается в стандартном пакете Ada.Calendar.Кроме того, пакет Ada.Calendar предоставляет функцию Clock, которая возвращает значение текущего момента времени (для получения более полной информации следует обратиться к спецификации этого пакета).

Отметим также, что при построении систем, которые должны работать в реальном масштабе времени, вместо типа времени Time, описанного в стандартном пакете Ada.Calendar, следует использовать тип времени Time, который описывается в пакете Ada.Real_Time (для получения более полной информации следует обратиться к спецификации пакета Ada.Real_Time).



Инструкция перехода goto



Инструкция перехода goto

Инструкция перехода goto предусмотрена для использования в языке Ада, в исключительных ситуациях, и имеет следующий вид:

goto Label;<<Label>>

Использование инструкции goto очень ограничено и четко осмысленно. Вы не можете выполнить переход внутрь условной инструкции if, внутрь цикла (loop), или, как в языке Паскаль, за пределы подпрограммы.

Вообще, при таком богатстве алгоритмических средств Ады, использование goto едва-ли можно считать оправданным.

Инструкция перенаправления очереди requeue



Инструкция перенаправления очереди requeue

С целью поддержки построения необходимых управляющих алгоритмов, позволяющих разделить обработку какого-либо сервиса на несколько (два и более) этапов и для организации предпочтительного управления, стандарт Ada95 ввел единственную и простую инструкцию перенаправления очереди requeue.

Инструкция перенаправления очереди используется для завершения инструкции принятия рандеву (accept) или тела защищенного входа при необходимости перенаправления соответствующего вызова клиента от одного входа задачи или защищенного модуля к другому входу (или даже к тому же самому входу).Общий вид этой инструкции следующий:

requeue имя_входа [ with abort ] ;

Для того чтобы сервер получил доступ к параметрам вызова, нет необходимости возобновлять обработку вызова и требовать от клиента инициирование вызова на другм входе, основываясь на результатах первого вызова.С помощью инструкции перенаправления requeue, можно просто переслать вызов клиента в очередь другого входа.

В случае вызова защищенного входа исключительный доступ обеспечивается на протяжении периода проверки параметров и выполнения перенаправления.В случае тела инструкции принятия рандеву (accept), задача-сервер управляет своим собственным состоянием, и в данном случае атомарность также обеспечивается, так как она может отвергнуть принятие любого промежуточного вызова.

Инструкция перенаправления requeue предназначена для обработки двух основных ситуаций: После начала выполнения инструкции принятия accept или тела защищенного входа, может быть определено, что запрос не может быть удовлетворен немедленно.Взамен, существует необходимость перенаправлять вызывающего клиента до тех пор, пока его запрос/вызов не сможет быть обработан. В качестве альтернативы, часть запроса клиента может быть обработана немедленно, но могут существовать дополнительные шаги, которые должны быть выполнены несколько позже.

В обоих случаях, инструкция принятия accept или тело защищенного входа, нуждается в передаче управления.Таким образом, могут быть обработаны запросы от других клиентов или выполнена какая-либо другая обработка.Инструкция перенаправления requeue позволяет разделить обработку оригинального запроса/вызова на два (и более) этапа.

Чтобы продемонстрировать логику работы и использования этой инструкции, рассмотрим пример широковещательного сигнала (или события).Задачи приостанавливаются для ожидания некоторого события, а после того как оно происходит, все приостановленные задачи продолжают свое выполнение, а событие очищается.Сложность состоит в том, как предотвратить от попадания в ожидание задачи которые вызвали операцию ожидания после того как событие произошло, но до того как событие очищено.Другими словами, мы должны очистить событие только после того как новые задачи продолжат свое выполнение.Нам позволяет запрограммировать такое предпочтительное управление инструкция перенаправления requeue:

protected Event is entry Wait; entry Signal;private entry Reset; Occurred: Boolean := False;end Event;protected body Event is entry Wait when Occurred is begin null; -- пустое тело! end Wait; entry Signal when True is -- барьер всегда True begin if Wait'Count > 0 then Occurred := True; requeue Reset; end if; end Signal; entry Reset when Wait'Count = 0 is begin Occurred := False; end Reset;end Event;

Задачи указывают на то, что они желают ждать событие, выполняя вызов:

Event.Wait;

а возникновение события индицируется тем, что какая-либо задача выполняет вызов:

Event.Signal;

после чего, все приостановленные в ожидании события задачи продолжат свое выполнение и событие очистится.Таким образом, последующие вызовы входа Wait работают корректно.

Логическая переменная Occurred обычно имеет значение False.Она имеет значение True только тогда, когда задачи возобновляют свое выполнение после приостановки в ожидании события.Вход Wait существует, но фактически не имеет тела.Таким образом, вызывающие задачи приостанавливают свое выполнение в его очереди, ожидая того, что переменная Occurred получит значение True.

Особый интерес представляет вход Signal.Значение его барьера постоянно и всегда равно True, таким образом, он всегда открыт для обработки.Если нет задач ожидающих событие (нет задач вызвавших вход Wait), то его вызов просто ничего не делает.С другой стороны, при наличии задач ожидающих событие, он должен позволить им продолжить выполнение, причем так, чтобы ни одна новая задача не попала в очередь ожидания события, после чего, он должен сбросить флаг, чтобы восстановить управление.Он выполняет это перенаправляя себя на вход Reset (с помощью инструкции перенаправления requeue) после установки флага Occurred в значение True, для индикации появления события.

Семантика инструкции перенаправления requeue подобна тому, что описано при рассмотрении алгоритма работы Signal.Однако, помните, что в конце обработки тела входа или защищенной процедуры осуществляется повторное вычисление состояний тех барьерных условий в очереди которых находятся задачи, приостановленные в ожидании обслуживания.В этом случае, действительно существуют задачи находящиеся в очереди входа Wait, и существует задача в очереди входа Reset (та задача которая перед эти вызвала вход Signal).Барьер для Wait теперь имеет значение True, а барьер для Reset, естественно, False, поскольку очередь задач на входе Wait не пуста.Таким образом, ожидающая задача способна выполнить тело входа Wait (которое фактически ничего не делает), после чего опять осуществляется повторное вычисление значений барьеров.Этот процесс повторяется до тех пор, пока все приостановленные в ожидании задачи не возобновят свое выполнение и значение барьера для Reset не получит значение True.Оригинальная задача, которая вызвала сигнал, теперь выполняет тело входа Reset, сбрасывая флаг Occurred в значение False, возвращая всю систему в исходное состояние еще раз.Теперь, защищенный объект (как единое целое) полностью освобожден, поскольку нет ни одной ожидающей задачи, ни на одном барьере.

Следует обратить внимание на то, что если какие-либо задачи пытаются вызвать Wait или Signal, когда происходит обработка всего выше описанного процесса, то эти задачи будут заблокированы, поскольку защищенный объект, как единое целое, будет находиться в занятом состоянии.Это иллюстрирует два уровня защиты и является смысловой основой отсутствия возможности появления состязания задач за ресурс.

Другим следствием двухуровневой защиты является то, что все будет работать надежно даже в случае таких сложностей как временные и условные вызовы и принудительное завершение (abort).Можно заметить, что это несколько противоположно использованию атрибута 'Count для входов задач, который не может быть использован при временных вызовах.

Следует сделать небольшое замечание о том, что вход Reset описан в приватной части защищенного объекта, и, следовательно, не может быть вызван извне.Ada95 позволяет задачам также иметь приватную часть, содержащую приватные входы.

Следует заметить, что показанный выше пример был приведен только в качестве демонстрации.Внимательный читатель должен заметить, что проверка условия внутри Signal не является строго необходимой.Без этой проверки, вызвавшая задача будет просто всегда осуществлять перенаправление requeue и немедленно продолжать вторую часть обработки, при отсутствии ожидающих задач.Однако это условие делает описание более ясным и чистым.Еще более внимательный читатель может заметить, что мы можем запрограммировать этот пример на Ada95 вовсе без использования инструкции перенаправления requeue.

Как видно из общего вида инструкции перенаправления requeue, она может быть указана с принудительным завершением "with abort".В Ada83, после начала рандеву, вызывающий клиент не обладал возможностью отменить свой запрос (таймаут мог быть получен только если запрос не был принят в обработку задачей-сервером).Такое поведение весьма обосновано.После того как сервер начал обработку запроса, он находится в неустойчивом состоянии, и асинхронное удаление вызывающего клиента может нарушить внутренние структуры данных сервера.В дополнение, при наличии параметров переданных по ссылке, сервер, принимающий вызов, должен быть способен получить доступ к данным вызывающего клиента (таким как данные в стеке).При исчезновении вызывающего клиента, это может привести к "висячим" ссылкам и последующему краху работы программы.

Однако, в некоторых случаях, откладывание аннулирования вызова невозможно.В частности, когда значение таймаута необходимо использовать для контроля за общим временем обращения, которое также включает время непосредственной обработки вызова, а не только время ожидания принятия вызова.В дополнение к асинхронной передаче управления Ada95, подобная ситуация может возникнуть когда вызывающий клиент "прерван" и должен изменить последовательность своего выполнения как можно быстрее.

Поскольку не существует единственно возможного наилучшего решения сразу для всех приложений, и поскольку отсутствует легкий обходной путь, то использование инструкции перенаправления (requeue) с принудительным завершением (with abort) предоставляет программисту возможность выбора для его приложения наиболее подходящего механизма.В основном, когда допускается аннулирование вызова в процессе перенаправления, сервер будет сохранять состояние своих внутренних структур данных перед началом выполнения инструкции перенаправления с принудительным завершением, после чего, если вызывающий клиент будет удален из следующей очереди, то сервер сможет нормально продолжить свою работу.Когда это не возможно, или когда не требуется аннулирование вызова в процессе перенаправления, будет достаточно использования простого перенаправления, и вызывающий клиент будет удерживаться до полного завершения обработки запроса.

Инструкция перенаправления requeue позволяет перенаправить вызывающего клиента в очередь того же самого или какого-либо другого входа.При этом, вызывающий клиент не обязан заботиться о таком перенаправлении и даже о фактическом количестве необходимых для удовлетворения его запроса этапов обработки, которые вообще могут быть не видимыми извне защищенного типа или типа задачи.Использование такого эффекта, предоставляет гибкие возможности построения сервера со сложной внутренней архитектурой и простым, однозначным для клиента, интерфейсом.

В процессе выполнения перенаправления, не происходит никаких переопределений параметров.Вместо этого, значения параметров прямо переносятся в новый вызов.Если был предусмотрен новый список параметров, то он может включать ссылки на данные, которые локальны для инструкции принятия accept или тела защищенного входа.Это может вызвать некоторые трудности, поскольку выполнение инструкции принятия accept или тела защищенного входа будет завершено в результате выполнения инструкции перенаправления requeue и локальные переменные будут, таким образом, деаллоцированы (deallocated).Необходимо соответствие используемых подтипов между вновь вызываемым целевым входом (если он имеет какие-либо параметры) и текущим входом.Это позволяет использовать то же самое представление для нового множества параметров, когда они передаются по значению (by-copy) или по ссылке (by-reference), а также исключить необходимость размещения (allocate) нового пространства для хранения параметров.Необходимо отметить, что при выполнении перенаправления, кроме передачи тех же самых параметров, существует еще только одна возможность - не передавать никаких параметров вовсе.

В заключение, как общий итог обсуждения инструкции перенаправления requeue и логики ее использования, сделаем следующие выводы: Инструкция перенаправления requeue допустима внутри тела защищенного входа или внутри инструкции принятия рандеву accept.Целевой вход (допустим тот же самый вход) может быть в той же самой задаче или том же защищенном объекте, или другой задаче или другом защищенном объекте.Возможно использование любых перечисленных комбинаций. Любые фактические параметры оригинального вызова передаются к новому входу.Следовательно, новый вход должен иметь такой же самый профиль параметров или не иметь никаких параметров.



Инструкция присваивания



Инструкция присваивания

Инструкция присваивания используется в Аде для установки и изменения значений переменных. Она использует операцию присваивания, и в общем случае имеет следующий вид:

result := expression;

Операция присваивания ":=" разделяет инструкцию присваивания на левую и правую части (между символами двоеточия и знак равенства, пробелы - не допустимы!).В левой части записывается имя переменной (result) содержимому которой будет производится присваивание нового значения. Следует заметить, что в левой части может распологаться имя только одной переменной.В правой части записывается выражение (expression) результат вычисления которого становится новым значением переменной result.Выражение expression, расположенное в правой части, может быть одиночной переменной или константой, может содержать переменные, константы, знаки операций и вызовы функций. Тип переменной определяемой как result должен быть совместим по присваиванию с типом результата вычисления выражения expression.

Выполнение присваивания разделяется на несколько действий.Сначала производится вычисление имени переменной result и результата выражения expression (порядок следования этих действий не регламентирован стандартом языка).После этого, в случае успеха, для переменных скалярных типов проверяется принадлежность значения результата вычисления выражения expression подтипу переменной.Если проверка успешна, то значение результата вычисления выражения expression становится новым значением содержимого переменной result. При этом старое значение содержимого result - теряется.Иначе, в случае какой-либо неудачи, возбуждается исключение ошибки ограничения или, проще говоря, - ошибка, а значение переменной result остается без изменений.

Приведем несколько примеров инструкций присваивания:

A := B + C X := Y

Следует заметить, что в Аде, в результате выполнения присваивания производится изменение только содержимого result (значение содержимого переменной указанной в левой части).Необходимо также подчеркнуть, что операция присваивания в Аде, в отличие от языков C/C++, не возвращает значение и не обладает побочными эффектами. Кроме того, напомним, что операция присваивания, в Аде, не допускает совмещение, переименование и использование псевдонимов, а также она запрещена для лимитированных типов.



Инструкция выбора сase



Инструкция выбора сase

Еще одним средством позволяющим строить разветвляющиеся алгоритмы является инструкция выбора сase.

Инструкция выбора сase должна предусматривать определенное действие для каждого возможного значения переменной селектора (переключателя). В случаях, когда невозможно перечислить все значения переменной селектора, нужно использовать метку others.

Каждое значение выбора может быть представлено как одиночное значение (например, 5), как диапазон значений (например, 0), или как комбинация, состоящая из одиночных значений и/или диапазонов значений, разделенных символом '|'.

Каждое значение выбора должно быть статическим значением, то есть оно должно быть определено компилятором во время компиляции программы.

case выражение is when значение_выбора => действия when значение_выбора => действия . . . when others => действия end case;

Важные примечания: "выражение", в инструкции case, должно быть дискретного типа метка others обязательна в инструкции сase тогда, когда инструкции when не перечисляют всех возможных значений селектора.

case Letter is when 'a'..'z'| 'A'..'Z' => Put ("letter"); when '0'..'9' => Put ("digit! Value is"); Put (letter); when ''' | '"' | '`' => Put ("quote mark"); when '&' => Put ("ampersand"); when others => Put ("something else"); end case;

В некоторых случаях, в качестве действий, указываемых для метки others, может использоваться пустая инструкция null:

. . . when others => null; -- ничего не делать . . .



Интерфейс с другими языками



Интерфейс с другими языками

Обычно, не зависимо от того насколько хорош язык программирования, необходимо учитывать, что он должен сосуществовать с программным обеспечением которое написано с помощью других языков программирования.Разработчики Ады предусмотрели это и предлагают стандартные механизмы для осуществления связи с программами которые написаны на других языках.

При этом следует заметить, что различные компиляторы Ады могут расширять стандартные средства взаимодействия с другими языками программирования добавляя какие-либо дополнительные возможности, специфичные для конкретного компилятора.Поэтому, для получения более точных сведений, необходимо обратиться к справочному руководству используемого компилятора Ады.



Интерфейсные пакеты



Интерфейсные пакеты

Стандарт Ada95 определяет интерфейс взаимодействия с языками программирования C, COBOL и Fortran.Для облегчения осуществления связи программ Ады с этими языками программирования существует стандартно определенная иерархия пакетов, состоящия из пакета Interfaces и его дочерних модулей:

package Interfaces package Interfaces.C package Interfaces.C.Pointers package Interfaces.C.Strings package Interfaces.COBOL package Interfaces.Fortran

Эти интерфейсные пакеты обеспечивают достаточно мощные средства для взаимодействия с другими языками программирования.



Исключение Constraint_Error



Исключение Constraint_Error

Исключение Constraint_Error возбуждается в следующих случаях: при попытке нарушения ограничения диапазона, ограничения индекса или ограничения дискриминанта при попытке использования компонента записи, не существующего при текущем значении дискриминанта при попытке использования именуемого или индексируемого компонента, отрезка или атрибута объекта, обозначенных ссылочным значением, если этот объект не существует, поскольку ссылочное значение равно null

Рассмотрим пример:

procedure Constraint_Demo is X : Integer range 0; Y : Integer;begin Put("enter a number "); Get(Y); X := Y; Put("thank you"); end Constraint_Demo;

Если пользователь вводит значение выходящее за диапазон значаний 0, то нарушается ограничение диапазона значений для X, и происходит исключение Constraint_Error.Поскольку в этом примере не предусмотрен код, который будет обрабатывать это исключение, то выполнение программы будет завершено, и окружение времени выполнения Ады (Ада-система) проинформирует пользователя о возникшей ошибке.При этом, строка

Put("thank you");

выполнена не будет.Таким образом, при возникновении исключения, остаток, выполняющегося в текущий момент блока, будет отброшен.

Рассмотрим пример в котором выполняется нарушение ограничения диапазона индексных значений для массива:

procedure Constraint_Demo2 is X : array () of Integer := (1, 2, 3, 4, 5); Y : Integer := 6;begin X(Y) := 37; end Constraint_Demo2;

Здесь, исключение Constraint_Error будет генерироваться когда мы будем пытаться обратиться к несуществующему индексу массива.



Исключение Numeric_Error



Исключение Numeric_Error

Исключение Numeric_Error возбуждается в случае когда предопределенная численная операция не может предоставить математически корректный результатЭто может произойти при арифметическом переполнении, делении на нуль, а также не возможности обеспечить требуемую точность при выполнении операций с плавающей точкой.Следует заметить, что в Ada95 Numeric_Error переопределена таким образом, что является тем же самым, что и Constraint_Error.

procedure Numeric_Demo is X : Integer; Y : Integer;begin X := Integer'Last; Y := X + X; -- вызывает Numeric_Error end Numeric_Demo;



Исключение Program_Error



Исключение Program_Error

Исключение Program_Error возбуждается в следующих случаях: при попытке вызова подпрограммы, активизации задачи или конкретизации настройки, если тело соответствующего модуля еще не обработано. если выполнение функции достигло завершающего end так и не встретив инструкцию возврата (return ...) при межзадачном взаимодействии во время выполнения инструкции отбора с ожиданием (select ...), когда все альтернативы закрыты и отсутствует раздел else

Кроме того, это исключение может возбуждаться в случае возникновения ошибки элаборации.

procedure Program_Demo is Z : Integer; function Y(X : Integer) return Integer is begin if X < 10 then return X; elsif X < 20 then return X end if; end Y; -- если мы попали в эту точку, то это значит, -- что return не был выполненbegin Z := Y(30); end Program_Demo;