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

вкл. .

 

Массив √ это упорядоченный набор данных. Как правило, количество элементов массива ограничено. Среда Delphi использует синтаксис языка Object Pascal, а согласно последнему, массивы объявляются так:

var My_Array : array[index1..indexN] of BaseType
 

Где index1 и indexN принадлежат упорядоченному типу, диапазон которого, как написано в документации по Delphi 6, не превышает 2Gb. BaseType √ тип элементов массива.

Например,

var My_Array : array[0..99] of Real;

Мы объявили массив My_Array, состоящий из 100 элементов типа Real. Массивы могут быть одномерными, двухмерными и n-мерными. Теоретически, ограничения на размерность массива нет, на практике размерность ограничена доступной памятью.

Все сказанное верно для статических массивов. Однако статические массивы обладают существенным недостатком. В большинстве случаев мы не знаем, из скольких элементов будет состоять наш массив. Поэтому приходится резервировать память "про запас": лучше я зарезервирую память еще для десяти элементов массива, чем один элемент массива окажется лишним. Например, при чтении информации из файла мы не знаем, сколько элементов мы можем прочитать: один, два, десять или сто. Обычно для решения таких задач применялись динамические списки LIFO (Last In First Out, стек) или FIFO (First In First Out, очередь).

Разработчики Delphi в последних версиях (5,6) своего продукта реализовали достаточно гибкий механизм для работы с динамическими массивами. Нам уже не нужно создавать динамические списки, мы уже никогда не забудем поставить знак "^" при обращении к элементу списка и на определенное время забудем о процедурах new, mark и dispose.

Примечание. В более ранних версиях (например, 3) такого механизма не существовало.

Динамические массивы не имеют фиксированного размера или длины. Для объявления такого массива достаточно записать:

var My_Array : array of Real;

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

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

SetLength(My_Array,100);

После вызова этой процедуры будет выделена память для 100 элементов массива, которые будут проиндексированы от 0 до 99 (обратите внимание: индексирование начинается с нуля, а не с единицы!).

Динамические массивы √ это неявные указатели и обслуживаются тем же самым механизмом, который используется для обработки длинных строк (long strings). Чтобы освободить память, занимаемую динамическим массивом, присвойте переменной, которая ссылается на массив, значение nil: A:=nil.

Сухая теория без практики √ это ничто, поэтому для лучшего переваривания материала рассмотрим следующую программу:

Листинг 1

program Project1;

{$APPTYPE CONSOLE}

uses
 SysUtils;

var A,B : array of Integer;

1. begin {A[], B[] √ память не выделена}
 
2. setlength(A,2); { A[0,0], B[]}
3. B:=A; { A[0,0], B[0,0]}
4. A[0]:=2; A[1]:=4; { A[2,4], B[2,4]}
 Writeln(A[0],' ',A[1]); 

5. setlength(A,3); { A[2,4,0], B[2,4]}
 Writeln(A[0],' ',A[1]); 

6. A[0]:=2; A[1]:=4; A[2]:=5; B[0]:=1; { A[2,4,5], B[1,4]}
 Writeln(A[0],' ',A[1],' ',A[2]);
7. A:=nil; { A[], B[1,4]}
 Writeln(A[0],' ',A[1],' ',A[2]);

end.

Это консольное приложение, при его запуске вы увидите окно, напоминающее сеанс DOS. Скорее всего, это окно закроется, прежде чем вы успеете его увидеть. Поэтому узнать, как же все-таки Delphi работает с этим чудом природы, вам поможет режим пошаговой трассировки программы (активизируется нажатием клавиши F7). Перед запуском приложения с помощью команды меню Run, Add Watch (Ctrl + F5) добавьте в список просмотра переменных две переменные √ A и B .

Затем откройте окно Watch List, нажав комбинацию клавиш Ctrl + Alt + W и установите режим окна Stay On Top (команда Stay On Top появится в всплывающем меню, если вы щелкните правой кнопкой где-нибудь в области окна), чтобы окно не скрылось из виду, когда вы запустите программу .В листинге 1 возле каждой строки я указал значения переменных A и B после прохода строки. Как вы заметили, я пронумеровал только строки, которые содержат операторы, изменяющие переменные A и B.

Строка 1: для переменных A и B память не выделена. Отсюда вывод: до вызова процедуры SetLength работать с массивом нельзя. Во второй строке память выделена только для массива A. Поскольку переменная A √ глобальная, значения элементов массива обнуляются. Если бы переменная A была локальной, потребовалась инициализация элементов массива. Локальные переменные не обнуляются и, как правило, содержат случайные значения (мусор), взятые из памяти, в которой размещена переменная.

