В рамках изучения Ады я решаю некоторые основные задачи по программированию. Я столкнулся с ситуацией, когда я хотел бы создать двумерный массив целых чисел фиксированного размера (размер определяется во время выполнения). Моя идея заключалась в том, чтобы иметь небольшую служебную функцию, которой можно было бы передать размер массива, создать его, заполнить и вернуть для использования в других функциях.
Как мне это сделать? Другие ответы, которые я видел, сохраняют созданный массив в области функций и не возвращают его.
Пока это моя основная процедура:
with Ada.Integer_Text_IO;
with Ada.Text_IO;
with Coord;
with Grid;
procedure test is
boundary : Coord.Box;
-- This is the array I want to create and fill
-- Note sure about what I put here for the "initial" size
new_grid : Grid.GridType (0 .. 1, 0 .. 1);
begin
-- This is just for the example, actually these
-- values are calculated from values in a text file
Ada.Text_IO.Put ("X Min?");
Ada.Integer_Text_IO.Get (boundary.min.x);
Ada.Text_IO.Put ("X Max?");
Ada.Integer_Text_IO.Get (boundary.max.x);
Ada.Text_IO.Put ("Y Min?");
Ada.Integer_Text_IO.Get (boundary.min.y);
Ada.Text_IO.Put ("Y Max?");
Ada.Integer_Text_IO.Get (boundary.max.y);
new_grid := Grid.get_grid (boundary);
Grid.print (new_grid);
end test;
А это grid.adb
, в котором есть функция get_grid
:
with Ada.Integer_Text_IO;
with Ada.Text_IO;
package body Grid is
function get_grid (bounds : Coord.Box) return GridType is
-- This is the grid I'd like to return
new_grid : Grid.GridType (bounds.min.x .. bounds.max.x, bounds.min.y .. bounds.max.y);
begin
for X in bounds.min.x .. bounds.max.x loop
for Y in bounds.min.y .. bounds.max.y loop
new_grid (X, Y) := X + Y;
end loop;
end loop;
return new_grid; -- Needs to persist outsde this function
end get_grid;
-- Print function removed for clarity (this works)
end Grid;
Grid_Type
объявляется в grid.ads
как:
type GridType is array (Integer range <>, Integer range <>) of Integer;
В этих файлах Coords.Box
— это простая запись, содержащая минимальные/максимальные целые числа X/Y.
Если я запущу это и введу разумные числа для размера сетки, я получу CONSTRAINT_ERROR
.
Я прочитал этот ответ и этот ответ и некоторые другие менее связанные ответы, и я просто не понимаю.
Я новичок в Аде, но знаю другие языки.
Размер объекта массива не может быть изменен после объявления объекта. Хотя это может показаться проблемой, Ада предоставляет решение путем объявления объекта во внутреннем блоке.
В приведенном ниже примере тип вашего массива определяется как неограниченный массив element_type. Замените любой тип, который вы хотите, чтобы ваш массив содержал в вашем фактическом коде. Этот неограниченный тип позволяет создавать экземпляры типа с любыми необходимыми размерами.
type Grid_Type is array(Natural range <>, Natural range <>) of element_type;
В вашей функции прочитайте информацию о границах массива, а затем объявите экземпляр, используя эти границы массива.
function Make_Grid return Grid_Type is
Dim1_Min, Dim1_Max : Natural;
Dim2_Min, Dim2_Max : Natural;
begin
get(Dim1_Min);
get(Dim1_Max);
get(Dim2_Min);
get(Dim2_Max);
declare
New_Grid : Grid_Type(Dim1_Min..Dim1_Max, Dim2_Min..Dim2_Max);
begin
return New_Grid;
end;
end Make_Grid;
Внутренний блок создает новый экземпляр Grid_Type, используя значения, считанные из ввода. Функция просто возвращает объект из внутреннего блока. Раздел 5.6 Справочного руководства по Ада описывает операторы блока Ада.
В следующем примере показано, как это работает для массива целых чисел:
package Grids is
type Grid_type is array(Natural range <>, Natural range <>) of Integer;
function make_grid return Grid_Type;
end Grids;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
package body Grids is
---------------
-- make_grid --
---------------
function make_grid return Grid_Type is
Dim1_Min, Dim1_Max : Natural;
Dim2_Min, Dim2_Max : Natural;
begin
Get(Dim1_Min);
Get(Dim1_Max);
Get(Dim2_Min);
Get(Dim2_Max);
declare
New_Grid : Grid_Type(Dim1_Min..Dim1_Max, Dim2_Min..Dim2_Max) :=
(Others =>(Others => 0));
begin
return New_Grid;
end;
end make_grid;
end Grids;
Основная тестовая программа для этой программы:
with Ada.Text_IO; use Ada.Text_IO;
with Grids; use Grids;
procedure Grids_Test is
The_Grid : Grid_type := Make_Grid;
begin
Put_Line("Grid Dimensions");
Put_Line(Natural'Image(The_Grid'First(1)) & ".." &
Natural'Image(The_Grid'Last(1)) & " , " &
Natural'Image(The_Grid'First(2)) & "..." &
Natural'Image(The_Grid'Last(2)));
end Grids_Test;
Ввод и вывод выполнения примера:
0
10
20
30
Grid Dimensions
0.. 10 , 20... 30
Кроме того, я добавил свой Grid_Type
к вопросу, я случайно пропустил это.
Make_Grid
возвращает копию New_Grid
.
Переменные являются локальными для блока, в котором они объявлены, точно так же, как переменные, объявленные в функциональном блоке, являются локальными для функционального блока. Эта локальность не означает, что объект не может быть возвращен из функции.
Ах, я использовал мысленную модель этого, как будто это был C/C++, возвращающий указатель на объект, созданный с помощью malloc. Что явно неправильно!
return New_Grid : Grid_Type(… dims …) := (others => (others => 0));
будет короче и позволит избежать копирования потенциально большой структуры данных.
Значение по-прежнему копируется при возврате к объекту, определенному в вызывающей области. Копирование не предотвращается приведенным выше предложением.
@JimRogers Можете ли вы подтвердить это утверждение? Согласно Ада 2005 обоснование: Другими словами, адрес X передается как скрытый параметр функции Make, чтобы создать пространство для R. Поэтому копирование никогда не выполняется.
@flyx Раздел, который вы цитируете в обосновании Ады 2005, относится к возврату ограниченных типов. Ограниченные типы не могут быть переданы копированием, так как для ограниченных типов нет операции присваивания. Это требует прохождения по типу доступа. Неограниченные типы не имеют этого ограничения и передаются путем копирования.
В дополнение к использованию блоков объявления для временного хранения результата вашей функции вы также можете использовать Ада.Контейнеры.Индефините_Холдерс для более постоянного хранения результата.
Вам нужно будет создать экземпляр общего
use type Grid.Grid_Type; -- necessary to get the " = " operation
package Grid_Holders is new Ada.Containers.Indefinite_Holders(Grid.Grid_Type);
Вы бы объявили это как
New_Grid : Grid_Holders.Holder;
и во время выполнения вы можете инициализировать его, используя
New_Grid := Grid_Holders.To_Holder(Grid.Get_Grid(Boundary));
как только вы это сделаете, вы можете получить доступ к сетке, используя операцию Reference:
Grid.Print(New_Grid.Reference);
Если вы хотите получить доступ к элементам напрямую, вам может потребоваться сделать что-то вроде:
Grid.Reference.Element(1,2) := 23;
Обратите внимание, что в некоторых версиях GNAT есть ошибка, из-за которой может генерироваться исключение Finalization. Если это так, вы обычно можете обойти это, используя объявляющий блок или функцию. Объявить пример блока:
declare
The_Grid : Grid.Grid_Type renames New_Grid.Reference;
begin
The_Grid(1,2) := 23;
end;
Это отличается от примера объявления блока другого ответа, потому что этот блок объявления не создает никаких новых сеток, он просто обращается к существующей памяти. Это чисто для обхода ошибки компилятора. В этом примере New_Grid существует вне блока объявления, поэтому он остается доступным.
Я думал, что переменные
declare
были локальными по объему/времени жизни блока? Или в данном случае по-другому? Или у меня там что-то не так?