3. Тип данных - Указатель.
Для работы с динамической памятью в Турбо Паскале используется специальный тип данных - Указатель. Переменная типа Указатель имеет значением адрес оперативной памяти. Адрес приписан каждому байту оперативной памяти (от 0 до max) и состоит из двух частей: сегмента и смещения. Сегмент - непрерывный участок оперативной памяти длиной 64кбайт, начинающийся с физического адреса, кратного 16. Смещение указывает относительный адрес внутри сегмента. Как сегмент, так и смещение являются данными типа word. Таким образом, по своей внутренней структуре указатель состоит из двух слов типа word. С помощью указателя можно размещать в хип-памяти любой тип данных Турбо Паскаля (кроме файла). В случае, когда размещаемый тип данных имеет длину более 1 байта, указатель указывает на начало соответствующей структуры (на первый байт данных).
В Турбо-Паскале имеется два вида указателей:
типизированный указатель, указывающий на заданный тип данных;
нетипизированный указатель, указывающий лишь на область памяти данных.
Форма описания указателя зависит от вида указателя. Типизированный указатель описывается в следующей синтаксической форме:
type <имя типа-указателя> = ^ <имя базового типа >;
Здесь базовым типом может быть любой тип Турбо Паскаля, а символ ^, помещаемый перед базовым типом, идентифицирует тип-указатель.
Например: type pint = ^integer; - тип-указатель на данные типа integer.
Синтаксическая форма описания нетипизированного указателя имеет вид:
type <имя типа-указателя> =pointer;
Для работы с указателями, как и с любыми другими типами данных, объявляются переменные типа-указатель.
Например: var pp:pint; { переменная типа pint (типизированный указатель)}
np:pointer; {переменная нетипизированного указателя}
Наиболее часто указатели используются для создания рекурсивных структур, характерным примером которых являются списки.
Рекурсивной называется структура, описание которой содержит обращение к самой себе. Такие обращения в Турбо Паскале возможно реализовать только с помощью указателей.
Например: type refer = ^ inf; inf = record name,adr,tel:string;next:refer end;
Эта рекурсивная структура описывает линейный список, элементами которого являются записи, содержащие информацию о телефонах (три поля: имя, адрес и номер телефона) и указатель на следующую запись в списке.
4. Принципы работы с указателями.
Переменная типа указатель, как мы установили выше, всегда является некоторым адресом оперативной памяти, идентифицирующим размещаемую в хип-памяти структуру данных.
Создание указателя на структуру, по сути, является особым способом именования этой структуры, отличным от имён, принятых для статических переменных. Необходимо помнить, что обращаясь к указателю, мы обращаемся не непосредственно к структуре данных (не к её значению !), а лишь к адресу начала размещения этой структуры.
В тех случаях, когда необходимо обратиться к значению представленной структуры данных, следует выполнить операцию разыменования, которая в Турбо Паскале имеет специальное обозначение: если p - указатель, то p^ - значение, на которое указывает p. (Обратите внимание, что символ ^ при разыменовании записывается после переменной-указателя, а не до него, как при описании типизированного указателя).
Над указателями (т.е. адресами) допустимы операторы присваивания вида:
p1:=p2; - где p1,p2 - только указатели одного и того же типа.
p:= nil; - где р - переменная типа указатель, а nil - нулевой указатель (не являющийся ни одним из адресов хип-памяти), т.е. "нулевая" константа типа указатель.
Для работы с указателями в Турбо Паскале предусмотрен ряд стандартных процедур и функций. Важнейшими из них являются процедуры выделения (резервирования) хип-памяти под структуры, на которые указывают указатели.
Выделение хип-памяти под структуры, именуемые типизированными указателями, выполняет следующая процедура:
new(<переменная типизированного указателя>);
При выполнении этой процедуры менеджер выделяет хип-память для размещения структуры, на которую указывает параметр этой процедуры. Значением этого параметра становится адрес, на котором начинается указанная структура. Заметим, что непосредственно после выполнения new значение размещаемой структуры неопределенное - просто под структуру зарезервирована память. Для "наполнения" этой структуры каким-либо значением можно использовать присваивание разыменованному указателю.
Для нетипизированных указателей память выделяется другой процедурой:
getmem(<нетипизированный указатель>, < размер памяти >);
где: <размер памяти> указывается в байтах.
Действие процедуры getmem аналогично процедуре new, различие лишь в том, что размер структуры задаётся явно вторым параметром этой процедуры (для new он определялся неявно по описанию типа указателя).
Заметим, что работа с нетипизированными указателями сложнее, т.к. всю ответственность за размещение структуры и её "наполнение" значением несёт только сам программист. В то же время этот вид указателей обладает большей гибкостью и способен размещать в одном и том же фрагменте памяти различные типы структур, создавая предпосылки для более эффективного использования памяти. Начинающим программистам не рекомендуется, тем не менее, работать с нетипизированными указателями.
Оператор присваивания, процедуры выделения хип-памяти и разыменование переменной-указателя составляют основной арсенал средств работы с указателями. Кроме того, для указателей (как для адресов) допустимы обычные отношения сравнения: =, <>, <, >, <=, >=. Проиллюстрируем эти средства на примере - программе работы с линейным списком - списком телефонов:
program list_tel;{создание списка телефонов}
uses CRT;
type refer=^info; {тип - Указатель на запись}
info= record name,adr,tel:string;
next:refer
end;
var first,p,pp:refer; {указатели на начало списка и текущие}
s:string;
procedure write_list(start:refer);{вывод всех элементов списка}
begin repeat with start^ do
begin writeln(name);
writeln(adr);
writeln(tel);
end; start:=start^.next
until start=nil;
end{write_list};
BEGIN TextBackground(cyan);TextColor(white);ClrScr;
window(10,5,40,20);TextBackground(green); ClrScr;
new(first);p:=first;pp:=nil;
repeat if pp<>nil then {образование очередного элемента}
begin new(p);pp^.next:=p end;
with p^ do { заполнение элемента списка}
begin write('Ф.И.О.:'); readln(name);
write('Адрес:'); readln(adr);
write('Телефон:');readln(tel);
next:=nil
end; pp:=p;readln(s)
until (s=' ');
writeln('Список телефонов создан');
write_list(first);writeln('Вывод списка завершен');
END{list_tel}.
В этой программе элементы списка телефонов создаются циклически до тех пор, пока после заполнения полей очередного элемента списка будет введён символ-пробел. Последний элемент списка имеет нулевой указатель на следующий элемент. После создания списка процедура write_list распечатывает содержимое списка.
Для возврата освободившегося участка хип-памяти используется процедура dispose (p), где p – типизированный указатель, и процедура freemem(p,k), где к – размер в байтах освобождаемого участка для нетипизированного указателя p.
В Турбо Паскале имеется ещё ряд стандартных процедур и функций, работающих с указателями. Приведем некоторые из них.
Процедура mark(ptr) - запоминает в ptr текущее значение указателя HeapPtr.
Процедура release(ptr) - освобождение участка хип-памяти от текущего указателя, сохраненного процедурой mark(ptr), до конца хип-памяти.
Функции maxavail и memavail - возвращают размер (в байтах) наибольшего непрерывного участка или общего свободного пространства хип-памяти соответственно.
Функция addr(x) - возвращает указатель, содержащий адрес аргумента x (имя переменной, процедуры, функции). Эту же функцию выполняет операция @x.
