Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОАиП Лекции ч.2 ПОИТ.pdf
Скачиваний:
43
Добавлен:
24.02.2016
Размер:
1.08 Mб
Скачать

РАЗДЕЛ 6. ССЫЛОЧНЫЙ ТИП (ТИП УКАЗАТЕЛЬ)

6.1. Общие сведения

Впредыдущих разделах рассматривались автоматические переменные

это переменные, порождаемые непосредственно перед выполнением программы или подпрограммы и существующие в течение всего времени ее выполнения. Объем машинной памяти, необходимой для их размещения, не изменяется по ходу выполнения программы. Автоматические переменные порождаются с помощью соответствующих им описаний в разделе объявлений. Например,

Var

X: Integer;

Здесь X – автоматическая переменная.

Место в памяти для хранения значений автоматических переменных выделяется на этапе трансляции. Обращение к этим переменным в программе осуществляется с помощью указания их имен.

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

Для работы с динамическими переменными в языке Паскаль предусмотрен ссылочный тип (тип указатель). Значением этого типа является ссылка на какой-либо элемент программы. По данной ссылке осуществляется непосредственный доступ к этому элементу. В качестве ссылки используется адрес соответствующего элемента в памяти машины.

Синтаксическая диаграмма задания типа указатель имеет вид, который представляет рисунок 6.1.

На данной диаграмме <Идентификатор_типа> - это тип динамически размещаемой переменной. В качестве типа может быть использовано имя стандартного или описанного отдельно типа.

Из диаграммы видно, что в Паскале имеется два вида указателей

указатель на элемент некоторого типа (верхнее ребро синтаксической диаграммы) и указатель, не связанный с типом (Pointer).

108

<Тип_указатель>::=

^ <Идентификатор_типа>

Pointer

Рисунок 6.1 - Синтаксическая диаграмма задания типа указатель

Пример 6.1.

Использование указателей при объявлении программных элементов.

Type

Mas = Array [1..10] Of Integer;

Admas = ^Mas;

{Admas – тип указателя на массив типа Mas}

P1 = ^Integer;

{P1 – тип указателя на тип Integer}

Var

 

P: ^Integer;

{в указателе P будет храниться адрес

Q: ^Char;

динамической переменной целого типа}

{в указателе Q будет храниться адрес

Pp: P1;

динамической переменной типа Char}

{в указателе Pр будет храниться адрес

Pt: Pointer;

динамической переменной целого типа}

{в указателе Pt может храниться адрес

 

динамической переменной произвольного

Adrm: Admas;

типа}

{в указателе Adrm будет храниться адрес

 

динамического массива типа Mas}

Указатели являются автоматическими переменными ссылочного типа. Динамические переменные не объявляются в программе. Поэтому

единственным средством доступа к ним являются указатели.

Связь указателя Р с динамической переменной схематически отображает рисунок 6.2.

109

P

Aдрес Переменная

Рисунок 6.2 - Связь указателя Р с динамической переменной

Указатель занимает четыре байта памяти.

Иногда необходимо в качестве значения указателя принять пустую ссылку – ссылку, которая не связывает с данным указателем никакой динамической переменной. Для этого служит предопределенная константа Nil. Константа Nil является совместимой по типу с любым типом указателя.

Например, для указателей, объявленных в примере 6.1, можно записать:

Pt := Nil;

P := Nil;

Q := Nil;

Adrm := Nil;

Над значениями ссылочного типа в Паскале нет операций, которые давали бы результат этого же типа.

Над значениями ссылочного типа определены только присваивание и две операции сравнения: = и <>.

Два значения ссылочного типа равны, если они оба есть Nil либо указывают на одну и ту же переменную. Во всех остальных случаях имеет место неравенство.

При присваивании значения ссылочной переменной в правой части оператора присваивания используется ссылочное выражение того же типа, что и тип переменной в левой части. В качестве ссылочного выражения может использоваться:

-пустая ссылка Nil;

-ссылочная переменная;

-ссылочная функция (функция, значением которой является ссылка). Например, встроенная функция Addr(X) – возвращает адрес элемента X.

Здесь X – любая переменная, процедура или функция. Результат функции Addr(X) совместим по присваиванию со всеми типами “указатель”. Поэтому можно, например, написать такие операторы присваивания (для переменных типа указатель, объявленных в примере 6.1):

Pt := Addr (А); {А может иметь любой тип}

110

P := Addr (В);

{В должна иметь тип Integer}

Объявление ссылочной переменной в разделе Var не порождает самой динамической переменной. По этому объявлению транслятор только отводит место в памяти для размещения ссылки (адреса динамической переменной).

6.2.Методы работы с динамическими переменными

В Паскале существует три метода работы с динамическими переменными:

1)с помощью процедур New и Dispose;

2)с помощью процедур Getmem и Freemem;

