Цикл For и одномерные массивы в языке Ада. Перечисления. Массивы с неуточненными границами

Кроме ранее рассмотренных операторов цикла Loop и While, в Аде существует ещё один оператор цикла - For. Его отличительной особенностью является то, что счётчик цикла For всегда изменяется на 1. Рассмотрим цикл For на примере. Пусть нам нужно 10 раз вывести на экран сообщение "Hello, world!":

...
For i in 1..10 loop --i - счётчик цикла. Он определяет условие выхода из цикла
	Put_Line("Hello, world!");
end loop;
...

В данном примере i изначально равен 1 и на на каждой итерации будет увеличиваться на 1, пока не станет больше 10, т.е. 11. Необходимо отметить, что счётчик цикла i в разделе объявления переменных объявлять не нужно.

Таким образом, в этом примере строка "Hello, world!" будет выведена 10 раз.

Так как итератор цикла For всегда изменяется на 1, то этот цикл удобно использовать для прохода по всем элементам массива (или, как его ещё называют, вектора).

Одномерный массив (Вектор) - это участок памяти (которому присвоено имя), который содержит несколько переменных одного типа, расположенных последовательно, одна за другой. То есть, массив можно рассматривать как несколько идущих подряд ящиков, которые хранят переменные одного типа, причём, у каждого ящика есть свой порядковый номер (индекс). Индексация массивов в Аде может начинаться с любого числа (в отличие многих других языков (например, Си, С++, Python), где индексация всегда начинается с 0): с 1, с 10, с -3 и т.д. Однако в общем случае в Аде принято, что первый элемент массива имеет индекс 1.

Для создания массива в Аде используется ключевое слово array:

array(1..100) of Integer; --Объявлен массив с индексами от 1 до 100, который может содержать
                          --максимум 100 чисел типа Integer

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

...
procedure main is
        --Создание типа VECTOR, который определяет массивы с 5 элементами
        type VECTOR is array(1..5) of Integer;
        V, W : VECTOR; --Объявление двух массивов типа VECTOR
begin
...
end main;

Далее можно работать как с отдельными элементами массива, так и с целым массивом. Например:

...
procedure Main is
	type Vector is array(1..5) of Integer;
	V, W : Vector := (0,0,0,0,0); --Заполняем массивы нулями при объявлении
begin
	V := (1,2,3,4,5);         --Перезаполняем один из массивов (работа с целым массивом)
	W := V;                   --Массив W будет заполнен теми же числами, что и V
	V(3) := 10;               --Третий элемент массива V теперь не 3, а 10
	V(4) := -1;               --Четвёртый элемент массива V теперь -1
...
end main;

Присваивание W := V будет корректно, только если V и W будут принадлежать к одному типу (в данном случае это тип Vector).

Обратите внимание, что для работы с массивами в Аде используются круглые скобки (), а не [], как во многих других языках. Таким образом, обращение к массиву становится похожим на вызов функции (или процедуры). Вспомните, например, вывод на экран: Put(). Для обращения к элементу массива тоже нужно использовать круглые скобки. Если Вам когда нибудь понадобится заменить массив на функцию или наоборот, то при использовании "()" для этого нужно будет совершить меньше телодвижений, чем при использовании "[]"

Далее нам понадобятся так называемые перечисления. Перечисление - это тип данных, переменные которого могут содержать только определённые значения, которые задаются при объявлении этого перечисления. Рассмотрим пример: нужно создать переменную "день_недели", которая может принимать значения от понедельника (mondau) до воскресенья (sundau). Здесь на помощь приходят перечисления.

В следующем примере создаётся тип Days_Of_Week, который имеет диапазон значений от понедельника (Mon), до воскресенья (Sun), а затем объявляется переменная Day типа Days_Of_Week.

...
type Days_Of_Week is(Mon, Tue, Wen, Thu, Fri, Sat, Sun); --Тип перечисление
Day : Days_Of_Week;
...
Day := Tue;

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

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

