Для организации динамических структур используются переменные-указатели. Идея, лежащая в основе концепции указателей, состоит в том, чтобы связать определенный тип данных с конкретным указателем. Сам указатель в свою очередь тоже является элементом данных и представляет собой ссылку на определенную ячейку памяти, начиная с которой записывается значение переменной. Указатели бывают типизированные, указывающие на данные определенного типа, и нетипизированные (типа pointer), которые могут указывать на данные произвольного типа.
Тип указателя определяется специальным символом ^, за ним следует идентификатор типа динамических переменных, к которым можно будет обращаться через переменные указатели этого типа. Фактически указатель является адресом памяти, по которому можно получить доступ к значению динамической переменной.
Объявление своего типизированного указателя на любой тип имеет вид:
type <имя типа указателя> = ^<тип данных>;
Например, предложения:
type Pint = ^integer;
var P1, P2 : Pint;
объявляет тип Pint указателя на величину типа integer и две переменные P1 и P2, являющиеся указателями на значения типа integer. Однако надо помнить, что объявление переменных P1 и P2 не создает самих величин, на которые они указывают. Выделяется только память под хранение указателей, но сами эти указатели ни на что не указывают. Имеется предопределенная константа nil, которая обычно присваивается указателям, которые в данный момент ни на что не указывают.
Чтобы получить доступ к данным, на которые указывает типизированный указатель, надо применить операцию его разыменования. Она записывается с помощью символа ^, помещаемого после указателя. Например, если переменная P1 является указателем приведенного выше типа Pint, то выражение P1^ – это та целая величина, на которую указывает указатель P1. Если R – переменная целого типа, то после выполнения оператора
P1^ := R;
P1 начнет указывать на переменную R и выражение P1^ будет возвращать значение этой переменной. Того же результата можно добиться операцией адресации. Например, приведенный выше оператор можно заменить эквивалентным ему оператором
P1 := @R;
Этот оператор присваивает указателю P1 адрес переменной R.
Таким образом, применение операций разыменования или адресации – один из способов присвоить указателю ссылку на конкретную область памяти. Другой более распространенный способ – использование процедур.
Динамическое распределение памяти может производиться двумя способами: с помощью процедур New и Dispose и процедурами GetMen и FreeMen.
При первом способе выделение памяти производится процедурой
Procedure New (<имя указателя>);
где <имя указателя> – имя переменной, являющейся типизированным указателем. Этой переменной при успешном завершении процедуры передается адрес начала выделенной области памяти. Размер выделяемой области определяется размером памяти, необходимым для размещения того типа данных, который указан при объявлении указателя.
объявляют переменную Р, являющуюся указателем на действительное значение, процедура New выделяет память для этого значения. А следующий оператор заносит в эту область число 5.5.
Приведенный ниже пример показывает выделение памяти под запись:
type
rec = record
fio: string[40];
year: integer;
end;
var Pr: ^rec;
New(Pr);
with Pr^ do begin
fio : = ‘Иванов Иван Иванович’;
year := 1962;
end;
Освобождение памяти, динамически выделенной процедурой New, осуществляется процедурой Dispose:
procedure Dispose(<имя указателя>);
В эту процедуру должен быть передан тот указатель, в котором хранится адрес области памяти, выделенной ранее процедурой New. Для приведенных выше примеров соответствующие процедуры Dispose могут быть записаны следующим образом:
Dispose (Р);
Dispose(Pr);
Надо отметить, что применение процедуры Dispose освобождает память, но не изменяет значения указателя, не делает его равным nil, хотя теперь указатель не указывает ни на что конкретное.
Второй способ динамического выделения памяти связан с применением процедур GetMem для выделения памяти и FreeMem для ее освобождения. Они имеют следующий синтаксис:
procedure GetMem(<имя указателя>,<объем памяти в байтах>);
procedure FreeMem(<имя указателя>,<объем памяти в байтах>);
В отличие от процедур New и Dispose здесь задается не только указатель, в котором устанавливается процедурой GetMem и читается процедурой FreeMem адрес выделенной области памяти, но и указывается объем памяти в байтах. Благодаря этому в процедурах могут использоваться не только типизированные, но и нетипизированные указатели. Если же используется типизированный указатель, то объем необходимой памяти лучше всего определять функцией SizeOf, так как размеры памяти, отводимой под тот или иной тип данных, могут изменяться в различных версиях компилятора. Таким образом, в приведенных выше примерах вызовы процедуры New могут быть заменены следующим образом:
GetMem(P, SizeOf (real));
…
FreeMem(P, SizeOf (real));
…
GetMem(Pr, SizeOf (rec));
…
FreeMem(Pr, SizeOf(rec));
Надо иметь в виду, что два рассмотренных метода нельзя смешивать. Например, нельзя освободить методом FreeMem память, выделенную ранее методом New, и нельзя освободить методом Dispose память, выделенную методом GetMem.