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

4. Массивы (array)

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

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

4.1 Простые массивы

4.1.1 Описание простого массива

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


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 <тип_элементов_массива>;

Необходимо заметить:

4.1.2 Анонимные массивы

Массив может быть объявлен непосредственно, без использования предопределенного типа:


No_Of_Desks : array(1..No_Of_Divisions) of Integer;

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

4.1.3 Организация доступа к отдельным элементам массива

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


if Store_1_Stock(Dog) > 10 then ...

В приведенном примере производится чтение значения элемента массива Store_1_Stock (доступ по чтению).

Для сохранения значения в элементе массива Store_2_Stock (доступ по записи) можно использовать:


Store_2_Stock(Rabbit) := 200;

Необходимо отметить, что в обоих случаях доступ к элементу массива в Аде внешне никак не отличается от вызова функции.

4.1.4 Агрегаты для массивов

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

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


Store_1_Stock := (5, 4, 300);

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

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


Store_1_Stock := (Dog => 5, Budgie => 4, Rabbit => 300);

Такой вид агрегата называют именованым агрегатом.

Приведем еще один пример именованого агрегата:


Store_1_Stock := (Dog | Budgie => 0, Rabbit => 100);

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


Store_1_Stock := (5, 4, Rabbit => 300);    -- это недопустимо!

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


Store_1_Stock := (Dog..Rabbit => 0);

Агрегаты обоих видов удобно использовать в описаниях:


Store_1_Stock:  Pet_Stock := (5, 4, 300);

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


New_Shop_Stock  : Pet_Stock := (others := 0);

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


declare
    type Numbers1 is array(1..10) of Integer;
    type Numbers2 is array(1..20) of Integer;
    A : Numbers1;
    B : Numbers2;
begin
    A := (1, 2, 3, 4, others => 5);
end;

Заметьте, что в данном случае опция others используется вместе с позиционной нотацией. Поэтому Ада потребует указать квалификацию типа:


A : = Numbers1'(1, 2, 3, 4, others => 5);

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

4.1.5 Отрезки (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);

Напомним что использование отрезков допускается только для одномерных массивов.

4.1.6 Массивы-константы

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


type Months is (Jan, Feb, Mar, .... , Dec);
subtype Month_Days is Integer range 1..31;
type Month_Length is array (Jan..Dec) of Month_Days;

Days_In_Month : constant Month_Length := (31, 28, 31, 30, ... , 31);

4.1.7 Атрибуты массивов

С массивами ассоциируются следующие атрибуты:


<имя_массива>'First     -- нижняя граница массива
<имя_массива>'Last      -- верхняя граница массива

<имя_массива>'Length    -- количество элементов в массиве
                        -- <имя_массива>'Last - <имя_массива>'First + 1

<имя_массива>'Range     -- подтип объявленный как
                        -- <имя_массива>'First..<имя_массива>'Last

Эти средства очень полезны для организации перебора элементов массивов.


for Count in <имя_массива>'Range loop

    . . .

end loop

В приведенном выше примере, каждый элемент массива будет гарантированно обработан.

4.2 Многомерные массивы

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


Square_Size : constant := 5;
subtype Square_Index  is Integer range 1..Square_Size;
type  Square  is array (Square_Index, Square_Index) of Integer;

Square_Var  : Square := ( others => (others => 0) );

Здесь, агрегат, который инициализирует переменную Square_Var типа Square в нуль, построен как агрегат массива массивов, поэтому требуется двойное использование скобок (опции others использованы для упрощения примера).

Более сложный пример инициализации этой переменной, использующий агрегат с позиционной нотацией, может иметь следующий вид:


----------------- столбцы   1   2   3   4   5

Square_Var  : Square := ( ( 1,  2,  3,  4,  5),     -- строка 1
                          ( 6,  7,  8,  9, 10),     -- строка 2
                          (11, 12, 13, 14, 15),     -- строка 3
                          (16, 17, 18, 19, 20),     -- строка 4
                          (21, 22, 23, 24, 25) );   -- строка 5

Доступ к элементам такого массива можно организовать следующим образом:


Square_Var(1, 5) := 5;
Square_Var(5, 5) := 25;

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


Square_Size : constant := 5;
subtype Square_Index  is Integer range 1..Square_Size;