3)с помощью процедур Mark и Release.

6.2.1. Процедуры New и Dispose

Для порождения динамической переменной используется стандартная процедура New(P). Здесь P – это ссылочная переменная любого типа.

Данная процедура размещает динамическую переменную типа указателя. Переменной выделяется память, достаточная для хранения наиболее длинного значения типа указателя. Адрес начала данного места памяти присваивается заданной ссылочной переменной P.

Пример 6.2.

Использование процедуры New.

Var

 

P: ^Integer;

{Р – указатель на динамическую переменную типа

...

Integer}

Begin

 

New (P);

 

...

 

В данном примере процедура New(P) пораждает динамическую переменную типа Integer, адрес этой переменной присваивается указателю P.

111

Для динамической переменной процедура New играет ту же роль, что раздел описаний Var для автоматической переменной. Никакого значения процедура New динамической переменной не присваивает.

Для обращения к динамической переменной используется переменная с указателем, синтаксис которой представляет рисунок 6.3.

<Переменная_с_указателем>::=

<Ссылочная_переменная>^

Рисунок 6.3 – Синтаксическая диаграмма переменной с указателем

Символ ^ называется знаком карата.

Например, (в применении к примеру 6.2) обращение к динамической переменной, связанной с указателем Р, осуществляется с помощью переменной с указателем:

P^ := 15;

{Р^ - переменная с указателем; динамической

 

переменной типа Integer, связанной с указателем P,

 

присвоено значение 15}

Над переменными с указателями определены любые операции, допустимые для переменных того типа, что и тип динамической переменной.

Пример 6.3.

Пусть имеются объявления, сделанные в примерах 1 и 2. Операции над динамическими переменными.

Var

...

X: Integer; Begin

New (P);

P^ := 15;

...

X := X + P^;

112

P^ := P^ Mod 5;

Adrm^[P^ + 2] := 14;

Пример 6.4.

Организация массивов указателей. Пусть имеются объявления, сделанные в примере 6.1.

Var

...

А: Array[1..20] Of P1;

{A – массив ссылок на динамические

Begin

переменные типа Integer}

 

...

 

For I:=1 To 20 Do

 

Begin

 

New (A[I]);

{Порождение динамической переменной,

A[I]^ := I;

связанной с указателем A[I]}

{Обращение к динамической переменной}

End

 

...

 

В данном примере каждая из ссылок A[I] обеспечивает доступ к соответствующей переменной с указателем A[I]^.

Идентификатор типа в определении ссылочного типа может быть объявлен как до его использования, так и после (в отличие от всех других типов идентификаторов).

Например, в примере 6.1 идентификатор Mas был объявлен до его использования в типе указателя Admas. Возможно и обратное объявление:

Type

Admas = ^Mas;

Mas = Array [1..10] Of Integer;

Пример 6.5.

Работа с указателями и с динамическими переменными.

Var

 

X, Y: ^Integer;

{X, Y – указатели на динамические переменные

113

Begin

типа Integer}

 

New (X);

{Порождение динамической переменной типа

New (Y);

Integer, адрес которой помещен в Х}

{Порождение динамической переменной типа

X^ := 5;

Integer, адрес которой помещен в Y}

{В динамическую переменную, связанную

Y^ := 10;

с указателем Х, занесено значение 5}

{В динамическую переменную, связанную

X := Y;

с указателем Y, занесено значение 10}

{Указателю Х присвоено значение указателя Y}

Y := Nil;

{Указателю Y присвоено значение пустой ссылки}

Схематично действия операторов программы представляет рисунок 6.4.

X^, Y^ -

X^ := 5;

 

переменные с

 

указателем

Y^ := 10;

 

Оператор

присваивания X := Y; над ссылками

 

Адрес

 

Динамическая

 

 

 

переменная

X

*

5

В результате

 

 

 

 

*

10

выполнения

Y

оператора X := Y

 

 

 

на данную

 

 

 

динамическую

 

 

 

переменную не

X

*

5

указывает ни одна

 

 

 

ссылка, то есть

 

 

 

она стала

Y

*

10

недоступной для

 

 

 

программы

Ссылка Y не

 

X

 

Y := Nil;

*

ссылается ни

на один

 

 

 

 

 

 

динамический

 

Y

 

 

*

объект

 

 

 

 

 

 

 

 

5

10

Рисунок 6.4 – Действия операторов программы по примеру 6.5

114

Если вместо оператора присваивания X := Y (см. рисунок 6.4) будет выполнен оператор X^:=Y^, то результат его выполнения будет выглядеть так, как изображает рисунок 6.5.

X^ := Y^;

X

*

 

 

10

 

 

 

 

 

 

 

 

 

Y * 10

Рисунок 6.5 – Результат выполнения оператора X^ := Y^

Переменная типа указатель может являться параметром функции Ord. Результатом этой функции является целое значение, равное адресу динамической переменной, на которую ссылается данный указатель. Обратное преобразование (Integer Æ указатель не допускается).

В примере 6.5 после выполнения оператора

X := Y

динамическая переменная со значением 5 стала недоступна программе. Однако она продолжает занимать отведенное ей место в памяти. Данное место не может использоваться для хранения других динамических переменных.

Для уничтожения динамических переменных (освобождения занимаемой ими памяти) используется стандартная процедура Dispose (P).

В результате выполнения процедуры Dispose(P) динамическая переменная, связанная с указателем P, уничтожается, занимаемая ею память считается свободной, значение P считается неопределенным (равным Nil).

Процедура Dispose уничтожает только саму динамическую переменную, но не указатель на нее.

Пример 6.6.

Освобождение памяти, занятой динамической переменной X^ (модифицированный фрагмент программы, приведенной в предыдущем примере 6.5).

...

New (X);

115

New (Y);

X^ := 5;

Y^ := 10;

Dispose (X); {Освобождается область памяти, занятая динамической переменной X^}

X := Y;

Y := Nil;

...

Пример 6.7.

В заданном тексте найти слово максимальной длины. Слова разделяются одним пробелом. Признак конца текста – точка. Максимальная длина слова – 50 символов.

Program Slovo;

 

Type

 

Mas = Array [1..50] Of Char;

 

Ykaz = ^Mas;

{Тип указателя на массив}

Var

 

Vspom, Rez, Tek: Ykaz;

{Переменная типа указателя на массив}

Max, I: Integer;

 

Byk: Char;

 

Begin

 

Max := 0;

 

I :=0;

 

New (Tek);

{Порождение динамической переменной,

 

предназначенной для хранения текущего

{1} New (Rez);

слова текста}

{Порождение динамической переменной,}

 

предназначенной для хранения

Repeat

максимального из уже прочитанных слов}

 

Read (Byk);

If (Byk <> ‘ ‘) And (Byk <> ‘.’)

Then

 

Begin

 

I := I+1;

{I – счетчик длины текущего слова}

Tek^[I] := Byk

 

End

 

Else

 

If I > Max Then

 

Begin

 

116

 

 

Max := I;

{2}

Vspom := Rez;

{3}

Rez := Tek;

{4}

Tek := Vspom;

 

I := 0

End

Until Byk = ‘.’;

Dispose (Vspom);

Dispose (Tek);

Writeln;

For I := 1 To Max Do

Write (Rez^[I]);

Dispose (Rez)

End.

В данном примере выполнение оператора {3} не требует пересылки компонент массива типа Mas из динамической переменной Tek^ в динамическую переменную Rez^. В указатель Rez заносится адрес динамического массива Tek^. Таким образом, указатель Rez “перебрасывается” на другой массив. В указатель Tek с помощью операторов {2}, {4} заносится адрес предыдущего динамического массива максимальной длины. Фактически массивы как бы меняются местами, но физически этого не требуется.

На следующем цикле чтения в массив Tek^ будет заноситься очередное текущее слово.

Время выполнения программы, приведенной в данном примере, существенно уменьшается по сравнению с аналогичной программой, использующей только автоматические переменные, за счет отсутствия физической пересылки массивов.

Пояснения к программе, приведенной в данном примере, схематически представляют рисунок 6.6 – рисунок 6.9.

117

После выполнения оператора {1}:

Tek

Rez

Vspom

Рисунок 6.6 – Результат выполнения оператора New (Rez)

После выполнения оператора {2}:

Tek

Rez

Vspom

Рисунок 6.7 – Результат выполнения оператора Vspom := Rez

118

После выполнения оператора {3}:

Tek

Rez

Vspom

Рисунок 6.8 – Результат выполнения оператора Rez := Tek

После выполнения оператора {4}:

Tek

Rez

Vspom

Рисунок 6.9 – Результат выполнения оператора Tek := Vspom

6.2.2. Процедуры Getmem и Freemem

Использование процедур Getmem и Freemem – это второй метод работы с динамическими переменными.

Вызов процедуры Getmem имеет следующий формат:

Getmem (P, Size);

119

Здесь P - указатель любого типа, Size – выражение типа Word. Процедура Getmem выделяет из Heap-области (хип-область, область кучи,

область динамической памяти) блок памяти заданного размера Size (порождает динамическую переменную размера Size) и адрес этого блока присваивает ссылочной переменной P. Максимальный размер блока – 65521байт (64К байта

– $F (смещение)).

Для освобождения блока памяти, занимаемого динамической переменной, используется процедура Freemem:

Freemem (P, Size)

Здесь P – указатель произвольного типа, которому предварительно было присвоено значение процедурой Getmem или оператором присваивания; значение Size должно точно соответствовать размеру переменной, размещенной до этого процедурой Getmem.

Процедура Freemem уничтожает динамическую переменную P^, занимаемая ею память считается свободной, значение P становится неопределенным.

Таким образом, действие процедур Getmem/Freemem аналогично действию процедур New/Dispose – порождается и уничтожается динамическая переменная.

Пример 6.8.

Эквивалентные фрагменты программ, использующие процедуры

Getmem/Freemem и New/Dispose.

Type

T = <Тип>; Var

P: ^T;

Begin

New (P);

 

Getmem (P, Sizeof(T));

Эквивалентны

Dispose (P);

 

Freemem (P, Sizeof(T));

В общем случае процедуры Getmem/Freemem дают большую свободу действий по сравнению с процедурами New/Dispose, так как позволяют работать без привязки к конкретному типу переменных.

120

Пример 6.9.

Использование процедур Getmem/Freemem. Различная интерпретация выделенной области памяти.

Type

Zap = Record

X, Y: Integer; End;

St = String [20]; Var

P: Pointer; Begin

Getmem (P, 100); {Выделение блока памяти размером 100 байт, адрес этого блока присвоен ссылочной переменной Р}

Zap(P^).X := 124; {Интерпретация выделенной области как записи – блоку памяти, связанному с указателем Р, присваивается тип Zap}

Zap(P^).Y := 47; {Интерпретация выделенной области как записи}

Real(P^) := 0.213; {Интерпретация выделенной области как Real}

St(P^) := ’ПРОГРАММИРОВАНИЕ’; {Интерпретация выделенной области как строки}

Freemem (P, 100); {Освобождение блока памяти размером 100 байт}

6.2.3. Процедуры Mark и Release

Процедуры Mark (пометить) и Release (освободить) позволяют повысить эффективность работы с динамической памятью.

Обращение к процедуре Mark имеет следующий вид:

Mark (P1);

Здесь P1 – переменная типа указатель, ссылающаяся на переменную какого угодно типа.

Процедура Mark присваивает своему параметру P1 адрес начала свободной области динамической памяти.

Помеченная область с помощью процедур New или Getmem используется для размещения отдельных динамических переменных.

121

Когда данные динамические переменные окажутся ненужными, занимаемую ими память можно освободить. Для этого используется процедура Release с тем же параметром, что и в предыдущем обращении к процедуре

Mark:

Release (P1);

Процедура Release освобождает память, начиная с адреса, полученного в результате выполнения последней процедуры Mark. Значение указателя P1 после выполнения процедуры Release равно Nil.

Схематически представление работы с динамической памятью без и с использованием процедур Mark/Release иллюстрирует рисунок 6.10.

 

 

 

 

 

 

V1^

 

V1^

 

New (Vi)

 

 

 

 

V2^

 

V2^

Mark (V3)

 

 

 

 

 

 

Dispose (V3)

V3^

 

V4^

New (Vi)

 

 

 

 

 

V4^

 

V5^

 

 

 

 

 

 

 

Release (V3) –

 

 

Удаляет только

 

освобождает всю

 

 

 

VN^

 

VM^

область динамической

динамическую

 

 

 

 

памяти, начиная с

переменную V3^

 

 

 

 

 

 

 

 

 

метки V3

 

 

 

 

 

 

 

 

 

Рисунок 6.10 - Схематическое представление работы с динамической памятью

без и с использованием процедур Mark/Release

Параметр процедуры Mark нельзя использовать в качестве параметра процедур New, Getmem и его нельзя изменять (по крайней мере до его использования в соответствующей процедуре Release).

Возможна вложенность процедур Mark. Тогда каждой процедуре Mark соответствует своя процедура Release.

Пример 6.10.

Использование вложенных процедур Mark/Release.

122

Var

P1, P2: Pointer; P, Q, X: ^Integer;

Begin

Mark (P1);

New (P);

New (Q);

 

Внешние

Mark (P2);

Вложенные

процедуры

Mark/Release

New (X);

процедуры

 

Mark/Release

 

Release (P2);

 

 

 

 

Release (P1);

 

 

 

 

Водной и той же программе не рекомендуется использовать одновременно процедуры Dispose или Freemem и Mark/Release, так как это может привести к непредсказуемым результатам (механизмы управления свободной памятью для процедур Dispose, Freemem и Mark/Release существенно различаются).

Вобщем случае вместо любой автоматической переменной в программе можно использовать динамическую переменную.

Основное достоинство использования динамических переменных

экономия памяти компьютера.

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

удлиняется текст программы за счет использования процедур

New/Dispose, Mark/Release, Getmem/Freemem;

снижается наглядность программы за счет использования переменной с указателем (динамической переменной);

снижается быстродействие программы за счет необходимости во время

еевыполнения размещать порожденную динамическую переменную в памяти машины, за счет формирования значения соответствующего указателя и за счет усложнения доступа к значению динамической переменной.

123