- •Лекция №10. Адреса и указатели. Списочные структуры данных
- •Содержание
- •Статически выделяемая память
- •Указатели
- •Описание указателей
- •Операции с указателями Определение адреса
- •Разыменование
- •Присваивания
- •Сравнения
- •Динамически распределяемая память
- •Динамическое выделение памяти Типизированные указатели
- •Нетипизированные указатели
- •Динамическое освобождение памяти Типизированные указатели
- •Нетипизированные указатели
- •Списочные структуры
- •Структура списков
- •Описание списков
- •Оперирование элементами списка Хранение списка
- •Обращение к элементам списка
- •Создание списков
- •Просмотр элементов списка
- •Удаление элементов списка
- •Перестройка списков
- •Примеры перестройки линейных списков
- •Реализация
Операции с указателями Определение адреса
Физический адрес любой переменной можно узнать при помощи стандартной функции addr(<имя_переменной>):<указатель> или унарной операции @<имя_переменной>.
В зависимости от значения директивы компилятора {$T}, результатом операции @ будет либо типизированный указатель (если установлено {$T+} ), тип которого будет определен в соответствии с типом использованной переменной, либо нетипизированный указатель pointer (если установлено {$T-} ).
Результат функции addr() совместим с указателями любых типов:
p:= addr(x); {x: real; p: ^byte)
Разыменование
Для того чтобы воспользоваться значением, хранящимся по некоторому адресу, необходимо его оттуда "извлечь". Унарная операция ^ называется разыменованием и записывается по следующему шаблону:
<имя_указателя>^
Результатом операции ^ является значение, хранящееся по указанному адресу. Тип этого значения будет определяться типом (типизированного) указателя. К нетипизированным указателям операцию разыменования применять нельзя.
Из-за вольностей, допускаемых процедурой addr(), при разыменовании порой могут возникнуть забавные ситуации. Например, в результате выполнения такой вот программы:
const a: array[1..3] of char ='ААА'; {код(А)=128 или 01000000}
var p: ^word;
begin p:= addr(a);
writeln(p^)
end
на экран будет выведено 32896, что в двоичной системе счисления выглядит как 01000000.01000000 (точкой помечена граница двух байтов). Иными словами, коды двух первых букв оказались слитыми в значение типа word.
Замечание: Операции @ и ^ являются взаимно обратными, то есть для любой переменной a и для любого типизированного указателя p верны следующие равенства:
@(p^)= p и (@a)^ = a
Присваивания
Для указателей действуют гораздо более жесткие правила совместимости типов, чем для обычных переменных. В операции присваивания могут фигурировать только указатели, адресующие переменные одинаковых типов данных. Нельзя, скажем, записать
p:= q; {p: ^integer; q: ^byte}
Обойти эти ограничения позволяет универсальность нетипизированного указателя pointer, совместимого с указателями любых типов:
{p:= ^integer; q: ^byte; t: pointer}
t:= q;
p:= t;
У указателей также существует свой "ноль", который означает, что указатель не указывает никуда:
p:= nil;
Замечание: Если указатель не хранит конкретного адреса (его значение не определено), то это вовсе не означает, что он никуда не указывает. Скорее всего, он все-таки указывает, но только в какую-нибудь неожиданную (хорошо, если не системную) область памяти.
Сравнения
Для указателей определены две операции сравнения: = и <>.
Две переменные, описанные как указатели на один и тот же тип данных, считаются совпадающими, если они указывают на одну и ту же область памяти.
Для разнотипных указателей сравнения невозможны: попытка записать
if p = q then writeln('yes'); {p: ^byte; q: ^integer}
вызовет ошибку уже на этапе компиляции.
Однако сравнивать типизированный и нетипизированный указатели можно.