Все переменные, с которыми мы имели дело, известны уже на этапе компиляции. Однако во многих задачах нужны переменные, которые по мере необходимости можно создавать и удалять во время выполнения программы. С этой целью в языке Delphi организована поддержка так называемых указателей, для которых введен специальный тип данных Pointer.
1. Любая переменная в памяти компьютера имеет адрес. Переменные, значением которых является адрес в памяти (в частности, адреса других переменных), принято называть указателями. Указатели объявляются точно так же, как и обычные переменные:
Var P1: Pointer; // переменная-указатель нетипизированный N: Integer; // целочисленная переменная P: ^Integer; // переменная - указатель типизированный |
Переменная P занимает 4 байта и может содержать адрес любого участка памяти, указывая на байты со значениями любых типов данных: Integer, Real, string, record, array и других. Если указатель указывает на переменную структрированного типа (массив, запись…), то этот указатель содержит адрес первого элемента этой структуры.
Чтобы инициализировать переменную P, присвоим ей адрес переменной N. Это можно сделать двумя эквивалентными способами (для P и P1):
P := Addr(N); // с помощью вызова встроенной функции Addr |
или
P1 := @N; // с помощью оператора @ |
В дальнейшем мы будем использовать более краткий и удобный второй способ.
2. Если некоторая переменная P содержит адрес другой переменной N, то говорят, что P указывает на N. Графически это обозначается стрелкой, проведенной из P в N (рисунок выполнен в предположении, что N имеет значение 10):
Рисунок. Графическое изображение указателя P на переменную N
3. Теперь мы можем изменить значение переменной N, не прибегая к идентификатору N. Для этого слева от оператора присваивания запишем не N, а P вместе с символом ^:
P^ := 10; // Здесь не нужно приведение типаInteger(P1^) := 10; // Выполонено приведение типа |
Символ ^, записанный после имени указателя, называется оператором доступа по адресу. В данном примере переменной, расположенной по адресу, хранящемуся в P, присваивается значение 10. Так как в переменную P мы предварительно занесли адрес N, данное присваивание приводит к такому же результату, что и
4. Отдельно обратим внимание на символ ^.
^typeName; // Для описания типизированного указателяpointer^; // Оператор доступа по адресу |
5. При записи P: ^Integer переменная P по-прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных. В данном случае указатель P называют типизированным, в отличие от переменных типа Pointer, которые называют нетипизированнымиуказателями. При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип данных, а переменные-указатели просто объявлять с этим типом.
type pointerTypeName = ^type; |
Чтобы отличать указательные типы данных от других типов, будем назначать им идентификаторы, начинающиеся с буквы P (от слова Pointer). Объявление указательного типа данных является единственным способом введения указателей на составные переменные, такие как массивы, записи, множества и другие. Например, объявление типа данных для создания указателя на некоторую запись TPerson может выглядеть так:
Type PPerson = ^TPerson; TPerson = record FirstName: string[20]; LastName: string[20]; BirthYear: Integer; end; var P: PPerson; |
Переменная P, описанная с типом данных PPerson, является указателем и может содержать адрес любой переменной типа TPerson. Впредь все указатели мы будем вводить через соответствующие указательные типы данных. Типом Pointer будем пользоваться лишь тогда, когда это действительно необходимо или оправдано.