В третьей строке мы присваиваем массиву B массив A. Перед этой операцией не нужно выделять память для массива B с помощью SetLength. При присваивании элементу B[0] присваивается элемент A[0], B[1] √ A[1] и т.д. Обратите внимание на строку 4: мы присвоили новые значения переменным массива A и автоматически (массивы √ это же указатели) элементам массива B присвоились точно такие же значения и наоборот: стоит нам изменить какой-либо элемент массива B, изменится элемент массива A с соответствующим индексом.

Теперь начинается самое интересное. При изменении длины массива А (строка 5) произошел разрыв связи между массивами. Это явление демонстрирует строка 6, содержащая ряд операторов присваивания. Сначала мы присваиваем значения элементам массива A: 2, 4, 5. Затем пытаемся изменить значение элемента A[0], присвоив элементу B[0] значение 1. Но, как мы убедились, связи между массивами уже нет и после изменения значения элемента B[0], значение элемента A[0] не было изменено. Еще один важный момент: при изменении длины массива просто добавляется новый элемент, без потери уже существующих значений элементов (строка 5). Длина связанного массива B не изменяется.

В строке 7 мы освобождаем память, выделенную для массива A. Массив B остается существовать до конца работы программы. Массивы нулевой длины содержат значение nil.

Нужно сказать пару слов о сравнении массивов:

SetLength(A,3);
B:=A;

При попытке сравнения A=B или B=A мы получим true: массивы равны. А вот в такой ситуации при сравнении мы получим false:

SetLength(A,3);
SetLength(B,3);

Для того, чтобы обрезать массив можно использовать ту же процедуру SetLength или процедуру Copy. Первая работает намного быстрее, поэтому я рекомендую использовать именно ее. Рассмотрим следующий фрагмент кода:

setlength(A,5); 
for i:=0 to 4 do A[i]:=i; {A[0,1,2,3,4]}
setlength(A,4); {A[0,1,2,3]}

При обрезании (уменьшении длины) происходит освобождение памяти с конца массива: удаляются последние элементы. Процедуру Copy для этой же цели можно использовать так:

A:=Copy(A,0,2); {копируются первые два элемента A[0] и A[1], остальные обрезаются}

При попытке присвоить значение элементу статического массива, индекс которого выходит за границы массива, мы получим соответствующую ошибку: Constant expression violates subrange bounds (нарушение границ диапазона). При работе с динамическими массивами вы не увидите подобной ошибки. С одной стороны это хорошо, а с другой √ плохо. Вы можете потратить уйму времени, чтобы понять, почему вы не можете присвоить значение элементу массива с индексом 4, пока не обнаружите, что в самом начале программе выделили память только для трех элементов. Сейчас эта ситуация кажется смешной, но она происходит намного чаще, чем вам кажется. Функция SetLength порождает ошибку EOutOfMemory, если недостаточно памяти для распределения массива.

Не применяйте оператор "^" к переменной динамического массива. Также не следует передавать переменную массива процедурам New или Dispose для выделения или освобождения памяти.

Как только динамический массив был распределен, вы можете передавать массив стандартным функциям Length, High и Low. Функция Length возвращает число элементов в массиве, High возвращает массив самый высокий индекс (то есть Length √ 1), Low возвращает 0. В случае с массивом нулевой длины наблюдается парадоксальная ситуация: High возвращает -1, а Low √ 0, получается, что High < Low.

Как мы можем использовать функции High и Low с наибольшей пользой? Для этого рассмотрим процедуру init и функцию sum. Первая из них инициализирует массив (обнуляет значения элементов), а вторая √ подсчитывает сумму элементов массива:

procedure Init(var A: array of Real);

var
 I: Integer;
begin
 for I := 0 to High(A) do A[I] := 0;
end;

function Sum(const A: array of Real): Real;

var
 I: Integer;
 S: Real;
begin
 S := 0;
 for I := 0 to High(A) do S := S + A[I];
 Sum := S;
end;

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

var A : array of array of integer;

Для выделения памяти нужно вызвать процедуру SetLength с двумя параметрами, например,

SetLength(A,5,7);

Работать с двухмерным динамическим массивом можно так же, как и со статическим:

for i:=0 to 5 do
 for j:=1 to 7 do A[i,j]:=i+j;

Вы можете создавать не прямоугольные массивы. Для этого сначала объявите массив:

var A : array of array of Integer;

Затем создайте n рядков, но без колонок, например,

setlength(A,n);

Теперь вы можете создавать колонки разной длины:

SetLength(A[0], 5);
SetLength(A[1], 7);
SetLength(A[2], 3);
...
SetLength(A[n-1], 10);

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

 

 

Оставь комментарий первым