Описание простого массива
В общем случае, при объявлении массива, сначала производится описание соответствующего типа. Затем, экземпляр массива может быть создан используя описание этого типа.
type Stack is array (1..50) of Integer; Calculator_Workspace : Stack;
type Stock_Level is Integer range 0..20_000; type Pet is (Dog, Budgie, Rabbit); type Pet_Stock is array(Pet) of Stock_Level; Store_1_Stock : Pet_Stock; Store_2_Stock : Pet_Stock; |
В приведенном выше примере, тип Stack - это массив из 50-ти целочисленных элементов типа Integer, а Calculator_Workspace - это переменная типа Stack. Еще одним описанием массива является тип Pet_Stock. При этом, тип Pet_Stock - это массив элементов типа Stock_Level, а для индексирования элементов массива Stock_Level используется перечислимый тип Pet. Переменные Store_1_Stock и Store_2_Stock - это переменные типа Pet_Stock.
Общая форма описания массива имеет следующий вид:
type <имя_массива> is array (<спецификация_индекса>) of <тип_элементов_массива>; |
Необходимо заметить:
спецификация индекса может быть типом (например, Pet) спецификация индекса может быть диапазоном (например, 1..50) значения индекса должны быть дискретного типа
Описание простой записи
Как уже было сказано, запись - это структура данных состоящая из набора различных компонентов. В Аде, для описания такой структуры данных, необходимо описать тип записи. В общем случае, описание типа записи имеет следующий вид:
type <имя_записи> is record
<имя_поля_1> : <тип_поля_1>; <имя_поля_2> : <тип_поля_2>; . . . <имя_поля_N> : <тип_поля_N>; end record; |
Например:
type Bicycle is
record Frame : Construction; Maker : Manufacturer; Front_Brake : Brake_Type; Rear_Brake : Brake_Type; end record; My_Bicycle : Bicycle; |
Примечательно, что описание индивидуальных компонентов записи выглядит как описание переменных. Также, следует заметить, что описание типа записи не создает экземпляр объекта записи. В приведенном выше примере, тип Bicycle описывает структуру записи, а переменная My_Bicycle типа Bicycle - является экземпляром записи.
В отличие от массивов, Ада не позволяет создавать анонимные записи. Таким образом, следующий пример описания будет неправильным:
My_Bicycle : record -- использование анонимных -- записей - ЗАПРЕЩЕНО!!! Frame : Construction; Maker : Manufacturer; Front_Brake : Brake_Type; Rear_Brake : Brake_Type; end record; |
Из этого следует, что сначала необходимо описать тип записи, а затем описывать объекты этого типа.
Описание входов
Чтобы некоторая задача-клиент могла инициировать рандеву с задачей-сервером, описание спецификации задачи-сервера должно содержать описание соответствующего входа.
Следует заметить, что описания входов могут быть помещены только в описание спецификации задачи, кроме того, задача может иметь приватные входы.
Таким образом, описание входа в спецификации задачи-сервера может рассматриваться как декларация сервиса, предоставляемого задачей-сервером для задач-клиентов.
Для описания входов задачи-сервера используется зарезервированное слово entry, а семантика входов задач очень похожа на семантику процедур:
Так же, как и процедуры, входы задач имеют имена и могут иметь различные параметры. Для имен входов допускается совмещение имен, что подразумевает наличие у одной задачи нескольких входов с одинаковыми именами, но различными параметрами.
Параметры входов задачи, так же, как и параметры процедур, могут использоваться в режимах "in", "in out" и "out", и могут иметь значения по умолчанию.
При описании параметров входа задачи-сервера следует учитывать, что, в отличие от процедур, для входов задач не допускаются ссылочные параметры, хотя допускаются параметры ссылочного типа.
Кроме того, при описании входа может быть опционально указан дискретный тип, который будет использоваться для целого семейства входов в качестве типа индекса, значения которого применяются для определения индивидуальных входов в семействе.
Рассмотрим примеры следующих описаний:
task Anonimous_Task is
entry Start; end Anonimous_Task; type Level is (Low, Middle, Hight); task type Simple_Task is entry Read (Value: out Integer); entry Request (Level) (Item: in out Integer); end Simple_Task; |
Здесь описание спецификации объекта задачи Anonimous_Task содержит описание единственного входа Start, который не имеет ни одного параметра.
Спецификация типа задачи Simple_Task содержит описания двух входов.
Вход Read имеет один "out"-параметр Value
типа Integer.
Описание входа Request имеет один "in out"-параметр Item
типа Integer и использует указание дискретного типа индекса (перечислимый тип Level) представляя, таким образом, целое семейство входов.
Организация циклических вычислений
При решении реальных задач часто возникает необходимость в организации циклических вычислений. Все конструкции организации циклических вычислений в Аде имеют форму "loop ... end loop" с некоторыми вариациями. Для выхода из цикла может быть использована инструкция exit.
Организация доступа к индивидуальным битам
Организацию доступа к индивидуальным битам можно рассмотреть на примере операционной системы MS-DOS, в которой фиксированный адрес памяти 16#0417# содержит состояние установок клавиатуры.
Вид физического представления этого байта следующий:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Insert | Caps Lock |
Num Lock |
Scroll Lock |
Пример следующей простой программы демонстрирует организацию доступа к индивидуальным битам, характеризующим состояние клавиатуры:
with Ada.Text_IO; use Ada.Text_IO; with System.Storage_Elements; use System.Storage_Elements;
procedure Keyboard_Status_Demo is Keyboard_Address : constant Address := To_Address (16#0417#); type Status is (Not_Active, Active); for Status use (Not_Active => 0, Active => 1); for Status'Size use 1; type Keyboard_Status is record Scroll_Lock : Status; -- состояние Scroll Lock Num_Lock : Status; -- состояние Num Lock Caps_Lock : Status; -- состояние Caps Lock Insert : Status; -- состояние Insert end record; for Keyboard_Status use record Scroll_Lock at 0 range 4..4; -- бит 4 Num_Lock at 0 range 5..5; -- бит 5 Caps_Lock at 0 range 6..6; -- бит 6 Insert at 0 range 7..7; -- бит 7 end record; Keyboard_Status_Byte : Keyboard_Status; for Keyboard_Status_Byte'Address use Keyboard_Address; begin if Keyboard_Status_Byte.Insert = Active then Put_Line("Insert mode ON"); else Put_Line("Insert mode OFF"); end if; if Keyboard_Status_Byte.Caps_Lock = Active then Put_Line("Caps Lock mode ON"); else Put_Line("Caps Lock mode OFF"); end if; if Keyboard_Status_Byte.Num_Lock = Active then Put_Line("Num Lock mode ON"); else Put_Line("Num Lock mode OFF"); end if; if Keyboard_Status_Byte.Scroll_Lock = Active then Put_Line("Scroll Lock mode ON"); else Put_Line("Scroll Lock mode OFF"); end if; end Keyboard_Status_Demo; |
В данном примере, тип Status описан так, чтобы значения этого типа занимали ровно один бит.
Далее, с используя тип Status, описывается тип записи Keyboard_Status, внутреннее представление которой соответствует физической структуре байта состояния клавиатуры.
Следует заметить, что спецификатор "Scroll_Lock at 0 range 4 .. 4" указывает, что объект Scroll_Lock должен быть размещен по нулевому смещению в четвертой битовой позиции записи Keyboard_Status
(отсчет ведется в битах от начала записи).
Copyright (C) А.Гавва | V-0.4w май 2004 |
Организация доступа к отдельным элементам массива
Организацию доступа к отдельным элементам массива проще всего продемонстрировать на простых примерах. Так для обращения к значению элемента массива Store_1_Stock, описанного ранее, можно использовать:
if Store_1_Stock(Dog) > 10 then ... |
В приведенном примере производится чтение значения элемента массива Store_1_Stock
(доступ по чтению).
Для сохранения значения в элементе массива Store_2_Stock (доступ по записи) можно использовать:
Store_2_Stock(Rabbit) := 200; |
Необходимо отметить, что в обоих случаях доступ к элементу массива в Аде внешне никак не отличается от вызова функции.
Организация ввода/вывода
В отличие от своего прародителя Паскаля, Ада не содержит жестко встроенных средств ввода/вывода.
Вместо этого, для обеспечения поддержки ввода/вывода, Ада предусматривает простую, но эффективную коллекцию стандартных пакетов, средства которых концептуально подобны средствам ввода/вывода большинства современных расширений Паскаля.
Преимуществом такого решения является то, что стандартно предопределенные пакеты ввода/вывода, при необходимости, могут быть заменены.
Таким образом, высокопроизводительные приложения или системы использующие специфическое оборудование могут не использовать большинство стандартно предлагаемых средств ввода/вывода непосредственно (при этом следует учитывать, что может быть уменьшена или вовсе утрачена переносимость конечных программ).
В подобных случаях, стандартные средства ввода/вывода помогают формировать однородный базис для построения эффективной подсистемы файлового обмена.
Средства ввода/вывода Ады, определяемые стандартом Ada83, обеспечивают возможность работы с текстовыми и двоичными данными.
Стандарт Ada95 расширил эти средства возможностью использования гетерогенных потоков.
Прежде чем приступить к непосредственному обсуждению, необходимо заметить, что использование средств ввода/вывода Ады может показаться сначала достаточно не привычным.
Причина такой непривычности заключается в том, что природа строгой типизации данных Ады накладывает свой отпечаток и на средства ввода/вывода, а это значит, что весь ввод/вывод в Аде также подвержен строгой типизации данных.
Особенности программирования защищенных входов и подпрограмм
При программировании действий, выполняемых в телах защищенных входов и подпрограмм, следует учитывать, что время выполнения кода внутри защищенного объекта должно быть настолько кратким, насколько это возможно.
Это вызвано тем, что пока выполняется этот код, выполнение других задач, которые пытаются получить доступ к данным защищенного объекта, будет задержано.
Ада не позволяет принудительно ограничивать максимальную продолжительность выполнения кода во время защищенных действий, хотя и пытается убедиться в том, что задача не будет заблокирована в состоянии бесконечного ожидания доступа к защищенной процедуре или функции.
Потенциальными причинами возникновения подобных ситуаций могут быть попытки выполнения (внутри защищенных действий):
инструкции отбора (select) инструкции принятия (accept) инструкции вызова входа инструкции задержки выполнения создание или активация задачи
Перечисленные действия называют потенциально блокирующими.
Кроме того, обращение к любому потенциально блокирующему действию является также потенциально блокирующим.
Напомним, что при выполнении вызова защищенного входа в процессе обработки вызова защищенной процедуры или защищенного входа осуществляется проверка барьера.
Если барьер закрыт (условие барьера имеет значение False), то вызов ставится в очередь.
После завершения выполнения тела защищенной процедуры или защищенного входа значения всех барьеров вычисляются заново и, возможно, происходит выполнение тела входа.
Вычисление значения барьера для входа и постановка вызова входа в очередь являются защищенными операциями ассоциированного с ними защищенного объекта, и они могут быть названы защищенными действиями.
Любое исключение, возбужденное в процессе вычисления значения барьера для входа, приводит к возбуждению исключения Program_Error во всех задачах, которые в текущий момент находятся в очереди ожидания обслуживания вызова входа защищенного объекта.
Освобождение пространства динамической памяти
После того как мы рассмотрели как распределять пространство динамической памяти и выполнять инициализацию объектов, осталось рассмотреть как осуществляется освобождение более не используемого пространства динамической памяти.
Существует два способа освобождения пространства, которое было распределено в области динамической памяти, для его последующего повторного использования:
библиотека времени выполнения выполняет неявное освобождение распределеного пространства когда использованый для распределения пространства динамической памяти тип выходит из области видимости
Для этого случая примечательно то, что если тип описан на уровне библиотеки, то освобождение памяти не произойдет вплоть до завершения работы программы.
выполнение явного освобождения пространства динамической памяти в программе
Следует заметить, что стандарт языка Ада не определяет более четких требований и правил для алгоритмов библиотеки времени выполнения.
Поэтому, реальные алгоритмы будут определяться реализацией конкретного компилятора и его библиотек поддержки.
Следовательно, для уточнения этих сведений необходимо обратиться к документации на используемый компилятор, и здесь этот способ рассматриваться не будет.
Если вам необходимо освободить память (подобно тому как это делает системный вызов free в UNIX), то вы можете конкретизировать настраиваемую процедуру Ada.Unchecked_Deallocation.
Эта процедура называется непроверяемой (unchecked) поскольку компилятор не осуществляет проверку отсутствия ссылок на освобождаемый объект.
Таким образом, выполнение этой процедуры может привести к появлению "висячих" ссылок.
generic
type Object(<>) is limited private; type Name is access Object; procedure Ada.Unchecked_Deallocation(X : in out Name); pragma Convention(Intrinsic, Ada.Unchecked_Deallocatoin); |
Для показанного ранее ссылочного типа Element_Ptr, значения которого ссылаются на объекты типа Element
это может быть конкретизировано следующим образом:
procedure Free is new Ada.Unchecked_Deallocation(Object => Element, Name => Element_Ptr); |
Теперь, можно написать рекурсивную процедуру Free_List, которая будет удалять список, состоящий из объектов типа Element.
Начало списка будет указываться параметром процедуры Free_List, а для непосредственного удаления объекта типа Element
процедура Free_List будет использовать процедуру Free:
with Free; procedure Free_List (List_Head: in out Element) is begin if List_Head.Next /= null Free_List(List_Head.Next); -- рекурсивный вызов end if; Free(List_Head); end Free_List; |
начало которого указывается переменной Head_Element
можно выполнить следующее:
. . . Free_List(Head_Element); . . . |
При описании ссылочного типа Element_Ptr, может быть использована директива компилятора Controlled:
. . . type Element_Ptr is access Element; pragma Controlled(Element_Ptr); . . . |
Отладка контролируемых типов Некоторые рекомендации
Сложность отладки и проверки контролируемых типов заключается в том, что процедуры Initialize, Finalize и Adjust
вызываются автоматически, без какого-либо явного указания в программе.
В таком случае, можно поместить в тела соответствующих процедур Initialize, Finalize и Adjust
инструкции которые отображают диагностические сообщения, например, следующим образом:
procedure Initialize (Self: in out Controlled_Type) is
begin . . . -- код инициализации Ada.Text_IO.Put_Line("Initialize called for Controlled_Type"); end Initialize; procedure Finalize (Self: in out Controlled_Type) is begin . . . -- код очистки Ada.Text_IO.Put_Line("Finalize called for Controlled_Type"); end Finalize; procedure Adjust (Self: in out Controlled_Type) is begin . . . -- код подгонки Ada.Text_IO.Put_Line("Adjust called for Controlled_Type"); end Adjust; |
Не смотря на простоту данного подхода, он является достаточно эффективным способом проверки корректности выполняемых действий.
Перечислим также некоторые рекомендации которые помогут избежать некоторых ошибок при написании процедур Initialize, Finalize и Adjust:
Первой инструкцией подпрограмм Finalize и Adjust должна быть проверка if, которая проверяет, что объект не nil. Следует остерегаться любого декларативного кода, который может быть выполнен до проверки на nil.
Подпрограммы Finalize и Adjust должны быть симметричны и инверсны по отношению друг к другу.
Если контролируемый тип является производным от контролируемого родителя, то процедура Initialize производного типа всегда должна вызывать реализацию Initialize родителя перед выполнением инициализации необходимой для части расширения.
Если контролируемый тип является производным от контролируемого родителя, то Finalize и Adjust производного типа должны всегда вызывать реализацию Finalize и Adjust родителя после выполнения действий, необходимых для части расширения.
При тестировании подпрограммы Finalize, необходимо проверить, что значение объекта после очистки действительно будет nil.
Для агрегата или вызова функции, реализация конкретного компилятора может создавать, а может и не создавать отдельный анонимный объект. Следовательно, подпрограммы Finalize и Adjust
должны поддерживать создание временных анонимных объектов (следует остерегаться любых ограничений на число существующих объектов).
Следует помнить, что при программировании контролируемых типов, любое присваивание, описание константы или динамическое размещение которое использует инициализационный агрегат, в результате, может привести к вызову Finalize и/или Adjust.
В частности, не следует выполнять подобных операций при реализации процедур Finalize и Adjust
(это может привести к бесконечно рекурсивным вызовам).
Copyright (C) А.Гавва | V-0.4w май 2004 |
Отложенные константы (deferred constants)
В некоторых спецификациях пакетов возникает необходимость описать константу приватного типа. Это можно выполнить таким же образом как и описание приватного типа (а также большинство опережающих ссылок). В общедоступной части спецификации пакета мы создаем неполное описание константы, после чего, компилятор ожидает получить полное описание константы в приватной части спецификации пакета. Например:
package Coords is
type Coord is private; Home: constant Coord; -- отложенная константа! private type Coord is record X : Integer; Y : Integer; end record; Home : constant Coord := (0, 0); end Coords; |
В результате такого описания, пользователи пакета Coords
"видят" константу Home, которая имеет приватный тип Coord, и могут использовать эту константу в своем коде.
При этом, детали внутреннего представления этой константы им не доступны и они могут о них не заботиться.
Отрезки (array slices)
Для одномерных массивов Ада предусматривает удобную возможность указания нескольких последовательных компонент массива. Такая последовательность компонент массива называется отрезком массива (array slice). В общем случае, отрезок массива может быть задан следующим образом:
<имя_массива> (<диапазон_значений_индекса>) |
Таким образом, для переменной Calculator_Workspace типа Stack, рассмотренных ранее, можно указать отрезок, содержащий элементы с 5-го по 10-й, следующим образом:
Calculator_Workspace (5 .. 10) := (5, 6, 7, 8, 9, 10); |
В данном примере выполняется присваивание значений элементам массива Calculator_Workspace, которые попадают в указанный отрезок, с использованием позиционного агрегата массива.
Приведем еще один простой пример:
Calculator_Workspace (25 .. 30) := Calculator_Workspace (5 .. 10); |
Напомним что использование отрезков допускается только для одномерных массивов.
Пакет AdaDirect_IO
Пакет Ada.Direct_IO построен поверх пакета Ada.Sequential_IO.
Он предусматривает возможность прямого обращения к необходимой записи в файле, определения размера файла и определения текущего индекса.
Кроме этого, он дополнительно позволяет открывать файлы в режиме - Inout_File
(чтение/запись).
Такие средства, в совокупности с подходящим индексирующим пакетом, должны позволять построение пакета файловой обработки очень высокого уровня.
Следующий пример демонстрирует использование файлов с прямым доступом:
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Direct_IO; -- настраиваемый пакет with Personnel_Details; -- имеет: use Personnel_Details; -- тип записи "Personnel", -- процедуру "Display_Personnel", -- и т.д. ... with Display_Menu; -- внешняя процедура отображения меню procedure Direct_Demo is package Person_IO is new Direct_IO(Personnel); Data_File : Person_IO.File_type; A_Person : Personnel; Option : Integer; Employee_No : Integer; begin Person_IO.Open(Data_File, Inout_File, "Person.dat"); loop Display_Menu; Get_Option(Option); case Option is when 1 => Get(Employee_No); Set_Index(Positive_Count(Employee_No)); Read(Data_File, A_Person); Display_Person(A_Person); when 2 => Get(Employee_No); Set_Index(Positive_Count(Employee_No)); Read(Data_File, A_Person); Get_New_Details(A_Person); Write(Data_File, A_Person); when 3 => exit; when others => Put("not a great option!"); end case; end loop; Close(Data_File); end Direct_Demo; |
Здесь, для краткости подразумевается, что записи о служащих сохраняются в порядке номеров служащих - Employee_No.
Также заметим, что мы не акцентируем внимание на содержимом внешних модулей: пакете Personnel_Details и процедуре Display_Menu.
Пакет AdaExceptions
Стандартный пакет Ada.Exceptions предоставляет некоторые дополнительные средства, которые могут быть использованы при обработке исключений.
Описанный в нем объект:
Event : Exception_Occurence; |
и подпрограммы:
функция Exception_Name(Event) | - | возвращает строку имени исключения, начиная от корневого библиотечного модуля |
функция Exception_Information(Event) | - | возвращает строку детальной информации о возникшем исключении |
функция Exception_Message(Event) | - | возвращает строку краткого объяснения исключения |
процедура Reraise_Occurence(Event) | - | выполняет повторное возбуждение исключения Event |
процедура Reraise_Exception(e, "Msg") | - | выполняет возбуждение исключения e с сообщением "Msg" |
Могут быть весьма полезны при необходимости обработки неожиданных исключений. В таких случаях можно использовать код который подобен следующему:
. . .
exception . . . when The_Event: others => Put("Unexpected exeption is "; Put(Exeption_Name(The_Event)); New_Line; |
Пакет AdaInterrupts
Не-вложенная установка и удаление обработчиков прерываний полагается на дополнительные средства стандартного пакета Ada.Interrupts
спецификация которого имеет следующий вид:
package Ada.Interrupts is
type Interrupt_ID is Определяется_Реализацией; type Parameterless_Handler is access protected procedure; function Is_Reserved (Interrupt : Interrupt_ID) return Boolean; function Is_Attached (Interrupt : Interrupt_ID) return Boolean; function Current_Handler (Interrupt : Interrupt_ID) return Parameterless_Handler; procedure Attach_Handler (New_Handler : in Parameterless_Handler; Interrupt : in Interrupt_ID); procedure Exchange_Handler (Old_Handler : in out Parameterless_Handler; New_Handler : in Parameterless_Handler; Interrupt : in Interrupt_ID); procedure Detach_Handler (Interrupt : in Interrupt_ID); function Reference (Interrupt : Interrupt_ID) return System.Address; private . . . -- стандартом не определено end Ada.Interrupts; |
Процедура Attach_Handler используется для установки соответствующего обработчика прерывания, переопределяя любой существующий обработчик (включая обработчик пользователя).
Если параметр New_Handler - null, то осуществляется восстановление обработчика по умолчанию.
Если параметр New_Handler указывает защищенную процедуру для которой не была применена директива компилятора Interrupt_Handler, то возбуждается исключение Programm_Error.
Пакет AdaSequential_IO
Стандартный настраиваемый пакет Ada.Sequential_IO позволяет нам создавать файлы, состоящие из компонентов любого типа.
При этом, должно соблюдаться единственное условие: тип компонентов должен быть ограничен (constrained).
Базовое содержимое настраиваемого пакета Ada.Sequential_IO идентично пакету Ada.Text_IO, за исключением того, что процедуры Get и Put
соответственно заменены процедурами Read и Write, и эти процедуры будут работать с типом данных для которого была произведена конкретизация настраиваемого пакета.
Кроме этого, отсутствует понятие строки текста, и, следовательно, нет функции End_Of_Line и процедур Skip_Line, New_Line.
Примером использования этого пакета может служить следующее:
with Ada.Sequential_IO; -- настраиваемый пакет
with Personnel_Details; -- имеет тип записи "Personnel" use Personnel_Details; with Produce_Retirement_Letter; procedure Sequential_Demo is package Person_IO is new Ada.Sequential_IO(Personnel); Data_File : Person_IO.File_type; A_Person : Personnel; begin Person_IO.Open(Data_File, In_File, "person.dat"); while not Person_IO.End_Of_File(Data_File) loop Person_IO.Read(Data_File, A_Person); if A_Person.Age > 100 then Produce_Retirement_Letter(A_Person); end if; end loop; Close(Data_File); end Sequential_Demo; |
Заметим, что в данном примере мы не акцентируем внимание на содержимом пакета Personnel_Details, а только указываем в комментарии, что он описывает тип записи Personnel.
После открытия файла, он последовательно обрабатывается от позиции начала файла, и до тех пор, пока не будет достигнут конец файла, или будет выполнена одна из процедур Reset или Close.
Прямой доступ к элементам файла не возможен (отсюда и название: Ada.Sequential - последовательный).
Пакет AdaText_IO
Основой организации текстового ввода/вывода Ады является пакет Ada.Text_IO
и коллекция его дочерних пакетов.
Этот пакет обеспечивает средства, которые позволяют манипулировать текстовыми файлами.
Примерами таких средств могут служить подпрограммы Close, Delete, Reset, Open, Create ...
Главный тип данных пакета Ada.Text_IO - это лимитированный приватный тип File_Type. Он является внутренним представлением файла.
Стандарт Ada95 добавил тип File_Access, как ссылку на тип File_Type
(объекты имеющие тип File_Access часто называют дескрипторами файлов).
При открытии или создании файла, производится ассоциирование между именем файла в системе и объектом типа File_Type.
Кроме того, при открытии или создании файла, необходимо указывать режим доступа к файлу:
In_File | - | чтение файла |
Out_File | - | запись в файл |
Append_File | - | запись в конец существующего файла (Ada95) |
Заметим, что эти значения имеют тип File_Mode, который также описывается в пакете Ada.Text_IO.
После этого, объект типа File_Type может быть использован для выполнения непосредственных обращений к файлу.
В приведенном ниже примере, процедура Create создает файл "first_file.dat", после чего, в этот файл процедурами Put и New_Line производится запись строки "It is my first text file!".
В завершение, процедура Close закрывает ранее открытый файл.
with Ada.Text_IO; use Ada.Text_IO;
procedure Demo_File_IO is My_File : Ada.Text_IO.File_type; begin Create(File => My_File, Mode => Out_File, Name => "first_file.dat"); Put(File => My_File, Item => "It is my first text file!"); New_Line(My_File); Close(My_File); -- требуется! Ада может не закрыть -- открытый вами файл end Demo_File_IO; |
Программа, представленная в следующем примере, выполняет посимвольное чтение данных из одного файла ("input.dat") и посимвольную запись этих же данных в другой файл ("output.dat").
with Ada.Text_IO; use Ada.Text_IO;
procedure Read_Write is Input_File : File_type; Output_File : File_type; Char : Character; begin Open(Input_File, In_File, "input.dat"); Create(Output_File, Out_File, "output.dat"); while not End_Of_File(Input_File) loop while not End_Of_Line(Input_File) loop Get(Input_File, Char); Put(Output_File, Char); end loop; Skip_Line(Input_File); New_Line(Output_File); end loop; Close(Input_File); Close(Output_File); end Read_Write; |
Необходимо обратить внимание на то, что в таких языках программирования как Ада и Паскаль существует концепция терминатора строки, который не является обычным символом файла.
Это значит, что понятие "конец строки" ("End Of Line", или сокращенно - EOF) Ады отличается от того, что принято в системах DOS, Windows и UNIX.
В этих системах для обозначения конца строки используется обычный символ (символ "CR" - для UNIX, и символы: "CR", "LF" - для DOS и Windows), который может быть обработан обычными средствами символьной обработки.
Для того чтобы, при чтении из файла, процедура Read
"прошла" этот терминатор, необходимо использовать процедуру Skip_Line.
Подобным образом, для того чтобы осуществить построчную запись выходного файла, должна быть использована процедура New_Line.
Пакет Ada.Text_IO обеспечивает большое число процедур для выполнения различных файловых манипуляций.
В качестве примера наиболее часто используемых процедур можно перечислить следующие процедуры:
Create | - | Создает файл с указанным именем и режимом использования. Примечательно, что если файл имеет строку null, то файл является временным и позднее будет удален. |
Open | - | Открывает файл с указанным именем и режимом использования. |
Delete | - | Удаляет указанный файл. При попытке удалить открытый файл происходит ошибка. |
Reset | - | Возвращает позицию чтения (или записи) в начало файла. |
End_of_File | - | Возвращает истину если мы находимся в конце текущего файла. |
End_of_Line | - | Возвращает истину если мы находимся в конце текущей строки текста. |
Is_Open | - | Возвращает истину если текущий файл открыт. |
Mode | - | Возвращает режим использования текущего файла. |
Name | - | Возвращает строку имени текущего файла. |
Поэтому, для получения более подробных сведений лучше всего непосредственно обратиться к спецификации пакета Ada.Text_IO.
Пакеты
Хотя корни Ады лежат в языке Паскаль, концепция пакетов была заимствована из других языков программирования и подверглась значительному влиянию последних наработок в области разработки программного обеспечения обсуждавшихся в 1970-х годах.
Несомненно, что одной из главных инноваций в этот период была концепция программного модуля (или пакета).
Следует заметить, что концепция пакетов не рассматривает то, как программа будет выполняться.
Идея данного подхода посвящена проблеме построения программного комплекса, облегчению понимания того как он устроен и, следовательно, упрощению его сопровождения.
Пакеты для численной обработки
Полное обсуждение поддержки численной обработки в Аде - весьма обширная тема. Поэтому здесь, чтобы указать "куда бежать дальше", мы только приведем список пакетов для численной обработки, которые предоставляются поставкой компиляора GNAT:
Ada.Numerics Ada.Numerics.Aux Ada.Numerics.Float_Random Ada.Numerics.Discrete_Random
Ada.Numerics.Complex_Types Ada.Numerics.Complex_Elementary_Functions Ada.Numerics.Elementary_Functions Ada.Numerics.Generic_Complex_Types Ada.Numerics.Generic_Complex_Elementary_Functions Ada.Numerics.Generic_Elementary_Functions Ada.Numerics.Long_Complex_Types Ada.Numerics.Long_Complex_Elementary_Functions Ada.Numerics.Long_Elementary_Functions Ada.Numerics.Long_Long_Complex_Types Ada.Numerics.Long_Long_Complex_Elementary_Functions Ada.Numerics.Long_Long_Elementary_Functions Ada.Numerics.Short_Complex_Types Ada.Numerics.Short_Complex_Elementary_Functions Ada.Numerics.Short_Elementary_Functions |
Следует заметить, что пакет Ada.Numerics.Aux, который указан выше, не предназначен для непосредственного использования в программах пользователя, и упоминается только с целью полноты показанного выше списка.
Параметры командной строки
Во время выполнения программы существует возможность получения доступа к аргументам, которые указаны в командной строке запуска программы на выполнение.
Такая возможность обеспечивается средствами стандартного пакета Ada.Command_Line, спецификация которого имеет следующий вид:
package Ada.Command_Line is
pragma Preelaborate (Command_Line); function Argument_Count return Natural; function Argument (Number : in Positive) return String; function Command_Name return String; type Exit_Status is Определяемый_Реализацией_Целочисленный_Тип; Success : constant Exit_Status; Failure : constant Exit_Status; procedure Set_Exit_Status (Code : in Exit_Status); private -- Стандартом языка не определено end Ada.Command_Line; |
В качестве простой демонстрации использования средств этого предопределенного стандартом пакета, рассмотрим следующий пример:
with Ada.Text_IO; with Ada.Command_Line;
procedure Show_CMDLine is begin Ada.Text_IO.Put_Line ("The program " & '"' & Ada.Command_Line.Command_Name & '"' & " has " & Ada.Command_Line.Argument_Count'Img & " argument(s):"); for I in 1..Ada.Command_Line.Argument_Count loop Ada.Text_IO.Put_Line (" The argument " & I'Img & " is " & '"' & Ada.Command_Line.Argument (I) & '"'); end loop; end Show_CMDLine; |
Данная программа отображает фактическое имя запущенной программы, количество переданных в командной строке аргументов, а затем показывает строковые значения переданных аргументов.
Процедура Set_Exit_Status, пакета Ada.Command_Line, может быть использована для возврата во внешню среду кода статуса завершения работы программы (иначе - кода ошибки).
Параметры настройки для настраиваемых модулей
Существует три типа параметров для настраиваемых модулей:
параметры-типы параметры-значения параметры-подпрограммы
Необходимо заметить, что до настоящего момента в примерах мы рассматривали только параметры-типы.
Параметры-подпрограммы
В качестве параметра настройки для настраиваемого модуля может быть передана подпрограмма.
Необходимость в параметрах-подпрограммах чаще всего возникает когда какой-либо формальный параметр-тип настраиваемого модуля описан как приватный или лимитированный приватный тип.
В таких случаях, Ада накладывает традиционные ограничения на использование операций над экземплярами данного типа внутри тела настраиваемого модуля.
Однако, при этом может возникнуть необходимость в осуществлении сравнения или выполнения проверки на равенство значений данного типа, внутри тела настраиваемого модуля.
Следовательно, параметры-подпрограммы являются механизмом, который предоставляет для компилятора информацию о том как осуществлять эти действия.
В качестве примера рассмотрим типичный случай требующий применение параметров-подпрограмм.
Предположим, что в качестве одного из параметров настраиваемого модуля используется лимитированый приватный тип.
Тогда, для этого лимитированного приватного типа, с помощью параметров-подпрограмм, можно осуществить передачу операций: проверка на равенство и присваивание.
generic
type Element is limited private; with function "="(E1, E2 : Element) return Boolean; with procedure Assign(E1, E2 : Element); package Stuff is . . . |
Конкретизация такого настраиваемого модуля может иметь вид:
package Things is new Stuff(Person, Text."=", Text.Assign); |
Для формального параметра-подпрограммы может быть указана подпрограмма используемая по-умолчанию. Тогда, при конкретизации настраиваемого модуля, фактический параметр-подпрограмма может не указываться.
Предположим, что у нас есть подпрограмма, спецификация которой имеет вид:
procedure My_Assign(E1, E2 : Person); |
Тогда, при описании формального параметра-подпрограммы Assign, мы можем указать процедуру My_Assign как подпрограмму которую необходимо использовать по-умолчанию следующим образом:
generic
type Element is limited private; with function "="(E1, E2 : Element) return Boolean; with procedure Assign(E1, E2 : Element) is My_Assign(E1, E2 : Person); package Stuff is . . . |
В результате, конкретизация настраиваемого модуля, с использованием подпрограммы заданной по-умолчанию, может иметь следующий вид:
package Things is new Stuff(Person, Text."="); |
И наконец, при описании спецификации формального параметра-подпрограммы, можно указать, что выбор процедуры по-умолчанию должен осуществляться согласно традиционных правил выбора подпрограмм.
Для функции, реализующей действие знака проверки на равенство ("=") мы можем указать это следующим образом:
generic type Element is limited private; with function "="(E1, E2 : Element ) return Boolean is <>; . . . |
Теперь, если при конкретизации настраиваемого модуля для функции "="
не будет представлена соответствующая функция, то будет использоваться функция проверки на равенство по-умолчанию, выбранная в соответствии с фактическим типом Element.
Например, если фактический тип Element - тип Integer, то будет использоваться обычная, для типа Integer, функция "=".
Параметры-типы
Не смотря на привлекательные свойства настраиваемых модулей, при определении характера задач, решаемых с помощью настраиваемого модуля, необходимо иметь возможность накладывать некоторые ограничения.
Допустим, что некоторые настраиваемые модули предусматривают возможность суммирования массива чисел.
Очевидно, что это характерно только для чисел, и мы не можем производить суммирование записей.
Следовательно, для того, чтобы защититься от конкретизации настраиваемых модулей с указанием не подходящих типов данных, требуется возможность в установке некоторых ограничений.
Кроме того, желательно чтобы компилятор также мог осуществлять проверку того, что мы не выполняем какие-нибудь не допустимые действия с переменной внутри кода настраиваемого модуля, например, чтобы он не разрешал использовать атрибут 'Pred для записей.
Решение таких задач обеспечивается механизмом который основан на виде спецификации формального параметра-типа.
Таким образом, спецификация формального параметра-типа определяет категорию типов, которые могут быть использованы при конкретизации настраиваемого модуля, а также те действия, которые можно осуществлять над формальным параметром внутри настраиваемого модуля.
Ниже показан общий список вариантов спецификаций формальных параметров-типов и различные ограничения, накладываемые на выбор фактических параметров-типов настраиваемых модулей.
type T is limited private -- тип T - любой тип type T is private -- тип T - любой не лимитированный тип
type T is (<>) -- тип T любой дискретный тип -- (целочисленный или перечислимый) type T is range <> -- тип T любой целочисленный тип type T is mod <> -- тип T любой модульный целочисленный тип type T is digits <> -- тип T любой вещественный тип с плавающей точкой type T is delta <> -- тип T любой вещественный тип с фиксированной точкой type T is delta <> digits <> -- тип T любой вещественный децимальный тип type T is access Y; -- тип T любой ссылающийся на Y ссылочный тип type T is access all Y; -- тип T любой "access all Y" ссылочный тип type T is access constant Y; -- тип T любой "access constant Y" ссылочный тип -- примечание: тип Y может быть предварительно описанным -- настраиваемым параметром type T is array(Y range <>) of Z; -- тип T любой неограниченный массив элементов типа Z -- у которого Y - подтип индекса type T is array(Y) of Z; -- тип T любой ограниченный массив элементов типа Z -- у которого Y - подтип индекса -- примечание: тип Z (тип компонента фактического массива) -- должен совпадать с типом формального массива. -- если они не являются скалярными типами, -- то они оба должны иметь тип -- ограниченного или неограниченного массива type T is new Y; -- тип T любой производный от Y тип type T is new Y with private; -- тип T любой не абстрактный тэговый тип -- производный от Y type T is abstract new Y with private; -- тип T любой тэговый тип производный от Y type T is tagged private; -- тип T любой не абстрактный не лимитированый -- тэговый тип type T is tagged limited private; -- тип T любой не абстрактный тэговый тип type T is abstract tagged private; -- тип T любой не лимитированый тэговый тип type T is abstract tagged limited private; -- тип T любой тэговый тип |
Для того чтобы лучше понять правила управляющие конкретизацией для параметров типа массив, рассмотрим следующий настраиваемый пакет:
generic type Item is private; type Index is (<>); type Vector is array (Index range <>) of Item; type Table is array (Index) of Item; package P is . . . |
и типы:
type Color is (red, green, blue); type Mix is array (Color range <> ) of Boolean; type Option is array (Color) of Boolean; |
тогда, Mix может соответствовать Vector, а Option может соответствовать Table.
package R is new P( Item => Boolean, Index => Color, Vector => Mix, Table => Option); |
Параметры-значения
Параметры-значения позволяют указывать значения для переменных внутри настраиваемого модуля:
generic
type Element is private; Size: Positive := 200; package Stacks is procedure Push... procedure Pop... function Empty return Boolean; end Stacks; package body Stacks is Size : Integer; theStack : array(1..Size) of Element; . . . |
Тогда, создать экземпляр настраиваемого модуля можно одним из следующих способов:
package Fred is new Stacks(Element => Integer, Size => 50);
package Fred is new Stacks(Integer, 1000); package Fred is new Stacks(Integer); |
Следует обратить внимание на то, что при конкретизации настраиваемого модуля фактический параметр-значение должен быть обязательно указан только в случаях когда для формального параметра-значения не представлено значение по-умолчанию.
В качестве параметров-значений допускается использование строк.
generic
type Element is private; File_Name : String; package .... |
Примечательно, что параметр File_Name, имеющий строковый тип String, - не ограничен (not constrained).
Это идентично строковым параметрам для подпрограмм.
Перечислимые типы
До настоящего момента были рассмотрены типы данных которые представляли численные значения (целые и вещественные числа). Однако, при решении многих практических задач, важное значение имеет понятие перечислимого типа. Перечислимыми типами называют такие типы данных значения которых перечислены, то есть представлены списком некоторых значений. Перечислимый тип полезен когда необходимо представить фиксированное множество значений, которые не являются числами. Примером может служить представление дней недели или месяцев в году.
Переименование библиотечного модуля
Предположим, что в нашей библиотеке есть пакет который часто используется, и предположим, что этот пакет имеет довольно длинное имя.
Пользуясь переименованием, мы можем указать этот пакет в спецификаторе контекста with, после чего, переименовать пакет с длинным именем, и скомпилировать полученный модуль с более коротким именем обратно в библиотеку.
Например:
with Graphics.Common_Display_Types; package CDT renames Graphics.Common_Display_Types; |
Далее, мы можем использовать библиотечный модуль CDT, с более коротким именем, также как и библиотечный модуль Graphics.Common_Display_Types.
При этом следует избегать переименований, когда новое имя очень сильно отличается от оригинала.
Copyright (C) А.Гавва | V-0.4w май 2004 |
Переименование исключений
В некоторых случаях полезно осуществить локальное переименование исключения:
with Ada.IO_Exceptions;
package My_IO is Data_Error: exception renames Ada.IO_Exceptions.Data_Error; . . . end My_IO; |
Переименование компонентов
Наиболее часто забываемым свойством переименования Ады является возможность предоставления собственного имени определенному компоненту составного типа:
with Ada.Text_IO;
package Rename_A_Variable is Record_Count: renames Ada.Text_IO.Count; . . . end Rename_A_Variable; |
Переименование отрезка массива
Предположим, что у нас есть следующая строка:
Name : String(1..60); |
Причем, отрезок (1..30) - это фамилия (last name), отрезок (31..59) - имя (first name), символ в позиции 60 - это инициал отчества (middle name).
Используя переименования мы можем выполнить следующее:
declare
Last : String renames Name(1..30); First : String renames Name(31..59); Middle : String renames Name(60..60); begin Ada.Text_IO.Put_Line(Last); Ada.Text_IO.Put_Line(First); Ada.Text_IO.Put_Line(Middle); end; |
В результате, каждый вызов Put_Line будет обращаться к именованному объекту, а не к диапазону индексов.
При этом не осуществляется распределение дополнительного пространства для данных, а обеспечивается новое имя для доступа к уже существующим данным.
Переименование поля записи
Предположим, что у нас имеются следующие описания:
subtype Number_Symbol is Character range '0' .. '9'; subtype Address_Character is Character range
Ada.Characters.Latin_1.Space .. Ada.Characters.Latin_1.LC_Z; type Address_Data is array (Positive range <>) of Address_Character; type Number_Data is array (Positive range <>) of Number_Symbol; type Phone_Number is record Country_Code : Number_Data(1..2); Area_Code : Number_Data(1..3); Prefix : Number_Data(1..3); Last_Four : Number_Data(1..4); end record; type Address_Record is record The_Phone : Phone_Number; Street_Address_1 : Address_Data(1..30); Street_Address_2 : Address_Data(1..20); City : Address_Data(1..25); State : Address_Data(1..2); Zip : Number_Data(1..5); Plus_4 : Number_Data(1..4); end record; One_Address_Record : Address_Record; |
Используя переименование, мы можем переименовать один из внутренних компонентов переменной записи One_Address_Record типа Address_Record, для прямого использования в программе.
Например, мы можем переименовать Area_Code в инструкции блока:
declare
AC: Number_Data renames One_Address_Record.The_Phone.Area_Code; begin . . . end; |
Описание AC не требует никакого распределения дополнительного пространства данных.
Вместо этого, оно локализует имя для компонента, который вложен в запись.
При наличии компонентов записей с большим уровнем вложения, такой подход может оказаться весьма удобным.
Переименование знаков операций
В некоторых случаях, знак операции для типа описанного в пакете, который указан в спецификаторе контекста with, не является непосредственно видимым.
В действительности, правила Ады заключаются в том, что сущность, находящаяся в контексте, не будет непосредственно видимой до тех пор, пока не будет явно указано, что она непосредственно видима.
Спецификатор использования use для пакета всегда делает непосредственно видимыми знаки операций операции для типа описанного в пакете, однако, спецификатор использования use одновременно делает непосредственно видимыми все публично доступные ресурсы пакета, что может оказаться не желательным.
Переименование позволяет явно импортировать только те знаки операций, которые реально необходимы.
При этом, видимость всех остальных ресурсов пакета остается не тронутой.
Следующий пример показывает как это можно выполнить:
with Ada.Text_IO;
procedure diamond1 is package TIO renames Ada.Text_IO; function "+" (L, R: TIO.Count) return TIO.Count renames TIO."+"; function "-" (L, R: TIO.Count) return TIO.Count renames TIO."-"; . . . |
Использование знаков операций облегчается при предварительном планировании использования знаков операций в процессе разработки пакета.
В следующем примере знаки операций переименовываются во вложенном пакете, который, в последствии, может быть сделан непосредственно выдимым с помощью спецификатора использования use:
package Nested is
type T1 is private; type Status is (Off, Low, Medium, Hight); package Operators is function ">=" (L, R: Status) return Boolean renames Nested.">="; function "=" (L, R: Status) return Boolean renames Nested."="; end Operators; private type T1 is ... . . . end Nested; |
Показанный выше, вложенный пакет может быть сделан доступным путем указания спецификатора контекста "with Nested;", и последующего спецификатора использования "use Nested.Operators;" следующим образом:
with Nested;
procedure Test_Nested is use Nested.Operators; . . . begin . . . |
Возможно, что не все одобрительно встретят подобную технику, однако она упрощает использование инфиксной формы знаков операций, поскольку позволяет отказаться от локального переименования.
Следует заметить, что такое решение будет лучше чем использование спецификатора использования типа "use type", поскольку делает видимым только ограниченное множество знаков операций.
Однако, такой подход требует дополнительных усилий от разработчика пакета.
Переименования
Ада предоставляет программисту возможность осуществлять переименования.
Следует заметить, что переименование иногда вызывает споры в организациях программирующих на Аде.
Некоторым людям переименование нравится, а другим - нет.
Существует несколько важных вещей, которые необходимо понять:
Переименование не создает нового пространства для данных. Оно просто создает новое имя для уже присутствующей сущности.
Не следует постоянно переименовывать одно и то же. Этип можно запутать всех, включая самого себя.
Переименование необходимо использовать для упрощения кода. Введение нового имени, в некоторых случаях, делает код более легко читаемым.
При использовании переименований следует учитывать, что частое переименование объектов и их значений может создать трудности в понимании исходного текста.
Хотя каждое новое имя может иметь определенный смысл в контексте нового пакета, при большом количестве последующих переименований становиться трудно отследить имя оригинала.
Переменные окружения программы
Для поддержки организации взаимодействия с переменными окружения операционной системы, реализация компилятора GNAT содержит дополнительный пакет Ada.Command_Line.Environment
спецификация которого имеет следующий вид:
package Ada.Command_Line.Environment is
function Environment_Count return Natural; function Environment_Value (Number : in Positive) return String; end Ada.Command_Line.Environment; |
Функция Environment_Count
возвращает общее количество переменных окружения, а функция Environment_Value
возвращает строку в которой содержится имя переменной окружения и ее значение, разделенные символом равенства.
Copyright (C) А.Гавва | V-0.4w май 2004 |
Первая программа
Для того, чтобы дать "почувствовать", что представляет из себя программа написанная на языке Ада рассмотрим простую программу. Традиционно, первая программа - это программа которая выводит на экран приветствие: "Hello World!". Не будем нарушать традицию. Итак, на Аде такая программа будет иметь следующий вид:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is begin Put_Line("Hello World!"); end Hello; |
Давайте детально рассмотрим из каких частей состоит текст этой программы.
Строка "procedure Hello is" является заголовком процедуры и она указывает имя нашей процедуры.
Далее, между зарезервированными словами begin и end, располагается тело процедуры Hello.
В этом примере тело процедуры очень простое и состоит из единственной инструкции "Put_Line("Hello World!");".
Эта инструкция осуществляет вывод приветствия на экран, вызывая процедуру Put_Line.
Процедура Put_Line располагается в пакете текстового ввода/вывода Ada.Text_IO, и становится доступной благодаря спецификации контекста в инструкциях "with Ada.Text_IO;" и "use Ada.Text_IO;" (спецификация контекста необходима для указания используемых библиотечных модулей). Здесь, спецификатор контекста состоит из двух спецификаторов: спецификатора совместности with и спецификатора использования use. Cпецификатор совместности with указывает компоненты которые будут использоваться в данном компилируемом модуле. Cпецификатор использования use делает имена используемых объектов непосредственно доступными в данном компилируемом модуле.
Программа Hello настолько проста, что в ней нет ни переменных, ни какой-либо обработки данных, поэтому, несколько забегая вперед, приведем общий вид процедуры.
|
(другими словами - головной программой). Таким образом, процедура без параметров может быть выбрана как головная программа во время линковки.
Теперь, приведем еще один простой пример, в котором, для выдачи сообщения приветствия, используется ранее рассмотренная процедура Hello:
with Hello; -- указывает на использование показанной ранее -- процедуры Hello procedure Use_Hello is begin Hello; -- вызов процедуры Hello end Use_Hello; |
Подпрограммы
В Аде, также как и во всех современных языках программирования, подпрограммы позволяют программисту группировать инструкции в самостоятельные, логически законченные алгоритмические единицы, которые, в последствии, могут быть вызваны и выполнены в любом месте программы.
Они являются элементарным базовым средством для повторного использования кода и разделения одной большой задачи на самостоятельные подзадачи меньшего размера (декомпозиция).
Использование подпрограмм позволяет уменьшить как общий размер исходных текстов программы, так и общий размер результирующих исполняемых файлов.
Таким образом, применение подпрограмм облегчает общее управление проектом и упрощает его сопровождение.
При этом, подпрограммы Ады обладают некоторыми свойствами, которые будут новыми для программистов использующих Паскаль и/или Си.
Совмещение (overloading), именованные параметры, значение параметров по-умолчанию, режимы передачи параметров и возврата значений - все это значительно отличает подпрограммы языка Ада.
Подпрограммы как библиотечные модули
Любая подпрограмма Ады, при необходимости, может быть оформлена как абсолютно самостоятельный независимый библиотечный подпрограммный модуль.
Рассмотрим как это делается на примере процедур Ive_Got_A_Procedure и Display_Values, из предыдущего примера о раздельной компиляции.
Теперь, процедура Display_Values будет оформлена как самостоятельный библиотечный подпрограммный модуль, а процедура Ive_Got_A_Procedure
будет ее использовать.
В этом случае, полное описание процедуры Display_Values
будет состоять из двух файлов: файла спецификации и файла тела процедуры.
Примечание:
В системе компилятора GNAT существует соглашение согласно которому файлы спецификаций имеют расширение ads (ADa Specification), а файлы тел имеют расширение adb (ADa Body).
Файл спецификации процедуры Display_Values (display_values.ads) будет иметь следующий вид:
procedure Display_Values(Number : Integer); |
Файл тела процедуры Display_Values (display_values.adb) будет иметь следующий вид:
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Display_Values(Number : Integer) is begin Put(Number); New_Line; end Display_Values; |
Третий файл - это файл тела процедуры Ive_Got_A_Procedure (ive_got_a_procedure.adb). В этом случае он будет иметь следующий вид:
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Display_Values;
procedure Ive_Got_A_Procedure is X : Integer := 6; Y : Integer := 5; begin Display_Values(X); Display_Values(Y); end Ive_Got_A_Procedure; |
Примечательно, что теперь, в файле тела процедуры Ive_Got_A_Procedure, процедуру Display_Values, которая оформлена как самостоятельный библиотечный модуль, необходимо указать в спецификаторе совместности контекста with.
Также, необходимо заметить, что подпрограммы, оформленные как самостоятельные библиотечные модули, не указываются в спецификаторе использования контекста use.
Поля типа массив
В случаях когда какой-либо компонент записи необходимо описать как массив необходимо учесть, что такой компонент не может быть указан как анонимный массив. Это означает, что тип массива для такого компонента записи должен быть предварительно описан.
type Illegal is record
Simple_Field_1: Boolean; Simple_Field_2: Integer; Array_Field : array (1..10) of Float; -- использование -- анонимного массива -- ЗАПРЕЩЕНО!!! end record; type Some_Array is array (1..10) of Float; -- предварительно описанный -- тип массива type Legal is record Simple_Field_1: Boolean; Simple_Field_2: Integer; Array_Field : Some_Array; -- компонент предварительно -- описанного типа массив end record; |
Также следует учесть, что в качестве компонентов записей не допускается использование неограниченных массивов. Рассмотрим следующий пример:
type Some_Array is array (Integer range <>) of Float; -- неограниченный -- массив type Some_Record is record
Field_1: Boolean; Field_2: Integer; Field_3: Some_Array (1..10); -- описание компонента записи -- задает ограничение индекса end record; |
Здесь, тип Some_Array - это неограниченный массив. Поэтому, при описании поля Field_3 записи Some_Record
указывается ограничение значений индекса для массива - (1..10). После этого, компонент Field_3 становится ограниченным массивом.
Для доступа к индивидуальному компоненту поля Field_3
какой-либо переменной типа Some_Record можно использовать:
. . . Some_Var : Some_Record; . . . Some_Var.Field_3(1) := 1; |
Для инициализации всех значений какой-либо переменной типа Some_Record
можно использовать агрегаты. В качестве демонстрации, приведем несколько примеров.
Some_Var_1 : Some_Record := (False, 0, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Some_Var_2 : Some_Record := ( Field_1 => False, Field_2 => 0, Field_3 => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) );
Some_Var_3 : Some_Record := ( Field_1 => True, Field_2 => 10, Field_3 => (others => 0) ); |
Из приведенных примеров видно, что для инициализации простых полей Field_1 и Field_2
записей Some_Var_1, Some_Var_2, Some_Var_3
типа Some_Record используются обычные литералы соответствующего типа, а для инициализации поля Field_3, которое является массивом, используется агрегат массива. Таким образом, для инициализации подобных структур необходимо использовать вложенные агрегаты.
Поля записей типа String
Частным случаем использования массивов в качестве компонентов записей являются строки String. Тип String, по сути, является предопределенным неограниченным массивом символов, поэтому для строк, при описании полей записи, допускается как предварительное описание какого-либо строкового типа или подтипа, так и непосредственное использование типа String. Например:
type Name_String is new String(1..10); subtype Address_String is String(1..20);
type Person is record First_Name: Name_String; Last_Name : Name_String; Address : Address_String; Phone : String(1..15); end record; |
В приведенном выше примере описания типа записи Person, для описания полей First_Name и Last_Name
используется самостоятельный строковый тип Name_String, производный от типа String. Для описания поля Address, используется подтип Address_String. Следует заметить, что тип Name_String и подтип Address_String, оба описывают ограниченные строки (или массивы символов). При описании поля Phone непосредственно использован тип String. В этом случае, для типа String, указывается ограничение для значений индекса - (1..15).
Для строковых полей, вместо агрегатов массива допускается использование строковых литералов. Например:
Chief : Person := ( First_Name => "Matroskin ", Last_Name => "Kot ", Address => "Prostokvashino ", Phone => "8-9-222-333 " ); |
Потоки ввода/вывода
Стандарт Ada95 обогатил средства ввода/вывода Ады возможностью использования гетерогенных (состоящих из различных по составу, свойствам, происхождению частей) потоков ввода/вывода.
Основная идея этого подхода заключается в том, что существует поток данных который ассоциируется с каким-либо файлом.
За счет использования потоковых механизмов, обработка такого файла может быть выполнена последовательно, подобноAda.Sequential_IO, или позиционно, подобно Ada.Direct_IO.
Причем, в отличие традиционных средств файлового ввода вывода которые обеспечиваются пакетами Ada.Sequential_IO и Ada.Direct_IO, один и тот же поток позволяет выполнять чтение/запись для данных различного типа.
Для обеспечения поддержки механизмов потокового ввода/вывода Ада предоставляет стандартный пакет Ada.Streams.Stream_IO.
Прежде чем приступить к детальному рассмотрению потоковых механизмов, необходимо заметить, что они достаточно тесно связаны с тэговыми типами, которые до настоящего момента не были рассмотрены.
Поэтому, при первом ознакомлении с Адой, рассмотрение поддержки ввода/вывода на потоках можно пропустить, и вернуться к нему после ознакомления с тэговыми типами.
Пакет Ada.Streams.Stream_IO предоставляет средства которые позволяют создавать, открывать и закрывать файлы обычным образом.
Далее, функция Stream, которая в качестве параметра принимает значение типа File_Type (потоковый файл), позволяет получить доступ к потоку ассоциируемому с этим файлом.
Схематически, начало спецификации этого пакета имеет следующий вид:
package Ada.Streams.Stream_IO is
type Stream_Access is access all Root_Stream_Type'Class; type File_Type is limited private; -- Create, Open, ... function Stream(File: in File_Type) return Stream_Access; . . . end Ada.Streams.Stream_IO; |
Заметим, что все объекты потокового типа являются производными от абстрактного типа Ada.Streams.Root_Stream_Type
и обычно получают доступ к потоку через параметр который ссылается на объект типа Ada.Streams.Root_Stream_Type'Class.
Последовательная обработка потоков выполняется с помощью атрибутов 'Read, 'Write, 'Input и 'Output.
Эти атрибуты предопределены для каждого нелимитированного типа.
Следует заметить, что Ада, с помощью инструкции описания атрибута, предоставляет программисту возможность переопределения этих атрибутов.
Таким образом, при необходимости, мы можем переопределять атрибуты которые установлены по умолчанию и описывать атрибуты для лимитированных типов.
Атрибуты T'Read и T'Write
принимают параметры которые указывают используемый поток и элемент типа T следующим образом:
procedure T'Write(Stream : access Streams.Root_Stream_Type'Class; Item : in T); procedure T'Read(Stream : access Streams.Root_Stream_Type'Class; Item : out T); |
type Date is record Day : Integer; Month : Month_Name; Year : Integer; end record; |
Затем, мы можем вызвать процедуру атрибута для значения которое необходимо записать в поток:
use Streams.Stream_IO; Mixed_File : File_Type; S : Stream_Access; . . . Create(Mixed_File); S := Stream(Mixed_File); . . . Date'Write(S, Some_Date); Integer'Write(S, Some_Integer); Month_Name'Write(S, This_Month); . . . |
Все подобные гетерогенные файлы имеют один и тот же тип.
Записанный таким образом файл, может быть прочитан аналогичным образом.
Однако, необходимо заметить, что если мы попытаемся прочитать записанные данные используя не подходящую для этих данных подпрограмму, то мы получим ошибку Data_Error.
В случае простой записи, такой как Date, предопределенный атрибут Date'Write
будет последовательно вызывать атрибуты 'Write для каждого компонента Date.
Это выглядит следующим образом:
procedure Date'Write(Stream : access Streams.Root_Stream_Type'Class; Item : in Date) is begin Integer'Write(Stream, Item.Day); Month_Name'Write(Stream, Item.Month); Integer'Write(Stream, Item.Year); end; |
Предположим, что нам необходимо осуществлять вывод имени месяца в виде соответствующего целого значения:
procedure Date_Write(Stream : access Streams.Root_Stream_Type'Class; Item : in Date) is begin Integer'Write(Stream, Item.Day); Integer'Write(Stream, Month_Name'Pos(Item.Month) + 1); Integer'Write(Stream, Item.Year); end Date_Write; for Date'Write use Date_Write; |
Date'Write(S, Some_Date); |
Аналогичные возможности предусматриваются для осуществления ввода.
Это значит, что если нам необходимо прочитать значение типа Date, то теперь нам нужно описать дополнительную версию Date'Read
для выполнения чтения целых значений как значений месяца с последующей конверсией этих значений в значения типа Month_Name.
Примечательно, что мы изменили формат вывода Month_Name только для случая Date.
Если нам нужно изменить формат вывода Month_Name для всех случаев, то разумнее переопределить Month_Name'Write
чем Date'Write.
Тогда, это произведет к косвенному изменению формата вывода для типа Date.
Следует обратить внимание на то, что предопределенные атрибуты T'Read и T'Write, могут быть переопределены инструкцией определения атрибута только в том же самом пакете (в спецификации или декларативной части) где описан тип T (как любое описание представления).
В результате, как следствие, эти предопределенные атрибуты не могут быть изменены для стандартно предопределенных типов.
Однако они могут быть изменены в их производных типах.
Ситуация несколько усложняется для массивов и записей с дискриминантами, поскольку необходимо принимать во внимание дополнительную информацию предоставляемую значениями границ массива и значениями дискриминантов.
( В случае дискриминанта значение которого равно значению по умолчанию, дискриминант рассматривается как обычный компонент записи).
Это выполняется с помощью использования дополнительных атрибутов 'Input и 'Output.
Основная идея заключается в том, что атрибуты 'Input и 'Output
обрабатывают дополнительную информацию (если она есть) и затем вызывают 'Read и 'Write
для обработки остальных значений.
Их описание имеет следующий вид:
procedure T'Output(Stream : access Streams.Root_Stream_Type'Class; Item : in T); function T'Input(Stream: access Streams.Root_Stream_Type'Class) return T; |
Таким образом, в случае массива процедура 'Output
выводит значения границ и, после этого, вызывает 'Write
непосредственно для самого значения.
В случае типа записи с дискриминантами, если запись имеет дискриминанты значения которых равны значениям по умолчанию, то 'Output
просто вызывает 'Write, которая трактует дискриминант как простой компонент записи.
Если значение дискриминанта не соответствует тому значению, которое указано как значение по умолчанию, то сначала 'Output
выводит дискриминанты записи, а затем вызывает 'Write
для обработки остальных компонентов записи.
В качестве примера, рассмотрим случай определенного подтипа, чей тип - это первый подтип, который не определен:
subtype String_6 is String(1 .. 6); S: String_6 := "String"; . . . String_6'Output(S); -- выводит значения границ и "String" String_6'Write(S); -- не выводит значения границ |
принадлежат типам и, таким образом, не имеет значения или мы записываем String_6'Write, или String'Write.
Приведенное выше описание работы T'Input и T'Output
относится к атрибутам которые заданы по умолчанию.
Они могут быть переопределены для выполнения чего-либо другого, причем не обязятельно для вызова T'Read и T'Write.
Дополнительно отметим, что Input и Output
существуют также для определенного подтипа, и их значения просто вызывают Read и Write.
Для взаимодействия с надклассовыми типами предназначены атрибуты T'Class'Output и T'Class'Input.
Для вывода значения надклассового типа, сначала производится вывод внешнего представления тэга, после чего с помощю механизма диспетчеризации (перенаправления) вызывается процедура 'Output которая соответствующим образом выводит специфические значения (вызывая 'Write).
Подобным образом, для ввода значения такого типа, сначала производится чтение тэга, а затем, в соответствии со значением тэга, с помощю механизма диспетчеризации (перенаправления) вызывается функция Input.
Для полноты, также описаны атрибуты T'Class'Write и T'Class'Read
которые выполняют диспетчеризацию (перенаправление) вызовов к подпрограммам определяемых атрибутами 'Write и 'Read
специфического типа, который идентифицируется тэгом.
Из рассмотренных нами примеров следует основной принцип работы с потоками: то что сначала было записано, то и должно быть прочитано, при выполнении подходящей обратной операции.
Теперь можно продолжить рассмотрение структуры которая лежит в основе всех этих механизмов.
Все потоки являются производными от абстрактного типа Streams.Root_Stream_Type
который имеет две абстрактных операции Read и Write
описанные следующим образом:
procedure Read(Stream : in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Offset) is abstract; procedure Write(Stream : in out Root_Stream_Type; Item : in Stream_Element_Array) is abstract; |
Следует обратить внимание на разницу между потоковыми элементами (stream elements) и элементами памяти (storage elements) (элементы памяти будут рассмотрены при рассмотрении пулов памяти).
Элементы памяти (storage elements) затрагивают внутреннюю память (storage) в то время как потоковые элементы (stream elements) затрагивают внешнюю информацию и, таким образом, подходят для распределенных систем.
Предопределенные атрибуты 'Read и 'Write
используют операции Read и Write
ассоциированного с ними потока, и пользователь может описывать атрибуты одинаковым образом.
Примечательно, что параметр Stream для корневого типа имеет тип Root_Stream_Type, тогда как атрибут - это ссылочный тип обозначающий соответствующий класс.
Таким образом, такой атрибут, определяемый пользователем, должен будет выполнять подходящее разыменование:
procedure My_Write(Stream : access Streams.Root_Stream_Type'Class; Item : T) is begin . . . -- преобразование значений в потоковые элементы Streams.Write(Stream.all, ...); -- диспетчеризации (перенаправления) вызова end My_Write; |
может быть использован для осуществления индексированного доступа.
Это возможно, поскольку файл структурирован как последовательность потоковых элементов.
Таким образом, индексированный доступ работает в терминах потоковых элементов подобно тому как работает Direct_IO в терминах элементов типа.
Это значит, что индекс может быть прочитан и переустановлен.
Процедуры Read и Write выполняют обработку относительно текущего значения индекса, также существует альтернативная процедура Read
которая стартует согласно указанного значения индекса.
Процедуры Read и Write (которые используют файловый параметр) точно соответствуют диспетчеризуемым (перенаправляемым) операциям ассоциированного потока.
Позиционное сопоставление
Позиционное сопоставление формальных и фактических параметров при вызове подпрограммы достаточно традиционно, и используется во многих языках программирования.
При таком сопоставлении, ассоциирование между формальными и фактическими параметрами производится один к одному позиционно, т.е. первый формальный параметр ассоциируется с первым фактическим параметром и т.д.
procedure Demo(X : Integer; Y : Integer); -- спецификация процедуры
. . . Demo(1, 2); |
В этом случае, фактический параметр 1 будет подставлен вместо первого формального параметра X, а фактический параметр 2 будет подставлен вместо второго формального параметра Y.
Правила области видимости для обобщенных ссылочных типов
В целях обеспечения надежности, на использование обобщенных ссылочных типов накладываются некоторые дополнительные ограничения.
Так, область видимости косвенно доступного объекта, на который будет ссылаться значение переменной обобщенного ссылочного типа T, не должна быть глубже чем область видимости переменной обобщенного ссылочного типа T.
Это подразумевает, что следующий пример - не корректен, и, следовательно, будет отвергнут компилятором:
procedure Illegal is -- внешняя область видимости описаний
type Integer_Access is access all Integer; Integer_Ptr : Integer_Access; begin . . . declare -- внутренняя область видимости описаний Integer_Variable : aliased Integer; begin Integer_Ptr := Integer_Variable'Access; -- это не корректно!!! end; -- завершение области видимости -- переменной Integer_Variable Integer_Ptr.all := Integer_Ptr.all + 1; -- сюрприз! -- переменная Integer_Variable -- больше не существует! end Illegal; -- завершение области видимости -- для Integer_Access |
Смысл примера заключается в следующем.
Во внутреннем блоке, переменной IA ссылочного типа Integer_Access
присваивается значение которое ссылается на переменную IVar.
При завершении внутреннего блока, переменная IVar прекращает свое существование.
Следовательно, в следующей инструкции присваивания, переменная IA
ссылается на не существующую переменную.
Такая ситуация известна как "проблема висячих указателей".
Такое ограничение выглядит достаточно строго, но оно гарантирует, что любой объект на который могут ссылаться значения типа Integer_Access
будет существовать на протяжении всего времени существования переменных типа Integer_Access.
В частности, если тип Integer_Access описан на уровне библиотеки, то область видимости Integer_Access определяется всей программой, а это значит, что с типом Integer_Access могут быть использованы только те переменные, которые описаны на уровне библиотеки.
Бывают случаи когда необходимо нарушить строгость данного ограничения.
Тогда, для получения ссылочного значения, вместо атрибута 'Access
можно использовать атрибут 'Unchecked_Access, который позволяет получить ссылочное значение без осуществления проверки правил доступа:
procedure Legal_But_Stupid is type Integer_Access is access all Integer; IA : Integer_Access; begin . . . declare IVar : aliased Integer; begin IA := IVar'Unchecked_Access; -- это не надежно!!! end; IA.all := IA.all + 1; -- теперь это будет только ВАША ошибка!!! end Legal_But_Stupid; |
Это подразумевает, что при его использовании вы должны быть полностью уверены в своих действиях.
Еще одним способом, который позволяет обходить эти ограничения, является описание обобщенных ссылочных типов внутри настраиваемых модулей.
Идея такого подхода основана на том, что область видимости для типа который описан внутри настраиваемого модуля будет ограничена областью видимости места конкретизации этого настраиваемого модуля.
Правила области видимости ссылочных типов для подпрограмм
Ссылочные типы для подпрограмм используют те же самые правила области видимости, что и обобщенные ссылочные типы.
Таким образом, ссылочные типы для подпрограмм, которые описаны на уровне библиотеки, могут быть использованы только для ссылки на библиотечные подпрограммы.
Дополнительным ограничением является то, что со ссылочными типами для подпрограмм нельзя использовать атрибут 'Unchecked_Access.
Единственный способ, который позволяет обходить такие строгие ограничения, является описание ссылочных типов для подпрограмм внутри настраиваемых модулей.
Предопределенные исключения
Существует пять исключений которые стандартно предопределены в языке программирования Ада:
Constraint_Error | - | Ошибка ограничения |
Numeric_Error | - | Ошибка числа |
Program_Error | - | Ошибка программы |
Storage_Error | - | Ошибка памяти |
Tasking_Error | - | Ошибка задачи |
Предопределенные знаки операций для целочисленных типов
Следующие знаки операций предопределены для каждого целочисленного типа:
+, - унарные плюс и минус +, -, *, / сложить, вычесть, умножить и разделить ** возведение в степень (только целые значения степени) mod модуль rem остаток abs абсолютное значение |
Предопределенный логический тип Boolean
В Аде, предопределенный логический тип Boolean описывается как перечислимый тип в пакете Standard:
type Boolean is (False, True); |
Таким образом, переменные логического типа Boolean могут принимать только одно из двух значений: True (истина) или False (ложь).
Примечательно, что предопределеннный логический тип Boolean имеет специальное предназначение. Значения этого типа используются в условных инструкциях языка Ада ("if ... ", "exit when ... ", ...). Это подразумевает, что если вы пытаетесь описать свой собственный логический тип Boolean, и описываете его точно также как и предопределенный тип Boolean (полное имя предопределенного логического типа - Standard.Boolean), то вы получаете абсолютно самостоятельный тип(!). В результате, вы не можете использовать значения, описанного вами типа Boolean, в условных инструкциях языка Ада, которые ожидают только тип Standard.Boolean.
Значения предопределенного логического типа Standard.Boolean
возвращают знаки операций сравнения:
= -- равно /= -- не равно < -- меньше <= -- меньше или равно > -- больше >= -- больше или равно |
Для обработки значений типа Boolean могут быть использованы следующие знаки операций:
and -- логическое И: вычисляются и левая, и правая часть выражения and then -- логическое И: правая часть выражения вычисляется, если -- результат вычисления левой части - True
or -- логическое ИЛИ: вычисляются и левая, и правая часть выражения or else -- логическое ИЛИ: правая часть выражения вычисляется, если -- результат вычисления левой части - False xor -- исключающее ИЛИ not -- отрицание (инверсия); унарная операция |
Обычно, при вычислении значений логических выражений, компилятор сам определяет последовательность вычисления каждого логического значения. При этом, производится вычисление всех логических переменных, указанных в логическом выражении.
Тем кто знаком с языком Паскаль следует заметить, что такой подход отличается от правил принятых в современных диалектах Паскаля, где, для повышения производительности, определение значения результата всего логического выражения может быть выполнено сокращенно, в зависимости от предварительных результатов обработки выражения.
Например, при определении значения результата следующего логического выражения
(B /= 0) and (A/B > 0) |
Чтобы избежать подобных ошибок, а также в целях увеличения производительности, необходимо производить вычисление значений логических переменных в определенной последовательности и прекращать ее как только результат всего выражения уже определен. Для этого можно использовать "and then" вместо "and", и "or else" вместо "else", указывая порядок обработки логического выражения явно:
(B /= 0) and then (A/B > 0) |
Можно переписать предыдущий пример с использованием "or else". Тогда, обработка логического выражения будет завершена в случае если значение слева от "or else" вычислено как True:
(B = 0) or else (A/B |
(A < B) and (B > C) or (D < E) -- запрещено ((A < B) and (B > C)) or (D < E) -- разрешено |
Предопределенный тип Integer
Предопределенный целочисленный тип Integer описан в пакете Standard
(пакет Standard не нужно указывать в инструкциях спецификации контекста with и use). Точный диапазон целочисленных значений, предоставляемых этим типом, зависит от конкретной реализации компилятора и/или оборудования. Однако, стандарт определяет минимально допустимый диапазон значений для этого типа от -(2 ** 15) до +(2 ** 15 - 1) (например, в случае 32-битных систем, таких как Windows или Linux, для реализации компилятора GNAT диапазон значений типа Integer будет от -(2 ** 31) до +(2 ** 31 - 1)).
Кроме типа Integer, в пакете Standard предопределены два его подтипа (понятие подтипа будет рассмотрено позже) Natural и Positive, которые, соответственно, описывают множества не отрицательных (натуральных) и положительных целых чисел.
Преимущества и недостатки настраиваемых модулей
В заключение обсуждения настраиваемых модулей Ады необходимо отметить преимущества и недостатки использования данной концепции.
Основным преимуществом использования настраиваемых модулей является то, что они оказывают значительное содействие в многократном повторном использовании ранее разработанных и отлаженных алгоритмов.
Действительно, настраиваемые модули позволяют разработчику однажды написать и отладить алгоритмы подпрограмм для обработки объектов тип которых в последствии будет указываться пользователями этих подпрограмм.
Однако, применение настраиваемых модулей не лишено недостатков.
Разработка алгоритмов для настраиваемых модулей требует более тщательного внимания, что в результате является дополнительной нагрузкой для их автора.
Также необходимо заметить, что реализация настраиваемой процедуры, функции или пакета может оказаться не столь эффективной как непосредственная реализация.
Компилятор может генерировать один и тот же код для всех экземпляров настроенных процедур, функций или пакетов, не зависимо от фактически обрабатываемых данных.
Copyright (C) А.Гавва | V-0.4w май 2004 |