Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритмы и структуры данных / методичка структуры данных_задания.docx
Скачиваний:
54
Добавлен:
12.05.2015
Размер:
381.84 Кб
Скачать

3.1. Динамічні змінні. Вказівники

Динамічні структури даних будуються з динамічних змінних. Динамiчнi данi згiдно з картою розподілу оперативної пам’яті DOS можуть розміщуватися у так званій Heap-області (Heap – “купа”). На відміну від статичних динамічні змінні не мають імени, вони не описуються у розділі Var, для них не виділяється пам’яті під час компіляції, тип може бути будь-яким, крім файлового. Динамічні змінні створюються та знищуються під час виконання програми. Для роботи з динамічними змінними використовуються змінні базового типу Pointer, які мають назву вказівників.

Вказівникові типи займають проміжне становище між скалярними і складними типами: з одного боку, значення зсилочних типів є атомарними (неподільними), з другого – ці типи визначаються через інші (в тому числі складені) типи.

Змістовно будь-який зсилочний тип визначає множину значень, які є вказівниками на значення деякого визначеного типу.

Ознакою того, що змінна є вказівником, є або тип Pointer, або символ ^ перед позначенням типу.

Приклади опису:

Type

mas = array [1..10] Of integer;

dmas = ^mas;

Var

p : ^integer;

q : ^char;

workmas : dmas;

r,s,t : pointer;

де:

p – вказівник на динамічний об’єкт цілого типу;

q – вказівник на динамічний об’єкт символьного типу;

workmas – вказівник на динамічний об’єкт, значенням якого є масив з 10 цілих чисел;

r,s,t – вказівники на динамічні змінні будь-якого типу.

В змінній типу вказівник зберігається адреса динамічної змінної у Heap-області.

3.1.1. Засоби створення та використання динамічних даних

Процес створення динамічної змінної складається з наступних кроків:

– пошук та резервування місця у Heap-області для розміщення динамічної змінної;

– засилання адреси зарезервованої ділянки у вказівник;

–заповнення динамічної змінної, тобто занесення значення за адресою у вказівник.

Зв’язок вказівника з об’єктом схематично має вигляд:

*

об’ект

p

Вказівник може мати порожню вказівнику Nil . (Nil – службове слово).

Після введення у розділі опису вказівника p, він не посилається ні на який програмний об’ект . Для породження динамічного об’єкту використовується стандартна процедура:

New(Var p:pointer);

де

p – фактичний параметр, вказівник.

У результаті виконання оператору процедури породжується новий динамічний об’єкт типу, на який посилається вказівник, що є фактичним параметром процедури New. Вказівнику присвоюється його адреса.

У наведеному прикладі породжується динамічна змінна типу integer, а вказівнику р присвоюється її адреса. При цьому породженому динамічному об’єкту не присвоюється яке-небудь значення, а тільки резервується пам’ять.

Динамічним об’єктам на відміну від статичних імен не дається. Для звернення до динамічного об’єкту використовують змінну – вказівник у вигляді:

p^

Наявність символу ^ після змінної - вказівника говорить про те, що мова йде не про значення самого вказівника, а про значення тієї змінної, на яку вказує вказівник.

Динамічну змінну можна ініціювати будь-яким з відомих засобів.

p^:=10; – означає присвоєння значення 10 динамічній змінній, на яку вказує p.

Змінна з символом ^ може бути використана у будь-яких конструкціях мови, де припустиме використання змінних того ж типу, що й тип динамічної змінної:

r^:=p^+3;

Один з поширених засобів роботи з Неар-областю реалізується за допомогою пари процедур New-Dispose.

Приклад:

Var

x1,x2,s : ^integer;

Begin

New(x1);

New(x2);

New(s);

Readln(x1^);

Readln(x2^);

s^:=x1^+x2^;

Writeln(‘сумма=’, s^);

Dispose(x1);

Dispose(x2);

Dispose(s)

End.

В опису

Var

x1,x2,s : ^integer;

знак  ^  перед типом вказує на те, що кожна із змінних x1,x2,s є вказівником на динамічну змінну цілого типу.

У результаті виконання процедур

New(x1);

New(x2);

New(s);

у Heap-областi резервується пам’ять для збереження трьох змінних цілого типу, адреси цих областей пам’яті заносяться відповідно у змінні-вказівники x1, x2, s.

Змінна-вказівник, записана зі знаком  ^  справа вказує на те, що мова йде не про адресу динамічної змінної, а про значення динамічної змінної. Тому процедури:

Readln (x1^);

Readln (x2^);

здійснюють ввід значень самих динамічних змінних, на які зсилаються вказівники x1, x2.

Оператором присвоєння

s^:=x1^+x2^;

за адресою s заноситься відповідна сума – так формується третя динамічна змінна, значення якої виводиться на екран:

Writeln (‘сума=’, s^);

Кожному виклику New повинен відповідати виклик Dispose, тобто необхідно знищити всі динамічні змінні, інакше вони будуть продовжувати займати пам’ять (стають “сміттям”).