Вернёмся к массивам. Как и у предопределённых типов, у массивов есть атрибуты. Например, пусть у нас есть следующий массив:

type Vector is array(1..5) of Integer;
V : Vector;

Тогда,

V'Last   -- последний элемент массива V
V'First  -- первый элемент массива V
V'Length -- Число элементов массива V
V'Range  -- Размерность массива (границы, фактически то же, что и V'First..F'Last)

Кроме рассмотренных способов работы с массивами, в Аде можно использовать так называемые непозиционные составные значения (если кратко, то это способ задания элементов массива используя диапазоны индексов). Рассмотрим пример. В нём создаются 2 типа массивов, 1 тип перечисления и 5 массивов (3 одного типа и 2 другого), далее эти массивы заполняются и последовательно выводятся на экран. Советую скопировать эту программу и запустить её.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 
procedure Main is
    --Объявляем тип массива с индексами от одного до 5:
    type Vector is array(1..5) of Integer;
    A, B, C : Vector; --Объявляем три массива типа Vector
    --Объявляем тип Перечисление, т.е. переменные этого типа cмогут принимать только
    --указанные в скобках значения
    type Day is(Mon, Tue, Wen, Thu, Fri, Sat, Sun);
    --Объявляем тип массива с индексами от Mon до Sun
    type Week is Array(Day) of Integer;
    D, E : Week;    --Объявляем два массива типа Week
begin
    A := (1..3 => 1, 4..5 => 0);  --Заполняем массив A, используя
                                  --непозиционные составные значения. Здесь элементы
                                  --с 1 по 3 будут иметь значение 1, а с 4 по 5 - 0
    --Вывод массива A. 
    for i in 1..5 loop --Индекс поочерёдно принимает значения от 1 до 5. Если взять
                       --индекс не из этого диапазона, то при компиляции возникнет ошибка
        Put(Item => A(i), Width => 1); --Вывод элемента массива с индексом i
        Put(" ");
    end loop;
    New_Line;
 
    B := (1..3 => 10, others => -10); --Заполняем массив B, используя
                                     --непозиционные составные значения.Здесь элементы
                                     --с 1 по 3 будут иметь значение 10, а все остальные - -10
    --Вывод массива B
    for i in B'First..B'Last loop  --Использование атрибутов гарантирует, что не
                                   --будет даже предпринята попытка выйти за границы массива
	Put(Item => B(i), Width => 1); --Вывод элемента массива с индексом i
        Put(" ");
    end loop;
    New_Line;
 
    C := (1|3|5 => 5, others => 0);  --Заполняем массив C, используя
                                     --непозиционные составные значения. Здесь элементы
                                     --с номерами 1, 3 и 5 будут иметь значение 5,
                                     --а все остальные - 0
    --Вывод массива C
    for i in C'First..C'Last loop
        Put(Item => C(i), Width => 1); --Вывод элемента массива с индексом i
        Put(" ");
    end loop;
    New_Line;
 
    D := (Mon..Fri => 8, others => 0); --Заполняем массив D, используя
                                       --непозиционные составные значения
    --Вывод массива D
    for i in Mon..Sun loop
        Put(Item => D(i), Width => 1); --Вывод элемента массива с индексом i
        Put(" ");
    end loop;
    New_Line;
 
    --Заполняем массив E, используя непозиционные составные значения. Так как
    --каждому индексу (имени) значение присваивается индивидуально,
    --то порядок заполнения неважен:
    E := (Sun => 7, Sat => 6, Fri => 5, Thu => 4, Wen => 3, Tue => 2, Mon => 1);
    --Вывод массива E
    for i in E'First..E'Last loop
        Put(Item => E(i), Width => 1); --Вывод элемента массива с индексом i
        Put(" ");
    end loop;
end Main;

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

for i in reverse 1..10 loop
...
end loop;

Здесь индексы будут меняться на каждой итерации на единицу от 10 до 1.

