
- •Лекция 13. Динамические структуры данных
- •8.1. Указатели
- •8.1.1. Типизированные и нетипизированные указатели
- •8.1.3. Операции над указателями
- •8.1.4. Действия с указателями
- •8.1.5. Операторы для указателей (pointer)
- •8.2. Динамические переменные
- •8.2.1. Динамические структуры данных
- •8.2.2. Указатели и ссылки
- •8.2.3. Линейные списки. Основные операции
8.2.2. Указатели и ссылки
Характерная особенность рекурсивных структур, которая отличает их от основных структур (массивов, записей, множеств), – их способность изменять размер. Поэтому для рекурсивно определенных структур невозможно установить фиксированный размер памяти, и поэтому компилятор не может приписать такой переменной определенного адреса. Для решения этой проблемы чаще всего применяется метод динамического распределения памяти, то есть выделения памяти для отдельных переменных в тот момент, когда они появляются во время выполнения программы, а не во время компиляции. Во время компиляции выделяется фиксированный объем памяти для хранения адреса динамической переменной, а не самой переменной.
Поскольку нам необходимо динамически создавать структуры из произвольного числа элементов, эти элементы нужно связывать друг с другом. Поэтому каждый элемент динамической структуры должен содержать один или несколько адресов тех переменных, с которыми он связан (на которые, как говорят, он указывает или ссылается).
Явное использование ссылок позволяет строить более разнообразные структуры, чем те, которые можно задать лишь с помощью рекурсивных определений. Следовательно, нужно ввести типы данных, значениями которых являются указатели (ссылки) на другие данные. Для этого используются такие обозначения:
type
TPtr=^TElement.
Здесь имеется ввиду, что значениями типа TPtr являются ссылки на переменные типа TElement. Стрелка ^ читается как «ссылка на». Существенно, что тип элементов, на которые ссылаются значения типа TPtr, задан в его определении. Это означает, что TPtr связан с TElement. Эта связь отличает ссылки в языках высокого уровня от адресов в языке ассемблера и является очень важным средством увеличения надежности программ. Пусть есть описания
type
Tp1=^TE1;
Tp2=^TE2;
…
var
p1: Tp1;
p2: Tp2
Тогда переменные p1 и p2 – разных типов, так как они ссылаются на переменные разных типов. И это несмотря на то, что и размер памяти, отводимой под p1 и p2, – одинаков, и одинакова сущность их значений: обе эти переменные содержат адреса памяти.
Для работы с динамическими структурами необходимы следующие основные операции:
создание переменной;
удаление переменной.
Значения ссылочных типов создаются всякий раз, когда динамически создается соответствующая переменная. Для динамического создания переменной служит стандартная процедура new. Если дана ссылочная переменная p типа TPtr, то оператор new(p) выделяет память для переменой типа TElement, создает ссылку типа TPtr на эту вновь созданную переменную и присваивает значение этой ссылки (адрес новой переменной) p. Поскольку собственно значение адреса программиста, как правило, не интересует, ссылка на переменную обозначается стрелкой.
Рис. 8.1.
Сама ссылка обозначается как p, значение ее – адрес динамически созданной переменной. Динамически созданная переменная своего имени не имеет, но к ней можно обратиться через ее ссылку, и тогда p^ – динамически созданная переменная.
К множеству значений типа TPtr добавляется еще одно значение – Nil, (от англ. ничего, ноль) которое не ссылается ни на какой элемент. Элемент, содержащий такое значение, является конечным элементом структуры.
Операции, допустимые над ссылочными переменными:
Присваивание (p:=q). Как и для переменных других типов данных, присваивать одной ссылочной переменной можно значение другой ссылочной переменной только если эти переменные одного и того же типа. Значение Nil можно присваивать любой ссылочной переменной.
Сравнение (if (p<>Nil) and (p=q) then …). Две переменные ссылочного типа можно сравнивать только на равенство/неравенство. Допустимо сравнение со значением Nil.
Разыменование (p^). Это унарная операция, ее операнд (p) – указатель, результат – данные по адресу, заданные указателем.
Оператор @, используется для того, чтобы взять адрес переменной, функции и процедуры. Оператор @ возвращает адрес переменной, функции, процедуры или метода; т.е. @ создает указатель на свой операнд. Следующие правила относятся к операции @:
Если X — переменная, @X возвращает адрес X. Тип @X – Pointer, если выполнена директива компилятора {$T-} (по умолчанию). В состояние {$T+}, @X — тип ^T, где T — тип X.
Если F — функция или процедура, @F возвращает точку входа в F. Тип @F — всегда Pointer. При применении операции @ к методу перед идентификатором метода должна идти ссылка на имя класса. Например, @TMyClass.MyShow.