Динамічні структури даних: списки h2>
Вступ h2>
В
мовах програмування (Pascal, C, ін) існує й інший спосіб виділення
пам'яті під дані, який називається динамічним. У цьому випадку пам'ять під
величини відводиться під час виконання програми. Такі величини будемо називати
динамічними. Розділ оперативної пам'яті, розподіляється статичний, називається
статичної пам'яттю; динамічно розподілена розділ пам'яті називається
динамічною пам'яттю (динамічно розподіленою пам'яттю). p>
Використання
динамічних величин надає програмісту ряд додаткових
можливостей. По-перше, підключення динамічної пам'яті дозволяє збільшити
обсяг оброблюваних даних. По-друге, якщо потреба в якихось даних
відпала до закінчення програми, то зайняту ними пам'ять можна звільнити для
іншої інформації. По-третє, використання динамічної пам'яті дозволяє
створювати структури даних змінного розміру. p>
Робота
з динамічними величинами пов'язана з використанням ще одного типу даних --
посилального типу. Величини, що мають контрольний тип, називають покажчиками. P>
Покажчик
містить адресу поля в динамічній пам'яті, що зберігає величину певного
типу. Сам покажчик розташовується в статичної пам'яті. P>
Адреса
величини - це номер першого байта поля пам'яті, в якому розташовується
величина. Розмір поля однозначно визначається типом. P>
Далі
будемо більш докладно обговорювати покажчики і дії з ними в мові Pascal,
приклади будемо приводити на Pascal та C. p>
Величина
посилального типу (покажчик) описується в розділі опису змінних наступним
так: p>
Var: ^; p>
Ось
приклади опису покажчиків: p>
Type Mas1 = Array [1 .. 100] Of
Integer; p>
Var
P1: ^ Integer; p>
P2: ^ String; p>
Pm: ^ Mas1; p>
Тут
P1 - покажчик на динамічну величину цілого типу; P2 - покажчик на
динамічну величину строкового типу; Pm - покажчик на динамічний масив,
тип якого задано у роздiлi Type. p>
Самі
динамічні величини не вимагають опису в програмі, оскільки під час
компіляції пам'ять під них не виділяється. Під час компіляції пам'ять виділяється
тільки під статичні величини. Покажчики - це статичні величини, тому
вони вимагають опису. p>
Яким
же чином відбувається виділення пам'яті під динамічну величину? Пам'ять під
динамічну величину, пов'язану з покажчиком, виділяється в результаті
виконання стандартної процедури NEW. Формат звернення до цієї процедури: p>
NEW (); p>
Вважається,
що після виконання цього оператора створена динамічна величина, ім'я якої
має такий вигляд: p>
: =
^ p>
Нехай
в програмі, в якій є наведене вище, присутні
наступні оператори: p>
NEW (P1); NEW (P2); NEW (Pm); p>
Після
їх виконання у динамічній пам'яті виявляється виділеним місце під три
величини (два скалярні і один масив), які мають ідентифікатори: p>
P1 ^,
P2 ^, Pm ^ p>
Наприклад,
позначення P1 ^ можна розшифрувати так: Динамічна змінна, на яку
посилається вказівник P1. p>
Подальша
робота з динамічними змінними відбувається точно так само, як зі статичними
змінними відповідних типів. Їм можна присвоювати значення, їх можна
використовувати в якості операндів у висловах, параметрів підпрограм і пр.
Наприклад, якщо змінної P1 ^ потрібно присвоїти число 25, змінної P2 ^
присвоїти значення символу "Write", а масив Pm ^ заповнити по порядку
цілими числами від 1 до 100, то це робиться так: p>
P1 ^: = 25; p>
P2 ^: = 'Write'; p>
For I: = 1 To 100 Do Pm ^ [I]: = I; p>
Крім
процедури NEW значення покажчика може визначатися оператором присвоювання: p>
: =; p>
В
як посилального виразу можна використовувати p>
покажчик;
p>
посилальну
функцію (тобто функцію, значенням якої є покажчик); p>
константу
Nil. p>
Nil
- Це зарезервований константа, що позначає порожню посилання, тобто посилання,
яка ні на що не вказує. При присвоєнні базові типи покажчика та
посилального вирази повинні бути однакові. Константу Nil можна привласнювати
вказівником з будь-яким базовим типом. p>
До
присвоювання значення посилальної змінної (за допомогою оператора присвоєння
або процедури NEW) вона є невизначеною. p>
Введення
і висновок покажчиків не допускається. p>
Розглянемо
приклад. Нехай у програмі описані наступні покажчики: p>
Var D, P: ^ Integer; p>
K: ^ Boolean; p>
Тоді
допустимими є оператори присвоювання p>
D: = P; K: = Nil; p>
оскільки
дотримується принцип відповідності типів. Оператор K: = D помилковий, тому що базові
типи у правій та лівій частині різні. p>
Якщо
динамічна величина втрачає свій покажчик, то вона стає
"сміттям". У програмуванні під цим словом розуміють інформацію,
яка займає пам'ять, але вже не потрібна. p>
Уявіть
собі, що в програмі, в якій присутні описані вище покажчики, в
розділі операторів записано наступне: p>
NEW (D);
NEW (P); p>
(Виділено
місце в динамічній пам'яті під дві цілі змінні. Покажчики отримали
відповідні значення) p>
D ^
: = 3; P ^: = 5; p>
(Динамічним
змінним присвоєно значення) p>
P
: = D; p>
(Покажчики
P і D стали посилатися на одну й ту ж величину, що дорівнює 3) p>
WriteLn (P ^,
D ^); (Двічі друкується число 3) p>
Таким
чином, динамічна величина, що дорівнює 5, втратила свій покажчик і стала
недоступною. Однак місце в пам'яті вона займає. Це і є приклад
виникнення "сміття". На схемі показано, що відбулося в результаті
виконання оператора P: = D. p>
В
Паскалі є стандартна процедура, що дозволяє звільняти пам'ять від
даних, потреба в яких відпала. Її формат: p>
DISPOSE (); p>
Наприклад,
якщо динамічна змінна P ^ більше не потрібна, то оператор p>
DISPOSE (P) p>
видалить
її з пам'яті. Після цього значення покажчика P стає невизначеним.
Особливо істотним стає ефект економії пам'яті при видаленні великих
масивів. p>
В
версіях Турбо-Паскаля, що працюють під операційною системою MS DOS, під дані
однієї програми виділяється 64 кілобайт пам'яті (або, якщо точніше, 65520
байт). Це і є статична область пам'яті. При необхідності працювати з
великими масивами інформації цього може виявитися мало. Розмір динамічної
пам'яті - набагато більше (сотні кілобайт). Тому використання динамічної
пам'яті дозволяє істотно збільшити обсяг оброблюваної інформації. p>
Слід
чітко розуміти, що робота з динамічними даними уповільнює виконання
програми, оскільки доступ до величини відбувається у два кроки: спочатку шукається
покажчик, потім по ньому - величина. Як це часто буває, діє
"закон збереження неприємностей": виграш в пам'яті компенсується
програшем в часі. p>
Приклад.
Дан текстовий файл розміром не більше 64 Кб, що містить дійсні числа, за
одному в кожному рядку. Переписати вміст файлу в масив, розмістивши його в
динамічно розподіляє пам'яті. Обчислити середнє значення елементів
масиву. Очистити динамічну пам'ять. Створити цілий масив розміром 10000,
заповнити його випадковими цілими числами в діапазоні від -100 до 100 і обчислити
його середнє значення. p>
(Мова Turbo Pascal) p>
Program Srednee; p>
Const NMax = 10000; p>
Type Diapazon = 1 .. NMax; p>
MasInt = Array [Diapazon] Of Integer; p>
MasReal = Array [Diapazon] Of Real; p>
Var PIint: ^ MasInt; PReal:
^ MasReal; p>
I, Midint: longInt; MidReal: Real;
T: Text; S: string; p>
Begin p>
Write ( 'Введіть ім'я
файлу: '); ReadLn (S); p>
Assign (T, S); Reset (T); MidReal: =
0; MidInt: = 0; p>
Randomize;
p>
NEW (PReal);
(Виділення пам'яті під речовинний масив) p>
(Введення
і підсумовування речового масиву) p>
While Not Eof (T) Do p>
Begin ReadLn (T, PReal ^ [I]); MidReal
: = MidReal + PReal ^ [I] End; p>
DISPOSE (PReal);
(Видалення речового масиву) p>
NEW (PInt);
(Виділення пам'яті під цілий масив) p>
(Обчислення
і підсумовування цілого масиву) p>
For I: = 1 To NMax Do p>
Begin PInt ^ [I]: = -100 +
Random (201); MidInt: = MidInt + PInt ^ [I] End; p>
(Висновок
середніх значень) p>
WriteLn ( 'середнє
ціле одно: ', MidInt Div NMax); p>
WriteLn ( 'середнє
речовий одно: ', (MidReal/NMax): 10: 6) p>
End. p>
// Мова C + + p>
# include p>
# include p>
# include p>
# include p>
# define NMax 10000 p>
typedef int MasInt; p>
typedef float MasReal; p>
MasInt * PInt; MasReal * PReal; p>
int I, n, MidInt; float MidReal;
char S [255]; p>
FILE * t; char * endptr; p>
void main () p>
(
cout> S; p>
t = fopen (S, "r"); p>
MidReal = 0; MidInt = 0; p>
randomize (); I = 0; p>
/* Виділення пам'яті під речовинний
масив */ p>
PReal = (MasReal *) malloc (sizeof (MasReal)); p>
/ * Введення і підсумовування речового масиву */ p>
while (! feof (t)) p>
(fgets (S, 255, t);// вводимо з файлу
рядок p>
PReal [I] = strtod (S, & endptr);//
перетворимо введену рядок у дійсне число p>
MidReal + = PReal [I]; I + +;) p>
n = I +1; p>
free (PReal);/* Видалення речового масиву */ p>
PInt = (MasInt *) malloc (sizeof (MasInt));
/ * Виділення пам'яті під цілий масив */ p>
/* Обчислення і підсумовування цілого
масиву */ p>
for (I = 0; I
(PInt [I] = -100 + random (201); p>
MidInt + = PInt [I];) p>
/ * Висновок середніх значень */ p>
cout Next = First;
First = Vsp; p>
return First; p>
) p>
Zveno
* Iz_Nachala (Zveno * First) p>
(Zveno * Vsp; p>
Vsp = First-> Next; p>
free (First); p>
return Vsp; p>
) p>
Zveno * V_Spisok (Zveno
* Pred, BT X) p>
(Zveno * Vsp; p>
Vsp = (Zveno *) malloc (sizeof (Zveno));
p>
Vsp-> Inf = X; p>
Vsp-> Next = Pred-> Next; p>
Pred-> Next = Vsp; p>
return Vsp; p>
) p>
BT Iz_Spiska (Zveno
* Pred) p>
(BT X; p>
Zveno * Vsp; p>
Vsp = Pred-> Next; p>
Pred-> Next = Pred-> Next-> Next; p>
X = Vsp-> Inf; p>
free (Vsp); p>
return X; p>
) p>
void Print (Zveno
* First) p>
(Zveno * Vsp; p>
Vsp = First; p>
while (Vsp) p>
(cout Inf Next;) p>
cout 0 p>
Then If S2 = Nil p>
Then Begin V_Nachalo (S2, V1 ^. Inf);
V2: = S2 End p>
Else Begin V_Spisok (V2, V1 ^. Inf);
V2: = V2 ^. Next End; p>
If V1 ^. Inf <0 p>
Then If S3 = Nil p>
Then Begin V_Nachalo (s3, V1 ^. Inf);
V3: = S3 End p>
Else Begin V_Spisok (V3, V1 ^. Inf);
V3: = V3 ^. Next End; p>
V1: = V1 ^. Next p>
End; p>
WriteLn ( 'результуючий список з
позитивних елементів: '); Print (S2); p>
WriteLn ( 'результуючий список з
негативних елементів: '); Print (S3); p>
Ochistka (S1); Ochistka (S2); Ochistka (S3); p>
End. p>
// Програма на C + + p>
# include "SPIS.CPP" p>
void main () p>
(Zveno * S1, * S2, * S3, * V1, * V2, * V3;
p>
BT a; int i, n; p>
clrscr (); p>
randomize ();
p>
S1 = NULL;
p>
//
створюємо перший елемент p>
a =- 100 + random (201); p>
S1 = V_Nachalo (S1, a); p>
n = 1 + random (20);
p>
//
формуємо список довільної довжини і виводимо на друк p>
V1 = S1; p>
for (i = 2; iInf> 0) p>
if (! S2) p>
(S2 = V_Nachalo (S2, V1-> Inf); V2 = S2;) p>
else (V_Spisok (V2, V1-> Inf); V2 = V2-> Next;); p>
if (V1-> Inf <0) p>
if (! S3) p>
(S3 = V_Nachalo (S3, V1-> Inf); V3 = S3;) p>
else (V_Spisok (V3, V1-> Inf); V3 = V3-> Next;); p>
V1 = V1-> Next;) p>
cout