Потоки ввода/вывода
Стандарт 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 (которые используют файловый параметр) точно соответствуют диспетчеризуемым (перенаправляемым) операциям ассоциированного потока.