Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгор_ТХТК_пособие.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
1.6 Mб
Скачать

10.2 Ссылочные переменные

Описание ссылочного типа выглядит следующим образом:

Туре Ptr = ^t,

где t - стандартный или заранее описанный тип данных, называемый базовым типом. Сами адреса будут храниться в ссылочных переменных, которые описываются обычным образом, например, Var Р : Ptr. Такие переменные для хранения адресов динамической памяти называются ссылками или указателями.

Например:

Туре

Pint = ^Integer;

W = array [1..20] of Real;

p = ^W;

Var

N : Pint;

U : p;

Под переменную N и переменную U будет отведено по 4 байта памяти в сегменте данных. Переменные будут содержать адрес какой-либо ячейки памяти, расположенной в динамической области. Но прежде, чем переменная ссылочного типа примет значение, необходимо в ходе выполнения программы выполнить специальную процедуру. Ссылка представляет собой адрес начала, т.е. первой ячейки, некоторого места в памяти, выделенного для объекта базового типа. Переменная U будет содержать адрес первой ячейки, выделенной под массив W в динамической области памяти.

В объявлениях ссылочных типов после символа "^" может стоять только простое имя типа. В случае сложных имен используется переопределение типов, как в приведенном примере.

Указатели, связанные с адресами значений конкретных базовых типов, называются типизированными. N и U - типизированные указатели. В Турбо Паскале можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Такие указатели называются нетипизиро-ванными. Описание нетипизированных указателей осуществляется с помощью служебного слова Pointer, например, Var P : Pointer.

Адрес хранится как два слова: одно из них определяет сегмент, а другое - смещение. Значение указателя не может быть в явном виде выведено на экран или печать. Его надо предварительно расшифровать. Для работы с указателями вводится специальный набор функций:

Addr(X)

Ссылка на начало объекта X в памяти (тип Pointer) Аналогом этой функции является операция @Х

Seg(X)

Сегмент, в котором хранится объект X (тип Word)

Ofs(X)

Смещение в сегменте для объекта X (тип Word)

Ptr(S,O:Word) Pointer

Ссылка на место в памяти, заданное значениями смещения О и сегмента S (тип Pointer)

SizeOf(x)

Размер объекта X в байтах

Так как значение указателя состоит из двух слов (Word), хранящих сегмент и смещение, можно вывести их в отдельности, используя функции Seg и Ofs:

Writeln('Сегмент ', Seg(p), ' смещение ', Ofs(p));

Указатели могут обмениваться значениями через оператор присваивания. Типизированному указателю можно присвоить значение либо указателя того же типа, что и он сам, либо нетипизированного указателя. Если, например,

Var

Р1, Р2 : ^Integer;

РЗ : ^Real;

РР : Pointer;

то присваивание Р1 := Р2 вполне допустимо, в то время как Р1 := РЗ запрещено, поскольку Р1 и РЗ указывают на разные типы данных. Это ограничение не распространяется на нетипизированные указатели. Можно записать РР := РЗ; Р1 := РР и достичь нужного результата. Присутствие в программе таких переприсвоений говорит о том, что программист делает это осознано и в программе действительно нужны такие действия. Указателю можно присвоить значение Nil. Nil - это предопределенная константа типа Pointer, соответствующая адресу 0000:0000 (пустая ссылка). Если указателю присвоено значение Nil, то этот указатель ни на какие данные не ссылается.

Указатели могут сравниваться с помощью операций отношения = или <> (не равно). Сравнение для указателей - ненадежная операция. Если два указателя содержат один и тот же адрес в памяти, но записанный в них разными способами, то они считаются различными. Зато можно проверить, ссылается ли указатель р на что-нибудь или нет путем сравнения р <> Nil.

Содержимое ячейки доступно через имя указателя. Чтобы обратиться к данным, находящимся по адресу, содержащемуся в указателе, используется символ "^", который ставится сразу после имени ссылочной переменной. Эта операция называется операцией разыменования. Суть ее состоит в переходе от ссылочной переменной к значению, на которое она указывает. Пусть имеется следующее описание:

Var

a, b: ^Real;

тогда в программе с переменными а^ и b^ допустимы все действия, что и с любыми переменными типа Real, например,

а^ := b^;

