Организация и функционирование компьютеров
bf1271d8

Указатели


Перейдем теперь к механизму использования указателей в языке Турбо?Паскаль. Различают нетипизированные и типизированные указатели. Нетипизированный указатель объявляется при помощи предопределенного типа pointer. Значение нетипизированного указателя представляет собой адрес в оперативной памяти. Значение типизированного указателя также представляет собой адрес, но по этому адресу обязано быть размещено данное только определенного типа. Для оперирования с типизированным указателем используется символ “^” (крышка). В разделе объявлений переменных типизированный указатель описывается следующим образом:

<имя переменной-указателя> : ^<имя типа значения указателя>

Например, указатель типа ^integer

ссылается на переменную целого типа, а указатель типа ^rec ссылается на запись типа rec, определенную программистом в разделе типов.

Для того, чтобы значение по адресу, хранящемуся в указателе, использовать в выражении, нужно записать крышку после имени указателя: если переменная n имеет значение 1, а переменная p указывает на n, то выражение p^ равно 1. Используя указатель, можно также присвоить значение по адресу, указанному в указателе: в вышеприведенном примере оператор  p^:=5  законен и после него переменная n примет значение 5. Общее правило можно сформулировать следующим образом: если в текущий момент указатель ссылается на некоторую переменную, то использование имени указателя с крышкой после него тождественно использованию имени переменной. Преимущество в использовании указателей заключается в том, что меняя содержимое указателя, одно и то же действие можно сделать с различными переменными и даже с непоименованными значениями.

В Турбо?Паскале имеется унарная операция @, которая присваивает указателю адрес переменной, являющейся операндом операции: p := @n. После выполнения этой операции указатель p

будет указывать на переменную n. Следует отметить, что эта операция не требует, чтобы типы переменной и указателя были согласованы (этим Турбо?Паскаль отличается от стандартного Паскаля).
Однако лучше без необходимости не смешивать переменные различных типов.

Указателю в Паскале можно присвоить значение специальной предопре­деленной константы с именем Nil

(типа pointer), что означает, что указатель не имеет определенного значения и на него нельзя ссылаться.

Использование указателей в Паскале обусловлено двумя различными обстоятельствами. Во-первых, в некоторых задачах использование указателей позволяет унифицировать действия, которые без использования указателей кажутся сложными и запутанными. Сюда можно отнести обработку списков, очередей, деревьев, поддержку индексов массивов. Во-вторых, указатели необходимы при использовании динамической памяти. Зачастую обе указанные причины переплетаются.

Образование динамической переменной в Паскале осуществляется функцией new, параметром которой должен служить типизированный указатель. Функция new выделяет столько байтов, сколько требуется для динамической переменной заданного типа, а адрес начала выделенной памяти присваивает указателю. После этого динамической переменной можно присвоить какое-либо значение. Следует отметить, что единственный доступ к динамической памяти - это содержимое указателя. Достаточно изменить значение указателя - и динамическая переменная потеряна для программы навсегда. После того, как динамическая переменная перестанет быть нужна, ее можно уничтожить (а занятую ею память освободить для дальнейшего использования) функцией dispose. В качестве параметра функции dispose нужно передать указатель, ссылающийся на уничтожаемую динамическую переменную. После уничтожения динамической переменной указатель на нее теряет смысл, хотя значение адреса сохраняется. Хороший стиль программирования заключается том, что после освобождения памяти функцией dispose(p)



указателю p должно быть присвоено значение Nil.

Для пояснения оперирования с указателями приведем несколько примеров. Пусть переменные определены следующим образом:

type

    tp1 = array [1..10] of integer;

var

    n1,n2: integer;



    p1,p2: ^integer            

    p3: ^real;

    p4: ^tp1;

    p5: pointer;

После операторов

    n1 := 3;    n2 := 5;   p1 := @n1;   p2 := @n2;

указатель p1 будет содержать адрес переменной n1, а указатель p2 будет содержать адрес переменной n2. При этом будет выполняться  p1^=3, p2^=5. После присваивания p2:=p1 переменная p2 будет ссылаться на n1 и окажется p2^=3. После присваивания  p2^:=1  значение 1 занесется в переменную n1 и теперь окажется  p1^=p2^=n1=1, n2=5.

Пусть теперь мы хотим выделить память новым динамическим переменным:

new (p1);

new (p3);

new (p4);

В свободной оперативной памяти выделится 2,6 и 20 байтов для соответственно целой и вещественной переменной и массива из 10 целых чисел, на которые будут ссылаться указатели p1, p3 и p4. Этим переменным пока не присвоено никакого значения. Для занесения значений в динамические переменные мы можем написать примерно следующее

p1^ := 7;

p3^ := 2.75;

p4^[1] := 2;

p4^[8] :=6;

 и т.д. Если в дальнейшем присвоить  p2:=p1, то указатели p1 и p2 будут ссылаться на одно и то же значение. Если изменить указатель p1 оператором  p1:=@n1, то на динамическую переменную будет ссылаться только указатель p2. Если изменить также и его, например, p2:=@n2, то адрес динамической переменной, образованной функцией new, будет потерян навсегда и эту динамическую переменную нельзя будет ни использовать, ни закрыть, в то время как память ею будет занята. Для того, чтобы этого не происходило, надо было предварительно освободить память процедурой dispose(p2).

Указатели можно только присваивать и сравнивать на равенство и неравенство. При этом необходимо следить за согласованностью типов указателей:  оператор  p1:=p2  и сравнение  p1=p2  правильные, а оператор  p1:=p3  и сравнение  p1=p3  будут считаться синтаксической ошибкой. Однако можно выполнить присваивание в два приема с использованием нетипизированного указателя p5, а именно:  p5:=p3;  p1:=p5. Аналогично можно сравнивать любой типизированный с нетипизированным указателем.


Содержание раздела