Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Развёрнутые лекции по Паскалю.doc
Скачиваний:
8
Добавлен:
01.03.2025
Размер:
512.51 Кб
Скачать

24.Динамическая память

Вы наверное уже знаете, что когда вы активизируете программу,

то она считывается с диска и загружается в оперативную память. Та-

ким образом, запуск программы производится из оперативной памяти. В

ней же размещаются и другие данные, а именно:

->Часть памяти с младшими адресами занимает MS DOS. Это 50-60

килобайт, в зависимости от версии Dos.

->Далее хранится образ выполняемой программы - EXE-файл.

->Часть памяти занимают константы и переменные. Этот объем па-

мяти называется сегментом данных. Его размер не может превы-

шать 64 Кбайт.

Максимально возможный размер оперативной памяти у машин IBM PC

может быть 640 кбайт (1 байт=8 бит).

640 Кбайт ┌───────────────────┐

│ Свободный список │

├ - - - - - - - - - ┤<-FreePtr ─┐

│ Свободная память │ │

│-------------------│<-HeapPtr ├─ динамическая

│ Используемая куча│ │ память.

OvrHeapEnd->│-------------------│<-HeapOrg ─┘

│ Оверлейный буфер │

│ (если необходим) │

OvrHeapOrg->│-------------------│

│ Стек │ {для хранения локальных пе-}

SSeg:Ptr ->│-------------------│ {ременных - из подпрограмм}

│ Свободный стек │

SSeg:0000 ->│-------------------│

│ Переменные │ {сегмент данных - для хране-}

DSeg:0000 ->│-------------------│ {ния глобальных переменных}

│ │

│ *.EXE файл │

│ │

│-------------------│

│ 50-60 кбайт │

│ DOS │

└───────────────────┘

Рис.5.

25.ТИП УКАЗАТЕЛЬ

При работе с ЭВМ возникает естественный вопрос, а нельзя ли

использовать свободную память для решения своих задач. Для этих це-

лей существует аппарат указателей.

1.Как объявить переменную типа указатель?

->В ТР можно использовать три типа указателей, которые объяв-

ляются следующим образом:

а)Нетипизированные указатели:

Var A,Pl,Ptr1:Pointer;

б)Типизированные указатели:

Синтаксис этого типа данных

┌─ caret - символ вставки

<идентификатор переменной>:^<идентификатор типа>;

->Если нужен указатель стандартного типа, то:

Var I1:^Integer;

R5:^Real;

St:^String; {но нельзя объявить St:^String[12];}

Chi:^Char; {String[12] - не идентификатор типа}

->Если нужен указатель пользовательского типа, то возможны

различные варианты, но всегда <идентификатор типа> дол-

жен быть объявлен заранее.

Пример 70.

Type ArReal=Array [1..10] Of Real;

ArPtr=^ArReal;

Var X1:ArPtr;

Type ArReal=Array [1..10] Of Real;

Var X1:^ArReal; - Динамический массив.

Var X1:^Array[1..10] Of Real; {так нельзя объявить массив}

Var X2:Array [1..10] Of ^Real; - Массив динамических перемен-

ных.

в)Указатель ссылочного типа - это всегда запись минимум с дву-

мя полями.

Пример 71.

Type ListPtr=^List;

List=Record

St:String[22];

R:Real;

Arb:Array[1..6] Of Byte;

Next:ListPtr;

End;

Var A1,B,CV:ListPtr;

Отметим важную особенность таких указателей-одно из полей обя-

зательно имеет тип указателя.

Примечание:

Переменная типа указатель занимает в памяти 4 байта и хранит

адрес, начиная с которого начинают размещаться данные.

г)Указатель на строку с нуль-окончанием

В блоке System предопределен тип PChar следующим образом

Type PChar=^Char;

В режиме расширенного синтаксиса элементы строки с нуль-окон-

чанием совместимы по операции присваивания с типом PChar.

Пример 72.

->Var P:PChar;

Begin

P:='Hello...';

. . .

->Const PString:Array[0..14]='Hello...'#0;

Var P:PChar;

Begin

P:=@PString;

. . .

2.Как указатели используются в программе?

Указатель -это не простая переменная. Она используется двояко,

что обусловлено особенностями указателей, которые заключаются в том,

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

начиная с которого размещаются данные, а с другой стороны, указа-

тель является переменной объявленного типа, которая хранит данные

по адресу на который указывает указатель. В этом втором случае ука-

затель используется со значком ^. Переход от указателя (адреса) к

значению, которое хранится по этому адресу, называется операцией

разыменования. Таким образом, если в примере 63 X1 - это указатель,