b^ := sin(a"); и т.д.

Память под любую динамически размещаемую переменную выделяется процедурой New(p). Только после выполнения процедуры New имеет смысл обращаться к ссылочным переменным. Параметром обращения к этой процедуре является типизированный указатель. В результате обращения указатель приобретает значение, соответствующее адресу, начиная с которого, можно разместить данные. Это адрес динамической области данных или кучи. Начало кучи хранится в стандартной переменной Heaporg, конец - в переменной Heapend. Текущую границу незанятой динамической памяти указывает указатель Heapptr. Если Var I^ integer, то после выполнения New(i) указатель i приобретет значение, которое перед этим имел указатель кучи Heapptr, а сам Heapptr увеличит свое значение на 2, так как длина внутреннего представления типа Integer, с которым связан указатель i, составляет 2 байта. Надо понимать, что на самом деле механизм выделения памяти сложнее, но для нас важно понять принципиальную схему (рис. 2).

Рисунок 10.2 - Расположение кучи в памяти ПК

Динамическую память можно не только забирать из кучи, но и возвращать обратно. Для этого используется процедура Dispose. Dispose(i) для предыдущего примера вернет в кучу 2 байта. Процедура Dispose не изменяет значение указателя, а лишь возвращает в кучу память, ранее связанную с этим указателем.

Проанализируем результаты вывода в следующей программе:

Var

p1, ip : ^integer;

Begin

{1}Writeln(ip^,' ',p1^);

{Выводятся значения, размещенные в динамической памяти по адресу Heapptr)

new(ip); new(p1);

{2}Writeln(ip^,' ',p1^);

{Выводятся значения, размещенные в динамической памяти по адресу Heapptr}

ip^ := 7;

р1^ := 20;

{3}Writeln(ip^,' ',p1^); {7 20}

ip := р1;

{4}Writeln(ip^,' ',p1^); {20 20}

dispose(ip);

{dispose(p1);}

{Использовать нельзя, так как р1 и ip указывают на одну и ту же ячейку памяти}

{5}Writeln(ip^,' ',p1^); {20 20}

ip := nil;

{6}Writeln(ip^,' ',p1^); {Случайное значение и 20}

End.

Несмотря на то, что первый оператор вывода предшествует процедуре New, ошибки не произойдет. Однако за указателями ip и р1 ячейки еще не закреплены и могут быть использованы другими динамическими переменными. Первый оператор является некорректным: работать с динамическими переменными можно только после выполнения процедуры New. Второй оператор Writeln выведет ту информацию, которая на момент начала выполнения программы расположена в ячейках с адресом Heapptr и Heapptr + 2. Следующим оператором в эти ячейки будет записана информация: 7 и 20 соответственно. Третий оператор вывода выдаст эти значения. Значение указателя ip изменилось после выполнения присваивания ip := р1. Теперь оба указателя указывают на одну и ту же ячейку памяти, в которой хранится ,, число 20. К числу 7 нет доступа. Это значение теперь пассивно занимает , память, т.е. 7 превратилось в мусор. Освободить память можно только для одного указателя ip или р1, так как оба эти указателя указывают на одну и ту < же ячейку памяти. Пятый оператор Writeln выведет те же значения 20 и 20, но это лучше рассматривать как случайность. После выполнения процедуры Dispose(ip) значения ссылок ip и р1 не определено, как и значения ip" и р1А. Выполнение шестого оператора Writeln также не приведет к ошибке, хотя его присутствие не имеет никакого смысла. Приведем еще один пример.

Type

Plnteger = ^Integer;

Var

SomeNumber: Integer;

Begin

SomeNumber := 17;{присвоить SomeNumber 17}

SomeAddress := @SomeNumber; {SomeAddress указывает на SomeNumber}

Writeln(SomeNumber); {напечатать 17}

{Writeln(SomeAddress); не допускается; указатели печатать нельзя}

Writeln(SomeAddress^); {напечатать 17}

AnotherAddress := SomeAddress;{также указывает на SomeNumber}

AnotehrAddress^ := 99; {новое значение для SomeNumber}

Writeln(SomeNumber);{Напечатать 99} End.

Перед использованием указателей им всегда нужно присваивать значения. Если разыменовывается указатель, которому еще не присвоено значение, то считанные из него данные могут представлять собой случайные биты, а присваивание значения указываемому элементу может затереть другие данные, программу или даже операционную систему. Чтобы избежать разыменования указателей, которые не указывают на что-либо значащее, нужен некоторый способ информирования о том, что указатель недопустим. В Паскале предусмотрено зарезервированное слово Nil, которое можно использовать в качестве содержательного значения указателей, которые в данный момент ни на что не указывают. Указатель Nil является допустимым, но ни с чем не связанным. Перед разыменованием указателя нужно убедиться, что он отличен от Nil (не пуст).