Массивы
Массивы (или индексируемые типы) предоставляют еще одно фундаментальное семейство составных типов в Аде.
Объявление типа массива
Массивы в Аде используются для определения непрерывных коллекций элементов, к которым можно обращаться по индексу.
Первое, что следует отметить, - это то, что мы указываем не размер массива,
а тип его индекса. Здесь мы объявили целочисленный тип с
именем Index
в диапазоне от 1
до 5
, поэтому каждый
экземпляр массива будет иметь 5 элементов, с индексами от 1 до 5.
Хотя в этом примере в качестве индекса использовался целочисленный тип, в Аде любой дискретный тип может служить для индексации массива, включая (перечислимые типы). Скоро мы увидим, что это значит.
Следующий момент, который следует отметить, заключается в том, что доступ к элементу массива по заданному индексу использует тот же синтаксис, что и для вызовов функций: то есть имя массива, за которым следует индекс в скобках.
Таким образом, когда вы видите выражение, такое как A (B)
, является
ли оно вызовом функции или индексом массива, зависит от того, на что
ссылается A
.
Наконец, обратите внимание, как мы инициализируем массив выражением
(2, 3, 5, 7, 11)
. Это еще один вид агрегата в Аде, который в
некотором смысле является литералом для массива, точно так
же, как 3
является литералом для целого числа.
Обозначение очень мощное, с рядом свойств, которые мы представим позже.
Подробный обзор приводится в главе
о агрегатах типов.
Пример также иллюстрирует две процедуры из:ada:Ada.Text_IO, которые не связанны с массивами:
Put
- выводит строку без завершающего конца строки.New_Line
- вывод конца строки
Давайте теперь углубимся в то, что значит иметь возможность использовать любой дискретный тип в качестве индекса массива.
На других языках
Семантически объект массива в Аде - это цельная структура данных, а не просто дескриптор или указатель. В отличие от C и C ++, не существует неявной эквивалентности между массивом и указателем на его начальный элемент.
Как следствие границы массива могут иметь произвольные
значения. В первом примере мы создали тип массива, первый индекс
которого равен 1
, но в приведенном выше примере мы объявляем тип
массива, первый индекс которого равен 11
.
Это прекрасно работает в Аде, а, кроме того, поскольку мы для итерации по массиву указали как диапазон тип индекса, то код использующий массив, не приходится менять.
Это приводит нас к важному принципу написания кода, оперируещего с
массивами. Поскольку границы могут меняться, лучше не полагаться
на конкретные значения и не указывать их в коде использующем массив.
Это означает, что приведенный выше код
хорош, потому что он использует тип индекса, но цикл for
, приведенный
ниже, считается плохой практикой, даже если он работает правильно:
for I in 11 .. 15 loop
Tab (I) := Tab (I) * 2;
end loop;
Поскольку для индексации массива можно использовать любой дискретный тип, разрешены и перечислимые типы.
В приведенном выше примере мы:
Создание типа массива для отображения месяца в его продолжительность в днях.
Создание экземпляра этого массива и инициализация его с помощью агрегата указывающего фактическую продолжительностью каждого месяца в днях.
Итерация по массиву, печать месяцев и количество дней для каждого.
Возможность использования перечислимых значений в качестве индекса очень полезна при создании сопоставлений, как показано выше, и часто используется в Аде.
Доступ по индексу
Мы уже видели синтаксис обращения к элементам массива. Однако следует отметить еще несколько моментов.
Во-первых, как и в целом в Аде, операция индексирования строго типизирована. Если для индексации массива используется значение неправильного типа, будет получена ошибка времени компиляции.
Во-вторых, в Аде контролируется выход за границы массива. Это означает, что если вы попытаетесь обратиться к элементу за пределами массива, вы получите ошибку во время выполнения вместо доступа к случайной памяти, как в небезопасных языках.
Более простые объявления массива
В предыдущих примерах мы всегда явно создавали тип индекса для массива. Хотя это может быть полезно для типизации и удобства чтения, иногда вам просто нужно указать диапазон значений. Ада позволяет вам делать и так.
В этом примере диапазон массива определен с помощью синтаксиса диапазона, который указывает анонимный подтип Integer и использует его в качестве индекса массива.
Это означает, что индекс имеет целочисленный тип Integer
. Точно так
же, когда вы используете анонимный диапазон в цикле for
, как
в приведенном
выше примере, тип параметра цикла также является Integer
, поэтому
вы можете использовать I
для индексации Tab
.
Также вы можете использовать именованный подтип для указания границ массива.
Атрибут диапазона
Ранее мы отметили, что указывать жесткие границы при итерации по
массиву - плохая идея, и показали, как использовать тип/подтип
индекса массива для итерации в цикле for
. Это
поднимает вопрос о том, как написать итерацию, когда массив имеет
анонимный диапазон для своих границ, поскольку нет имени позволяющего
сослаться на диапазон. В Аде эта проблема решается с помощью нескольких
атрибутов существующих у массивов:
Если требуется более точный контроль, можно использовать
отдельные атрибуты 'First
(«Первый») и 'Last
(«Последний»).
Атрибуты 'Range
, 'First
и 'Last
показанные в этих
примерах также можно применять к имени типа массива, а не только
к экземплярам массива.
Хотя это не демонстрируется в вышеприведенных примерах, другим полезным
атрибутом для экземпляра массива A
является A'Length
, который
возвращает количеством элементов в A
.
Законно и иногда полезно иметь «пустой массив», который не содержит элементов. Чтобы получить его, достаточно определить диапазон индексов, верхняя граница которого меньше нижней границы.
Неограниченные массивы
Давайте теперь рассмотрим одину из самых мощных возможностей массивов в языке Ада.
Все типы массивов, который мы определили до сих пор, имеют фиксированный размер: все экземпляры такого типа будут иметь одинаковые границы и, следовательно, одинаковое количество элементов и одинаковый размер.
Но Ада также позволяет объявлять типы массивов, границы которых не являются фиксированными: в этом случае границы необходимо будет указать при создании экземпляров типа.
Тот факт, что границы массива неизвестны, указывается синтаксисом
Days range <>
. Взяв, для примера дискретный тип Discrete_Type
,
eсли бы мы указали просто Discrete_Type
как индекс
массива, то для каждого значения из Discrete_Type
существовал бы индекс и соответствующий элемент в каждом
экземпляре массива.
Но, если мы определим индекс как Discrete_Type range <>
,
то, хотя Discrete_Type
все еще будет типом индекса, но разные
экземпляры массива смогут иметь свои границы.
Тип массива, который определяется с помощью синтаксиса
Discrete_Type range <>
, называется неограниченным индексируемым типом,
и, как показано выше, при создании экземпляра необходимо указать границы.
В приведенном выше примере также показаны другие формы записи
агрегата. Вы можете использовать именованное сопоставление, задав значение
индекса слева от стрелки. Таким образом, 1 => 2
означает «присвоить значение 2 элементу с индексом 1 в моем массиве».
Запись others => 8
означает «присвоить значение 8 каждому элементу,
который ранее не был назначен в этом агрегате».
Attention
Так называемый «бокс» (<>
) в Аде обычно
используется для обозначение места, где отсутствует некий элемент.
Как мы увидим еще не раз, такое обозначение можно читать, как
«значение, не заданое явно».
На других языках
В то время как неограниченные массивы в Аде могут казаться похожими на
массивы переменной длины в C, в действительности они гораздо более
мощные, поскольку работают как настоящие значения.
Их можно передавать их как параметры при вызове подпрограммам и возвращать
как результат функции, и они неявно содержат границы как часть своего
значения.
Это означает, что нет нужды явно передавать границы или длину массива
вместе с массивом, поскольку эти значения доступны через атрибуты
'First
, 'Last
, 'Range
и 'Length
,
описанные ранее.
Хотя различные экземпляры одного и того же неограниченного типа массива могут иметь разные границы, конкретный экземпляр имеет постоянные границы в течение всего срока его существования. Это позволяет компилятору языка Ада эффективно реализовывать неограниченные массивы; массивы могут храниться в стеке и не требуют выделения в куче, как в других языках, типа Java.
Предопределенный тип String
В нашем введении в типы языка Ада было отмечено, что такие важные
встроенные типы, как Boolean
или Integer
, определяются
с помощью тех же средств, что доступны пользователю. Это также верно и для
строк: тип String
в Аде является простым массивом.
Вот как тип строки определяется в Аде:
type String is array (Positive range <>) of Character;
Единственной дополнительной возможностью, которую Ада добавляет, чтобы сделать строки более простыми в использовании, являются строковые литералы, как мы можем видеть в примере ниже.
Hint
Строковые литералы являются синтаксическим сахаром для агрегатов, так
что в следующем примере A
и B
имеют одинаковое значение.
Однако явное указание границ объекта - это немного хлопотно; вам нужно вручную подсчитать количество символов в литерале. К счастью, Ада предлагает более простой способ.
Вы можете опустить границы при создании экземпляра неограниченного типа массива, если вы предоставляете инициализацию, поскольку границы могут быть вычислены по выражению инициализации.
Attention
Как вы можете видеть выше, стандартный тип String в Аде - это массив.
Таким образом, он сохраняет преимущества и недостатки массивов: значение
String
выделяется в стеке, доступ к нему осуществляется эффективно,
а его границы неизменны.
Если вам нужно что-то вроде :c++:std::string в C++, вы можете использовать Unbounded Strings из стандартной библиотеки Ада. Этот тип больше похож на изменяемый, автоматически управляемый строковый буфер, в который вы можете добавлять содержимое.
Ограничения
Очень важный момент, касающийся массивов: границы должны быть известны при создании экземпляров. Например, незаконно делать следующее.
declare
A : String;
begin
A := "World";
end;
Кроме того, хотя вы, конечно, можете изменять значения элементов в массиве, вы не можете изменять границы массива (и, следовательно, его размер) после его инициализации. Так что это тоже незаконно:
declare
A : String := "Hello";
begin
A := "World"; -- OK: Same size
A := "Hello World"; -- Not OK: Different size
end;
Кроме того, хотя вы можете ожидать предупреждения об ошибке такого рода в очень простых случаях, подобных этому, но компилятор в общем случае не может знать, присваиваете ли вы значение правильной длины, поэтому это нарушение обычно приведет к ошибке во время выполнения.
Обратите внимание
Хотя мы остановимся на этом позже, важно знать, что массивы - не единственные типы, экземпляры которых могут быть неизвестного размера в момент компиляции.
Говорят, что такие объекты имеют неопределенный подтип, что означает, что размер подтипа неизвестен во время компиляции, но динамически вычисляется (во время выполнения).
Возврат неограниченных массивов
Тип возвращаемого значения функции может быть любым; функция может возвращать значение, размер которого неизвестен во время компиляции. Точно так же параметры могут быть любого типа.
Например, это функция, которая возвращает неограниченную строку:
Примечание. Этот пример приведен только в иллюстративных целях.
Существует встроенный механизм, атрибут 'Image
для скалярных типов,
который возвращает имя (в виде строки – типа String
) любого элемента
типа перечисления. Например, Days'Image(Monday)
есть "MONDAY"
.)
На других языках
Возврат объектов переменного размера в языках, в которых отсутствует сборщик мусора, довольно сложен с точки зрения реализации, поэтому C и C++ не допускают такого, предпочитая зависеть от явного динамического распределения/освобождения памяти пользователем.
Проблема в том, что явное управление памятью становится небезопасным, как только вы захотите использовать выделенную память повторно. Способность Ада возвращать объекты переменного размера устраняют необходимость динамического распределения для одгого варианта использования и, следовательно, удаляет один потенциальный источник ошибок из ваших программ.
Rust следует модели C/C++, но использует указатели с безопасной семантикой. При этом динамическое распределение все еще используется. Ада может извлечь выгоду в виде повышения производительности, поскольку оставляет возможность использовать любой из этих механизмов.
Объявление массивов (2)
Хотя мы можем иметь типы массивов, размер и границы которых определяются во время выполнения, тип компоненты массива всегда должен быть определенного и ограниченного типа.
Таким образом, если необходимо объявить, например, массив строк,
подтип String
, используемый в качестве компоненты, должен иметь
фиксированный размер.
Отрезки массива
Последняя особенность массивов Аде, которую мы собираемся рассмотреть, - это отрезки массива. Можно взять и использовать отрезок массива (непрерывную последовательность элементов) в качестве имени или значения.
Как мы видим выше, вы можете использовать отрезок слева от присваивания, чтобы заменить только часть массива.
Отрезок массива имеет тот же тип, что и массив, но имеет другой подтип, ограниченния которого заданы границами отрезка.
Attention
В Ада есть многомерные массивы, которые не рассматриваются в этом курсе. Отрезки будут работать только с одномерными массивами.
Переименование
До сих пор мы видели, что следующие элементы могут быть переименованы:
подпрограммы,
пакеты
и компоненты записи. Мы также можем
переименовывать объекты с помощью ключевого слова renames
. Это
позволяет создавать альтернативные имена для данных объектов. Давайте
посмотрим на пример:
В приведенном выше примере мы объявляем переменную T
, переименовывая
объект Current_Temperature
из пакета Measurements
. Как вы
можете видеть, запустив этот пример, и Current_Temperature
, и
его альтернативное имя T
имеют одинаковые значения:
сначала они показывают значение 5.0
после сложения они показывают значение 7.5.
Это потому, что они по существу обозначают однин и тот же объект, но с двумя разными именами.
Обратите внимание, что в приведенном выше примере мы используем Degrees
как псевдоним Degree_Celsius
. Мы обсуждали этот метод переименования
ранее в курсе.
Переименование может быть полезно для улучшения читаемости кода со сложной индексацией массивов. Вместо того, чтобы явно использовать индексы каждый раз, когда мы обращаемся к определенным позициям массива, мы можем создавать короткие имена для этих позиций, переименовывая их. Давайте посмотрим на следующий пример:
В приведенном выше примере пакет Colors
содержит процедуру
Reverse_It
, где объявлены новые имена для двух позиций массива.
Таким образом реализация становится легко читаемой:
begin
Tmp := X_Left;
X_Left := X_Right;
X_Right := Tmp;
end;
Сравните это с альтернативной версией без переименования:
begin
Tmp := X (I);
X (I) := X (X'Last + X'First - I);
X (X'Last + X'First - I) := Tmp;
end;