то он хранит адрес в куче, а X1^ - это переменная типа массив, дан-

ные которого размещаются в динамической памяти, начиная с адреса,

который хранит указатель X1.

а)Для указателей допустимы только операции присваивания и сра-

внения. Указателю можно присвоить только содержимое другого указа-

теля, т.е. адрес, а также предопределенную константу - Nil (пустой

указатель типа Pointer, который указывает на адрес 0000:0000). При

этом надо учитывать тип указателя. Несколько указателей могут ука-

зывать на один адрес.

б)С данными указанного типа, которые хранятся по адресу, на

который указывает указатель можно выполнять все действия, которые

разрешены для этого типа данных.

Пример 73.

X1^[1]:=1.23;

R5^:=15.63;

St^:='Это значение переменной';

Ch1^:=#27;

X2[3]^:=5.4;

A1^.R:=3.3;

A1^.ArB[2]:=2;

в)Указатели могут использоваться и как типизированные констан-

ты, но их значением может быть Nil или адрес другой типизированной

константы типа указатель аналогичного типа.

Const Cr:^Real=Nil;

Cs:^String=Nil;

Ls:^String=@Cs; {адресное значение}

P:List=(St:'Указатель';R:22.4;ArB:(0,1,2,3,4,5);Next:nil);

Nil - Предопределенная типизированная константа типа Pointer,

соответствующая адресу 0000:0000.

3.Функции для работы с адресами и указателями

=>MemAvail:LongInt; - Возвращает сумму всех свободных участков в

динамической памяти.

=>MaxAvail:LongInt; - Возвращает размер наибольшего непрерывного

участка в динамической памяти.

=>Addr(X):Pointer; - Возвращает адрес указанного объекта, X - любая

переменная или идентификатор процедуры или фу-

нкции (@X), т.е. это указатель на объект.

=>Seg(X):Word; - Возвращает значение сегмента памяти с объектом X.

=>Ofs(X):Word; - Возвращает значение смещения внутри сегмента с объ-

ектом X.

=>Ptr(Seg,Ofs:Word):Pointer; - Преобразует адрес, заданный в виде

сегмента и смещения в значение типа

указатель.

=>Assigned(X):Boolean; - Возвращает True, если указатель или проце-

дура указывает на Nil.

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

Поскольку указатель указывает на адрес, по которому будут хра-

ниться данные, то недостаточно объявить в разделе описаний указа-

тель, необходимо в разделе операторов создать указатель, а после

окончания использования - уничтожить его.

=>GetMem(Var P:Pointer;Size:Word); - Создает новую динамическую пе-

ременную P заданного размера Size и

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

=>FreeMem(Var P:Pointer;Size:Word); - Освобождает Size байт в дина-

мической памяти, начиная с адреса, хранящегося в P.

Пример 74.

Var ScrPtr:Pointer;

Procedure GetScreen;

Begin

GetMem(ScrPtr,$1000);

Move(Mem[$B800:0],ScrPtr^,$1000);

End;

Procedure SetScreen;

Begin

Move(ScrPtr^,Mem[$B800:0],$1000);

FreeMem(ScrPtr,$1000);

ScrPtr:=Nil;

End;

Как использовать в основной программе?

GetScreen - Сохраняет копию экрана в динамической памяти.

SetScreen - Восстанавливает вид экрана.

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

Во-первых, необходимо отметить, что для типизированных указа-

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

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

лей.

Пример 75.

Type Item=Real;

ArItem=Array[1..1] Of Item;

ItemPtr=^ArItem;

Var ArPtr:ItemPtr;

N,I:Word;

{$R-} отключить проверку выхода индекса за границы диапазона

Begin

Write('Количество элементов в массиве=');

ReadLn(N);

GetMem(ArPtr,N*SizeOf(Item)); {создается указатель нужного}

For I:=1 to N Do ArPtr^[I]:=0; {размера, начиная с адреса, }

... {который хранится в ArPtr}

FreeMem(ArPtr,N*SizeOf(Item));

ArPtr:=Nil;

{$R+}

End.

Это пример создания динамического массива переменной длины.

Во-вторых, для типизированных указателей имеются специальные проце-

дуры создания и уничтожения динамических переменных.

=>New(Var P:<тип>); - Создает новую динамическую переменную P, раз-

мер которой определяется указанным типом, и

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

=>Dispose(Var P:<тип>); - Освобождает динамическую переменную, на-

чиная с адреса, указанного в P.

Пример 76.

Type St24=String[24];

Var P:^St24;

Begin

New(P);

P^:='Это значение указателя';

Dispose(P);

P:=Nil;