Мы рассмотрели, как можно присваивать значения отдельным элементам массива и массивам целиком. Существует также возможность присваивать значения отдельным непрерывным участкам массива. Эти участки называются срезами (или вырезками). Рассмотрим пример:

...
procedure Slise is
	V : array(1..100) of integer; --V - т.н. анонимный массив
begin
	V(6..10) := (0,0,0,0,0);      --Присвоить срезу с 6 по 10 элемент значение 0
	V(16..20) := V(6..10);        --Присвоить один срез другому
end Slice;

Сразу необходимо отметить, что пересечение срезов запрещено и присваивание, например, V(5..9) := V(6..10) - некорректно (т.к. элементы с индексами 6, 7, 8, 9 пересекаются).

Массивы с неуточнёнными границами.

Иногда есть необходимость работать с несколькими массивами одного типа (складывать элементы разных массивов, умножать, вычитать и т.д.). Как правило, все эти массивы содержат разное количество элементов, и было бы неправильно выделять для всех таких массивов одинаковые участки памяти (равные по размеру самому большому массиву). Бывает, что память - ресурс критичный. Вместо этого можно создать тип массива с неуточнёнными границами, а затем при создании массивов указывать границы. Рассмотрим пример:

...
procedure Main is
    --Создаём тип Index - индексы массива
    type Index is range 1..100;
    --Создаём тип Vector, индексы которого имеют тип Index и могут принимать значения
    --от 1 до 100, но само количество элементов массива не указывается:
    type Vector is array(Index) of Integer;
 
    V1 : Vector(1..100);  --V1 содержит 100 элементов
    V2 : Vector(1..10);   --V2 содержит 10 элементов
    V3 : Vector(1..30);   --V3 содержит 30 элементов
begin
...
end Main;

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

Ещё одним плюсом такого подхода является то, что если у нас есть обработчик (процедура или функция) массивов созданного нами типа (в данном случае Vector), то мы можем свободно передавать любой из этих массивов в такой обработчик (т.е. в данном случае не придётся создавать 3 типа массивов с разными размерами, затем 3 массива этих типов и 3 обработчика для каждого из созданных типов). Забегая вперёд, отмечу, что передача массивов в процедуру или функцию в Аде предполагает просто передачу имени массива, т.е. заботится о передаче границ массива нет необходимости.

Существует ещё один способ работы с массивом, когда его размер неизвестен. Например, пусть размер массива неизвестен до начала выполнения программы и пользователь должен ввести размер массива (N), а затем последовательно N целых чисел (хотя на самом деле не стоит давать пользователю возможность самостоятельно определять размер массива). Тогда тип массива можно объявить, например, так:

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

В данном примере размер массива будет ограничен размером типа Integer (указанного в скобках. Хотя вместо Integer можно задать свой тип со своими границами) и элементы этого массива будут иметь тип Integer. Так как до начала выполнения программы размер массива не задан, то создание массива будет происходить в блоке выполнения команд, после того как пользователь введёт размер массива. Это можно сделать с помощью блока declare...begin...end. Рассмотрим наш пример:

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Text_IO; use Ada.Text_IO;
 
procedure Main is
    type Vector is array(Integer range <>) of Integer;
    --количество элементов создаваемого массива:
    N : Integer;
begin
    Put("Enter N: ");
    Get(N);  --Прочитать количество элементов массива
 
    declare --Начало блока declare
        V : Vector(1..N); --Объявление массива и уточнение его границ
    begin
        --Блок считывания массива
        for i in V'First..V'Last loop
            Get(V(i)); --Считывание элемента массива с клавиатуры
        end loop;
 
        --Блок вывода массива на экран
        --Массив V доступен только внутри блока declare. Как только блок declare будет
        --обработан, массив V уничтожится, поэтому вся работа с этим массивом должна
        --производится в блоке declare
        for i in V'First..V'Last loop
            Put(Item => V(i), Width => 1);
            Put(" ");
        end loop;
 
    end; --Конец блока declare
end Main;

Как видите, ничего сложного. В следующем разделе мы традиционно рассмотрим решение задач.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *