Copyright (C) А.Гавва V-0.4w май 2004

11. Организация ввода/вывода

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

Средства ввода/вывода Ады, определяемые стандартом Ada83, обеспечивают возможность работы с текстовыми и двоичными данными. Стандарт Ada95 расширил эти средства возможностью использования гетерогенных потоков.

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

11.1 Текстовый ввод/вывод

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

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

Следует заметить, что стандартные пакеты поддержки текстового ввода/вывода для символов типа Character содержат в своем названии строку Text_IO, а стандартные пакеты поддержки текстового ввода/вывода для символов типа Wide_Character содержат в своем названии строку Wide_Text_IO.

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

11.1.1 Пакет Ada.Text_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  -  Возвращает позицию чтения (или записи) в начало файла.

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

End_of_File  -  Возвращает истину если мы находимся в конце текущего файла.
End_of_Line  -  Возвращает истину если мы находимся в конце текущей строки текста.
Is_Open  -  Возвращает истину если текущий файл открыт.
Mode  -  Возвращает режим использования текущего файла.
Name  -  Возвращает строку имени текущего файла.

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

11.1.2 Искючения ввода/вывода

Все средства организации ввода/вывода Ады полагаются на механизм исключений для сообщения об ошибках, которые возникли в процессе выполнения какой-либо операции ввода/вывода данных. Описания исключений ввода/вывода находятся в пакете Ada.IO_Exceptions который имеет следующий вид:


package Ada.IO_Exceptions is
   pragma Pure(IO_Exceptions);

   Status_Error : exception; -- попытка обращения к файлу который не был открыт
                             --   или повторное открытие файла
   Mode_Error   : exception; -- попытка чтения из файла который открыт для записи
   Name_Error   : exception; -- попытка открытия не существующего файла
                             --   или попытка попытка создания файла
                             --   с не допустимым именем
   Use_Error    : exception; -- зависит от характеристик внешней среды;
                             --   например, при обращении к файлу
                             --   при отсутствии соответствующих
                             --   привелегий доступа
   Device_Error : exception; -- запрошенная операция ввода/вывода не может
                             --   быть выполнена по причине неисправности
                             --   оборудования
   End_Error    : exception; -- попытка чтения за концом файла
   Data_Error   : exception; -- прочитанный элемент не соответствует
                             --   ожидаемому типу данных
   Layout_Error : exception; -- при текстовом вводе/выводе;
                             --   как правило, при выполении операции
                             --   ввода/вывода которая нарушает
                             --   установленные диапазоны значений
                             --   форматирования текста
end Ada.IO_Exceptions;

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

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


with Ada.Text_IO;        use Ada.Text_IO;

procedure Robust_Open(The_File : in out File_type;
                      Mode     : in     File_mode;
                      Name     : in     String) is
begin
    Open(The_File, Mode, Name);
        
exception
    when Name_Error =>
        Create(The_File, Mode, Name);
end Robust_Open;

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

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


with Ada.Text_IO;        use Ada.Text_IO;

function File_Exists(Name : String) return Boolean is

    The_File : Ada.Text_IO.File_Type;

begin
    Open(The_File, In_File, Name);

    -- файл открылся, закрываем его и возвращаем "истину"
    Close(The_File);
    return True;
        
exception
    when Name_Error =>
        -- файл не окрылся, значит его нет
        return False;
end File_Exists;

Эта функция пытается открыть файл и если при этом не возникает исключения Name_Error, то это значит что файл существует, и функция возвращает значение "истина" (если файл уже открыт, то генерируется исключение Use_Error).

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

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

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

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


Standard_Input
Standard_Output
Standard_Error

Current_Input
Current_Output
Current_Error

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

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


Set_Input
Set_Output
Set_Error

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

11.1.4 Настраиваемые пакеты текстового ввода/вывода

Для поддержки текстового ввода/вывода численных данных и данных перечислимых типов Ада предусматривает набор настраиваемых пакетов текстового ввода/вывода:


Ada.Text_IO.Integer_IO      -- для целочисленных типов
Ada.Text_IO.Modular_IO      -- для модульных типов
Ada.Text_IO.Float_IO        -- для вещественных типов с плавающей точкой
Ada.Text_IO.Fixed_IO        -- для вещественных типов с фиксированной точкой
Ada.Text_IO.Decimal_IO      -- для децимальных типов
Ada.Text_IO.Enumeration_IO  -- для перечислимых типов

Примечательно, что все эти настраиваемые пакеты являются дочерними модулями пакета Ada.Text_IO.

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

Рассмотрим пример использования настраиваемого пакета Ada.Text_IO.Integer_IO, который предназначен для организации ввода/вывода целочисленных типов. Предварительно, необходимо выполнить конкретизацию настраиваемого пакета Ada.Text_IO.Integer_IO для соответствующего целочисленного типа (в нашем случае выбран тип Integer, для простоты), и получить экземпляр настроенного модуля:


with Ada.Text_IO;        use Ada.Text_IO;

package Int_IO is new Integer_IO(Integer);

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


with Ada.Text_IO;  use Ada.Text_IO;
with Int_IO;       use Int_IO;      -- указываем экземпляр настроенного модуля

procedure Demo_Int_IO is

    Data_File : Text_IO.File_type;

begin
    Create(File => Data_File,
           Mode => Out_File,
           Name => "data.dat");

    for I in 1..10 loop             -- цикл вывода в файл
        Put(Data_File, I);          -- чисел и их квадратов
        Put(Data_File, "    ");
        Put(Data_File, I * I);
        New_Line(Data_File);
    end loop;

    Close(Data_File);
end Demo_Int_IO;

Здесь, процедура Create создает файл данных "data.dat", после чего, в этот файл производится запись некоторых данных процедурами Put и New_Line. Процедура Close закрывает файл.

Остальные вышеперечисленные настраиваемые пакеты могут быть использованы аналогичным образом.

Следует добавить, что в дополнение к настраиваемым пакетам, Ада также предоставляет набор настроенных пакетов:


Ada.Short_Short_Integer_Text_IO -- для значений типа Short_Short_Integer
Ada.Short_Integer_Text_IO       -- для значений типа Short_Integer
Ada.Integer_Text_IO             -- для значений типа Integer
Ada.Long_Integer_Text_IO        -- для значений типа Long_Integer
Ada.Long_Long_Integer_Text_IO   -- для значений типа Long_Long_Integer

Ada.Short_Float_Text_IO         -- для значений типа Short_Float
Ada.Float_Text_IO               -- для значений типа Float
Ada.Long_Float_Text_IO          -- для значений типа Long_Float
Ada.Long_Long_Float_Text_IO     -- для значений типа Long_Long_Float

Первые пять перечисленных пакетов, являются результатами конкретизации настраиваемого пакета Ada.Text_IO.Integer_IO для использования со значениями соответствующих целочисленных типов, последние четыре - результатами соответствующих конкретизаций настраиваемого пакета Ada.Text_IO.Float_IO.

11.2 Ввод/вывод двоичных данных

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

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

11.2.1 Пакет Ada.Sequential_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 - последовательный).

11.2.2 Пакет Ada.Direct_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.

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

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

11.4 Взаимодействие с командной строкой и окружением

11.4.1 Параметры командной строки

Во время выполнения программы существует возможность получения доступа к аргументам, которые указаны в командной строке запуска программы на выполнение. Такая возможность обеспечивается средствами стандартного пакета 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, может быть использована для возврата во внешню среду кода статуса завершения работы программы (иначе - кода ошибки).

11.4.2 Переменные окружения программы

Для поддержки организации взаимодействия с переменными окружения операционной системы, реализация компилятора 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