End.

Отметим, что в небольших программах, в которых нет повторного

обращения к динамическим переменным, их можно не уничтожать, но мы

с вами будем считать это ошибкой.

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

C помощью двух-трех указателей этого типа можно хранить в ди-

намической памяти массивы размером более 64 кбайт. Указатели ссыло-

чного типа организуют структуру в виде цепочки или очереди, где

один элемент помнит адрес другого и т.д. Для управления такими стру-

ктурами предусмотрено две процедуры:

=>Mark(Var P:<тип>,включая Pointer>); - Запоминает адрес начала сво-

бодного участка в динамиче-

ской памяти.

=>Release(Var P:<тип>, включая Pointer>); - Освобождает память выше

границы P.

Создаются и уничтожаются указатели ссылочного типа с помощью

уже известных процедур New и Dispose.

Пример 77.

Var P1,P2,P3,P4,P5:^Real;

P:^Integer;

Begin ┌──────────────┐

New(P1); │ │

New(P2); ├──────────────┤

Mark(P); ├──────────────┤ <-HeapPtr

New(P3); ├───────P5^────┤ <-P5

New(P4); ├───P4^────────┤ <-P4

New(P5); ├─────────P3^──┤ <-P3 <-P - запомнили границу

... ├──────P2^─────┤ <-P2

├──P1^─────────┤ <-P1<-HeapOrg

└──────────────┘

Dispose(P4); Release(P);

Y Y

┌─────────────┐ ┌─────────────┐

│ │ │ │

├─────────────┤<-HeapPtr │ │

├───────P5^───┤<-P5 │ │

├─────────────┤ │ │

├────────P3^──┤<-P3<-P ├─────────────┤<-HeapPtr

├─────P2^─────┤<-P2 ├───────P2^───┤<-P2

├──P1^────────┤<-P1<-HeapOrg ├────P1^──────┤<-P1<-HearOrg

└─────────────┘ └─────────────┘

Рис.6.

=>Release(HeapOrg); - Освобождает всю динамическую память.

а)Построение списков с добавлением элементов в конец очереди.

Пример 78.

Type ListPtr=^List;

List=Record

D:<любой тип>;

Next:ListPtr;

End;

Var FirstPtr,LastPtr,NewPtr:ListPtr;

TopHeap:^Integer;

D:<любой тип>;

Begin

...

Mark(TopHeap); {задание границы списка}

FirstPtr:=Nil; {начальный адрес указателя}

<цикл>

Begin

New(NewPtr); {создание указателя на каждом шаге и за-}

NewPtr^.D:=D; {дание значений полей данных}

If FirstPtr=Nil Then FirstPtr:=NewPtr

Else LastPtr^.Next:=NewPtr;

LastPtr:=NewPtr;

LastPtr^.Next:=Nil;

End;

... │ │

Release(TopHeap); удаление всего списка │ 3 шаг│

... ├──────┤

End. │ │ Nil<-│ Next │

│ │ │ 2 шаг│ ├ ─ ─ ─┤

│ 1 шаг│ ├──────┤ │ D3 │ ┌LastPtr

├──────┤ Nil<-│ Next │ ┌>├──────┤<─┴NewPtr

┌│ Next │ ├ ─ ─ ─┤ └─┤ Next │

│├─ ─ ─ ┤ │ D2 │ ┌LastPtr ├ ─ ─ ─┤

││ D1 │ ┌LastPtr ┌>├──────┤<─┴NewPtr │ D2 │

│├──────┤<─┼NewPtr └─┤ Next │ ┌>├──────┤

││ │ ├FirstPtr ├ ─ ─ ─┤ └─┤ Next │

│ └TopHeap │ D1 │ ├ ─ ─ ─┤

└>Nil ├──────┤<─┬FirstPtr│ D1 │

└TopHeap ├──────┤<─┬FirstPtr

Рис.7. └TopHeap

После последнего шага получим

├──────┤

┌ Nil<-│ Next │

выполняет- ├ ─ ─ ─┤ ┌LastPtr─выполняется на каждом этапе

ся на каж- │ Dn │ │ LastPtr:=NewPtr;

дом этапе ├──────┤<┐<─┴NewPtr──выполняется на каждом этапе

LastPtr^. │ Next ├─┘<─┐ New(NewPtr);

Next:=Nil; ├ ─ ─ ─┤ │

│ Dn-1 │ │

├──────┤<┐<─┤

│ . ├─┘ │

├──────┤ │

│ . │ │

├──────┤ │

│ . │ │

├──────┤<┐ │

│ Next ├─┘<─┼──выполняется на каждом этапе

