Ада-95. Компилятор GNAT

       

Потоки ввода/вывода


Стандарт 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);

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

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); . . .

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

Все подобные гетерогенные файлы имеют один и тот же тип.

Записанный таким образом файл, может быть прочитан аналогичным образом.

Однако, необходимо заметить, что если мы попытаемся прочитать записанные данные используя не подходящую для этих данных подпрограмму, то мы получим ошибку 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;

Мы можем написать свою собственную версию для Date'Write.

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

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, то теперь нам нужно описать дополнительную версию 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;

Примечательно, что 'Input - это функция, поскольку 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); -- не выводит значения границ

Примечательно, что атрибуты 'Output и 'Write

принадлежат типам и, таким образом, не имеет значения или мы записываем 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;

В заключение рассмотрения потоков Ады заметим что Ada.Stream_IO

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

Это возможно, поскольку файл структурирован как последовательность потоковых элементов.

Таким образом, индексированный доступ работает в терминах потоковых элементов подобно тому как работает Direct_IO в терминах элементов типа.

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

Процедуры Read и Write выполняют обработку относительно текущего значения индекса, также существует альтернативная процедура Read

которая стартует согласно указанного значения индекса.

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


Содержание раздела