type  Square_Row is array (Square_Index) of Integer;
type  Square     is array (Square_Index) of Square_Row;

Square_Var  : Square := ( others => (others => 0) );

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

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


Square_Var (1)(5) := 5;
Square_Var (5)(5) := 25;

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


<имя_массива>'First(N)
<имя_массива>'Last(N)
<имя_массива>'Length(N)
<имя_массива>'Range(N)

В данном случае, значение определяемое, например, как <имя_массива>'Range(N) будет возвращать диапазон N-мерного индекса.

4.3 Типы неограниченных массивов (unconstrained array),
      предопределенный тип String

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

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

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

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

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

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


type  Numbers_Array   is array (Positive range <>) of Integer;

Символы "<>" указывают, что диапазон значений индекса должен быть указан при описании объектов типа Numbers_Array. Переменная такого типа может быть описана следующим образом:


Numbers : Numbers_Array (1..5) := (1, 2, 3, 4, 5);

Здесь, при описании переменной Numbers предусматривается ограничение (constraint) размеров массива - указывается диапазон значений индекса - (1..5).

Пакет Standard предоставляет предопределенный тип String, который описывается как неограниченный массив символов:


type String is array (Positive range <>) of Character;

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

Также как и в предыдущем примере описания переменной Numbers, для создания фактического массива типа String, мы должны предусмотреть ограничение диапазона возможных значений индекса:


My_Name : String (1..20);

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

Необходимо заметить, что для инициализации объектов типа String, можно использовать агрегаты, поскольку тип String, по сути, является массивом символов. Однако, более цивилизованным способом будет использование строковых литералов. Так, вышеописанную переменную My_Name, можно инициализировать следующим образом:


My_Name := "Alex                ";

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

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


Some_Name   : String := "Vasya Pupkin";
Some_Saying : constant String := "Beer without vodka is money to wind!";

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


My_Name    : String (1..20);
My_Surname : String (21..50);

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

4.4 Стандартные операции для массивов

Существует несколько операций, которые могут выполняться не только над отдельными элементами массива, но и над целым массивом.

4.4.1 Присваивание

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


declare
    My_Name   : String(1..10)  := "Dale      ";
    Your_Name : String(1..10)  := "Russell   ";
    Her_Name  : String(21..30) := "Liz       ";
    His_Name  : String(1..5)   := "Tim  ";
begin
    Your_Name := My_Name;     -- это корректно, поскольку в обоих случаях
    Your_Name := Her_Name;    -- оба массива имеют одинаковое количество
                              -- элементов
    His_Name  := Your_Name;   -- это приведет к возбуждению исключения:
                              -- хотя обе переменные одного и того же типа,
                              -- но они имеют различную длину (число элементов)
end;

4.4.2 Проверки на равенство и на неравенство

Проверки на равенство и на неравенство доступны почти для всех типов Ады. Два массива считаются равными если каждый элемент первого массива равен соответствующему элементу второго массива.


if Array1 = Array2 then....

4.4.3 Конкатенация

Символ & может быть использован как знак операции конкатенации двух массивов.


declare
    type Vector is array(Positive range <>) of Integer;

    A : Vector (1..10);
    B : Vector (1..5) := (1, 2, 3, 4, 5);
    C : Vector (1..5) := (6, 7, 8, 9, 10);
begin
    A := B & C;
    Put_Line("hello" & " " & "world");
end;

4.4.4 Сравнение массивов

Для сравнения одномерных массивов могут быть использованы следующие знаки операций "<", "<=", ">" и ">=". Они наиболее полезны при сравнении массивов символов (строк).


"hello" < "world"        -- возвращает результат "истина" (True)

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

4.4.5 Логические операции

Если знаки логических операций "and", "or", "xor", "not" допускается использовать для индивидуальных компонентов массива, то использование знаков логических операций для такого массива также будет допустимым (например, в случае массива логических значений типа Boolean).

4.5 Динамические массивы

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


declare
    X        : Integer := Y    -- значение Y описано где-то в другом месте
    A        : array (1..X) of Integer;
begin
    for I in A'Range loop

        . . .

    end loop;
end;


procedure Demo(Item  : String) is
    Copy    : String(Item'First..Item'Last) := Item;
    Double  : String(1..2 * Item'Length) := Item & Item;
begin
    . . .

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


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