├ ─ ─ ─┤ │ LastPtr^.Next:=NewPtr;

│ D2 │ │

├──────┤<┐<─┘

│ Next ├─┘

├ ─ ─ ─┤

│ D1 │

├──────┤<─┬FirstPtr-выполняется на первом этапе

│ │ └TopHeap FirstPtr:=NewPtr;

Рис.8.

б)Построение списков с добавлением элементов в начало очереди

Пример 79.

Type ListPtr=^List;

List=Record

D:<любой тип>;

Next:ListPtr;

End;

Var LastPtr,NewPtr:ListPtr;

TopHeap:^Integer;

D:<любой тип>;

Begin

...

Mark(TopHeap); {задание границы списка}

LastPtr:=Nil; {начальный адрес указателя}

<цикл>

Begin

New(NewPtr); {создание очередного указателя и задание}

NewPtr^.D:=D; {значений полей данных}

NewPtr^.Next:=LastPtr;

LastPtr:=NewPtr;

End;

...

Release(TopHeap); {удаление всего списка}

End.

│ │

│ 3 шаг│

├──────┤ ┌LastPtr

┌──┤ Next │<─┴NewPtr

│ │ │ ├ ─ ─ ─┤

│ 2 шаг│ │ │ D3 │

├──────┤ │ ├──────┤

┌─┤ Next │ │┌─┤ Next │

│ │ │ ├ ─ ─ ─┤ ││ ├ ─ ─ ─┤

│ 1 шаг│ │ │ D2 │ ┌LastPtr ││ │ D2 │

├──────┤ │ ├──────┤<─┴NewPtr └│>├──────┤

│ Next │->Nil │ │ Next │->Nil │ │ Next │->Nil

├ ─ ─ ─┤ │ ├ ─ ─ ─┤ │ ├ ─ ─ ─┤

│ D1 │ ┌LastPtr │ │ D1 │ │ │ D1 │

├──────┤<─┼NewPtr └>├──────┤<─TopHeap └>├──────┤<─TopHeap

└TopHeap

Рис.9.

После последнего шага получаем:

│ │

├──────┤

┌──┤ Next │

│ ├ ─ ─ ─┤ ┌LastPtr-выполняется на каждом этапе

│ │ Dn │ │ LastPtr:=NewPtr;

│ ├──────┤<─┴NewPtr--выполняется на каждом этапе

│┌─┤ Next │ New(NewPtr);

┌>││ ├ ─ ─ ─┤

│ ││ │ Dn-1 │

│ └│>├──────┤

├─>│ │ . │

выполняется│ │ │

на каждом │ │ . │

этапе ─────┤ │ │

NewPtr^.New│ │ │ . │

:=LastPtr; │ │ ├──────┤

│ │┌─┤ Next │

│ ││ ├ ─ ─ ─┤

└>││ │ D2 │

││ ├──────┤

└│>│ Next │->Nil-выполняется на первом этапе

│ ├ ─ ─ ─┤ NewPtr^.Next:= LastPtr;

│ │ D1 │

└>├──────┤<─TopHeap

Рис.10.

В результате получается следующая цепочка связанных элементов.

┌─>┌──────┐ ┌─>┌──────┐ ┌─>┌──────┐

┌────────┐ │ │ D │ │ D │ │ │ D │

│ Начало ├─┘ ├ ─ ─ ─┤ . . . ├ ─ ─ ─┤ │ ├ ─ ─ ─┤

└────────┘ │ Next ├─┘ │ Next ├─┘ │ Next ├->Nil

└──────┘ └──────┘ └──────┘

Рис.11.

в)Вставка новых элементов в список

Вставку дополнительных элементов рассмотрим для списка, пост-

роенного в примере 67.

->Вставка в начало очереди:

Пример 80.

┌ - (было)- - - - -┌────>┌──────┐

┌────────┐ | │ │ D │

│ Начало ├─┴────>┌──────┐ │ ├ ─ ─ ─┤

└────────┘ │ │ D │ │ │ Next ├─ ...

│ ├ ─ ─ ─┤ │<┐ └──────┘

│ │ Next ├─────┘ │

│┌──>└──────┘ │

│├─New(NewPtr); │

│└─NewPtr^.D:=D; │

│ NewPtr^.Next:=FirstPtr;

└──FirstPtr:=NewPtr;

->Вставка в конец очереди:

Пример 81.

┌─>┌──────┐ ┌───>┌──────┐

│ D │ │ │ D │

├ ─ ─ ─┤ │ ├ ─ ─ ─┤ ┌──────────>┌──────┐

│ Next ├──┘ │ Next ├─┘│ │ D │