Над вказівниками визначені операції присвоєння, порівняння. Вказівник може мати порожню зсилку: p:=Nil.

Наприклад p,d - вказівники:

Var p,d : ^integer;

тоді операції:

p^:=3; d^:=58; p :=d;d :=Nil;

схематично можна зобразити у наступному вигляді:

p^:=3; d^:=58;

p :=d;

d :=Nil;

Інший засіб розміщення динамічних змінних у Неар-областi реалізується за допомогою двох взаємно пов’язаних процедур:

GetMem (Var P:pointer; Size:work);

FreeMem (Var P:pointer; Size:work);

GetMem створює динамічну змінну P^ визначеного розміру Size. FreeMem – звільнює динамічну змінну заданого розміру. Природньо, що виклики GetMem та FreeMem повинні відповідати один одному, а значення для одної пари викликів Size співпадати.

Дія пар процедур однакова.. Однак процедури GetMem, FreeMem мають більш широкий спектр застосування. Наприклад, можна виділити пам’ять визначеного розміру:

GetMem(P,20);

.............

FreeMem(P,20);

– що неможливо у New-процедурі.

Третя пара процедур Mark і Release використовується для фіксації та відновлення стану Неар-області.

Процедура Mark(Var p:pointer) записує поточний стан Heap-області у змінну-вказівник p.

Процедура Release(Var p:pointer) повертає Heap через p до того стану , який був збережений відповідним викликом Mark, тобто за допомогою Release звільняються усі динамічні змінні, розподілені будь-яким чином після виклику Mark. При цьому функція MemAvail(Var i:longint) повертає розмір вільної пам’яті. MaxAvail (Var i:longint)– розмір найбільшої безперервної частини вільної пам’яті.

Приклад:

Uses Crt;

Var

i : longint;

p : ^integer;

Begin

ClrScr;

i:=MemAvail;

Writeln(‘.....’, i);

New(p);

p^:=4566;

i:=MemAvail;

Writeln(‘....’, i);

Readln

End.

Протокол роботи:

28768

28760

тобто змінна цілого типу займає у Неар-області 8 байт.

У зв’язку з процедурою New з’являється важлива проблема: можливе вичерпання областi пам’ятi, що вiдведена пiд динамiчнi змiннi. Якщо при виконаннi New з’ясовується, що для розмiщення нової змiнної в кучi не хватає пам’ятi, то значення вказiвника, заданого у параметрi процедури, не змi­ниться. При цьому виконання програми не перерветься i нiяких повiдомлень видано не буде. Тому подальша робота з невстановленим вказiвником може привести до непередбачених наслiдкiв.

Для пiдвищення надiйностi програми слiд перевiряти поточний стан динамiчної пам’ятi перед кожним зверненням до New. Це можна зробити за допомогою стандартної функцiї MaxAvail, яка повертає максимальний розмiр безперервної частини вiльної пам’ятi, в якому можливо розмiстити динамiчну змiнну.

3.1.2. Проблема загублених вказівників

Робота з динамiчними змiнними через вказiвники потребує великої ретельностi i охайностi при проектуваннi програм. Зокрема, потрiбно звiльняти видiленi пiд динамiчнi змiннi областi одразу пiсля того, як необхiднiсть у них вiдпадає, iнакше “забруднення” пам’ятi непотрiбними диамiчними змiнними може привести до швидкого її вичерпання.

Крiм того, необхiдно враховувати ще одну проблему, пов’язану з протирiччям мiж стековим принципом розмiщення статичних змiнних i довiльним характером створення i знищення динамiчних змiнних.

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

Type

PtrAnk = ^Anketa;

Anketa = Record

. . .

End;

Procedure GetAnketa;

var

P : PtrAnk;

Begin

P : New (PtrAnk)

End;

Begin

WriteLn(MemAvail);

GetAnketa;

WriteLn(MemAvail)

End.

Виклик New в процедурi GetAnketa веде до вiдведення пам’ятi для динамiчної змiнної типу Anketa. Вказiвник на цю змiнну присвоюється змiннiй P. Розглянемо ситуацiю, яка створюється пiсля виходу з процедури GetAnketa.

За правилами блочностi всi локальнi змiннi пiдпрограми перестають iснувати пiсля її за вершення. В цьому випадку зникае локальна змiнна P. В той же час, область пам’ятi, яка вiдведенна в процесi  0виконання процедури GetAnketa продовжує iснувати, бо звiльнити її можна тiльки явно, через процедуру Dispose.Таким чином, пiсля виходу iз GetAnketa вiдсутнiй будь-який доступ до динамiчноїзмiнної, оскiльки вказiвник на неї вiдсутнiй.

При проектуваннi програм, якi iнтенсивно вико­ристо­вують динамiчну пам’ять необхiдно уважно вiдноситись до даної проблеми та, або вчасно звiльняти пам’ять, або, в разi потреби, запам’ятовувати вказівника на динамiчнi змiннi.