Динамічні
структури даних: списки h2>
Вступ h2>
У попередніх
оглядах ми розглядали програмування, пов'язане з обробкою тільки статичних даних. Статичними
величинами називаються такі, пам'ять під які виділяється під
час компіляції і зберігається протягом всієї роботи програми. p>
У мовах
програмування (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 - покажчик на динамічний масив, тип якого
заданий у розділі 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 <
NMax; I ++) p>
(
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