└──────┘ └──────┘ │ ├ ─ ─ ─┤

┌─N-1 LastPtr──┐│ │ Next ├─>Nil

└─>N-2 ┌>││ ┌─>└──────┘└───┐

│ └──────────>LastPtr │

│ │ └─────┐ │

│ │New(NewPtr);─┤ │

│ │NewPtr^.D:=D;┘ │

│ └LastPtr^.Next:=NewPtr;│

└───LastPtr:=NewPtr; │

LastPtr^.Next:=Nil;───┘

->Вставка в середину очереди:

Пример 82.

┌─>┌──────┐ ┌- - (было)- - - ┌─────>┌──────┐

│ D │ | │ │ D │

├ ─ ─ ─┤ ┌────>┌──────┐ │ ├ ─ ─ ─┤

│ Next ├─┘ │ D │ │ │ Next ├─

└──────┘ │ ├ ─ ─ ─┤ │<┐ └──────┘

A │ │ Next ├───┘ │

│┌───>└──────┘ │

│├─New(NewPtr); │

│└─NewPtr^.D:=D; │

│ NewPtr^.Next:=A^.Next;

└──A^.Next:=NewPtr;

г)Удаление элементов из очереди

->в начале очереди:

Пример 83.

┌──────────────┐

├ -│─>┌──────┐ ├─>┌──────┐ ┌─>┌──────┐

┌────────┐ │ │ │ D │ │ │ D │ │ │ D │

│ Начало ├─┘ │ ├ ─ ─ ─┤ │ ├ ─ ─ ─┤ │ ├ ─ ─ ─┤

└────────┘ │ │ Next ├─┘ │ Next ├─┘ │ Next ├─...

│ └──────┘ └──────┘ └──────┘

│ FirstPtr 2─>FirstPtr

│ NewPtr:=FirstPtr;

└─ FirstPtr:=FirstPtr^.Next;

Dispose(NewPtr);

NewPtr:=Nil;

->в конце очереди:

Пример 84.

┌─>┌──────┐ ┌->┌──────┐ ┌──────>┌──────┐

│ D │ │ │ D │ | │ D │

├ ─ ─ ─┤ │ ├ ─ ─ ─┤ | ├ ─ ─ ─┤

│ Next ├─┘ │ Next ├─┴──>Nil │ Next ├─>Nil

└──────┘ └──────┘ │ └──────┘

┌─N-2 ┌─N-1 │ LastPtr

└─>N-1 └─>LastPtr │ NewPtr:=LastPtr;

└───────────── LastPtr:=(N-2)^.Next;

Dispose(NewPtr);

NewPtr:=Nil;

->в середине очереди:

Пример 85.

┌───────────────┐

┌─>┌──────┐ ├-│- ─>┌──────┐ ├─>┌──────┐

│ D │ │ │ │ D │ | │ D │

├ ─ ─ ─┤ │ │ ├ ─ ─ ─┤ | ├ ─ ─ ─┤

│ Next ├─┘ │ │ Next ├─┘ │ Next ├─...

└──────┘ │ └──────┘ │ └──────┘

A │ B │ C

A^.Next:=B^.Next; │

Dispose(B);─┬─────────┘

B:=Nil;─────┘

7.Указатели структурированных типов данных

Пример 86.

Type ArR1=Array[1..20] Of Real;

ArR2=Array[1..10,1..10] Of ^Real;

ArRtr=^ArR1;

Rec=Record

B:Byte;

C:Char;

Ars:Array [1..10] Of String[10];

End;

ArRec=Array [1..15] Of Rec;

ArRecPtr=^ArRec;

ArPtrRec=Array [1..8] Of ^Rec;

Var A1:ArPtr;A2:ArR2;

A3:ArRecPtr;A4:ArPtrRec;

I,J:Byte;

Begin

New(A1);

A1^[14]:=5.4;

For I:=1 To 10 Do

For J:=1 To 10 Do New(A2[I,J]);

A2[1,5]^:=3.6;

A2[6,4]^:=0;

New(A3);

A3^[5].Ars[6]:='abcd';

A3^[4].B:=124;

For I:=1 To 8 Do New(A4[I]);

A4[3]^.C:=#65;

A4[7]^.Ars[1]:='abcde';

...

Dispose(A1); A1:=Nil;

For I:=1 To 10 Do

For J:=1 To 10 Do Begin

Dispose(A2[I,J]);

A2[I,J]:=Nil:

End;

Dispose(A3);

A3:=Nil;

For I:=1 To 8 Do Begin

Dispose(A4[I]);

A4[I]:=Nil;

End;

End.