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

5. Записи (record)

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

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

5.1 Простые записи

5.1.1 Описание простой записи

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


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;

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

5.1.2 Значения полей записи по-умолчанию

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


type Bicycle is
    record
        Frame       : Construction := CromeMolyebdenum;
        Maker       : Manufacturer;        
        Front_Brake : Brake_Type   := Cantilever;
        Rear_Brake  : Brake_Type   := Cantilever;
    end record;

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

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


Expensive_Bike  : Bicycle;

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

if Expensive_Bike.Frame = Aluminium then ...

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

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

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

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


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

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

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


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

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

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

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


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

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


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

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


type Summary  is
    record
      Field_1 : Boolean;
      Field_2 : Float;
      Field_3 : Integer;
      Field_4 : Integer;
    end record;

Variable_1  : Summary := (True, 10.0, 1, 1);  -- позиционная нотация
Variable_2  : Summary := (                    -- именованная нотация
                          Field_4 => 1
                          Field_3 => 1,
                          Field_2 => 10.0,
                          Field_1 => True
                         );
Variable_2  : Summary := (                    -- смешанная нотация
                          True, 10.0,
                          Field_4 => 1,
                          Field_3 => 1
                         );

-------------------------- использование символа '|'
Variable_4  : Summary := (
                          True, 10.0,
                          Field_3|Field_4 => 1
                         );
Variable_5  : Summary := (
                          Field_1 => True,
                          Field_2 => 10.0,
                          Field_3|Field_4 => 1
                         );

-------------------------- использование others
Variable_6  : Summary := (True, 10.0, others => 1);
Variable_7  : Summary := (
                          Field_1 => True,
                          Field_2 => 10.0,
                          others => 1
                         );

5.1.5 Записи-константы

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


My_Bicycle : constant Bicycle := ( Hi_Tensile_Steel,
                                   Unknown,
                                   Front_Brake => Side_Pull,
                                   Rear_Brake  => Side_Pull );

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

5.1.6 Лимитированные записи

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


type  Person  is  limited
    record
        Name    : String(1..Max_Chs); -- строка имени
        Height  : Height_Cm := 0;     -- рост в сантиметрах
        Sex     : Gender;             -- пол
    end record;

Mike    : Person;
Corrina : Person;

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


  . . .
Mike    := Corrina;       -- ОШИБКА КОМПИЛЯЦИИ!!!
                          -- для лимитированных записей присваивание запрещено
  . . .

if  Corrina = Mike  then  -- ОШИБКА КОМПИЛЯЦИИ!!!
                          -- для лимитированных записей сравнение запрещено
    Put_Line("This is strange");
end if;
  . . .

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

5.2 Вложенные структуры

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

5.2.1 Поля типа массив

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


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

5.2.2 Поля записей типа 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    "
                  );

5.2.3 Вложенные записи

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


type  Point is record
    X : Integer;
    Y : Integer;
end record

type Rect is record
    Left_Hight_Corner : Point;
    Right_Low_Corner  : Point;
end record

P : Point := (100, 100);
R : Rect;

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


R.Left_Hight_Corner.X := 0;
R.Left_Hight_Corner.Y := 0;

R.Right_Low_Corner := P;

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


R_1 : Rect := ( (0, 0), (100, 100) );
R_2 : Rect := (
                Left_Hight_Corner => (Y => 0, X => 0),
                Right_Low_Corner  => (100, 100)
              );

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

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

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

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

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

5.3.1 Вариантные записи

Использование дискриминантов позволяет конструировать вариантные записи (или, называемые иначе, записи с вариантами). В этом случае, значение дискриминанта определяет наличие или отсутствие некоторых полей записи.

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


type Vehicle is (Bicycle, Car, Truck, Scooter);

type Transport (Vehicle_Type : Vehicle) is
    record
        Owner       : String(1..10);      -- владелец
        Description : String(1..10);      -- описание
        case Vehicle_Type is
            when Car    =>
                           Petrol_Consumption : Float; -- расход бензина
            when Truck  =>
                           Diesel_Consumption : Float; -- расход солярки
                           Tare               : Real;  -- вес тары
                           Net                : Real;  -- вес нетто
            when others =>
                           null;
        end case;
  end record;

В представленном описании типа записи Transport, поле Vehicle_Type (vehicle - транспортное средство) является дискриминантом.

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


My_Car      : Transport (Car);
My_Bicycle  : Transport (Vehicle_Type => Bicycle);
His_Car     : Transport := (Car, "dale      ", "escort  ", 30.0);

Здесь, переменные My_Car и His_Car, у которых дискриминант имеет значение Car, содержат поля: Owner, Description и Petrol_Consumption. При этом, попытка обращения к таким полям этих переменных как Tare и/или Net будет не допустима. В результате, это приведет к генерации ошибки ограничения (constraint error), что может быть обнаружено и обработано при использовании механизма исключений (exceptions) язака Ада.

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

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

И в заключение укажем несколько общих особенностей описания записей с вариантами:

5.3.2 Ограниченные записи (constrained records)

Записи My_Car, My_Bicycle и His_Car, которые мы рассматривали выше, называют ограниченными. Для таких записей значение дискриминанта определяется при описании экземпляра (переменной) записи. Таким образом, однажды определенный дискриминант в последствии никогда не может быть изменен. Так, запись My_Bicycle не имеет полей Tare, Net, Petrol_Consumption, и т.д. При этом, компилятор Ады даже не будет распределять пространство для этих полей.

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

5.3.3 Неограниченные записи (unconstrained records)

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

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


type Accounts is (Cheque, Savings);
type Account (Account_Type: Accounts := Savings) is
    record
        Account_No  : Positive;
        Title       : String(1..10);
        case Account_Type is
            when Savings  => Interest : Rate;
            when Cheque   => null;
        end case;
    end record;

Здесь, дискриминант записи Account имеет значение по-умолчанию Savings. Теперь, мы можем описать запись:


Household_Account : Account;

Такая запись будет создана с определенным по-умолчанию значением дискриминанта. Но теперь, мы позже, при необходимости, можем изменить тип этой записи.


Household_Account:= (Cheque, 123_456, "household ");

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

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


type  Property  is array (Positive range <>) of Float;
type  Man (Number: Positive := 2; Size: Positive := 10) is
    record
        Name        : String (1..Size);
        Prop_Array  : Property (1..Number);
    end record;

The_Man : Man;

The_Man.Name       := "Ivanov I I";
The_Man.Prop_Array := (25.0, 50.0);

  . . .
  
The_Man := (
            Number     => 3,
            Size       => 8,
            Name       => "Pyle I C",
            Prop_Array => (25.0, 50.0, 160.5)
           );

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

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

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


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

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

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

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


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