Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
РП_31_АСД.pdf
Скачиваний:
145
Добавлен:
23.02.2016
Размер:
2.92 Mб
Скачать

Тема 7. ПОКАЖЧИКИ ТА ОПЕРАЦІЇ НАД НИМИ

Більшість простих базових та статичних структур даних були розглянуті в дисципліні «Основи програмування та алгоритмічні мови». Серед базових типів не розглядалися лише покажчики. Вони є статичними змінними, але призначені здебільшого для «обслуговування» динамічних структур даних. Тому спочатку будуть розглядатися основні поняття, пов’язані з розподілом оперативної пам'яті комп’ютера та її динамічної ділянки.

Поняття динамічної змінної та динамічної пам’яті

Статичні змінні характеризуються тим, що їх значення зберігаються в ділянках оперативної пам'яті, які визначаються на етапі компіляції програми і не змінюються під час її виконання. Проте у багатьох задачах обсяг оперативної пам'яті, необхідної для збереження певних даних, неможливо визначити наперед. Для збереження таких даних використовуються змінні, які створюються і знищуються в процесі виконання програми. Такі змінні називаються динамічними, а пам'ять, що для них виділяється, - динамічною пам'яттю. Оскільки обсяг оперативної пам'яті, що використовується для збереження значення динамічної змінної, компілятору не відомий, він не позначає динамічну змінну ідентифікатором. Доступ до значення такої змінної здійснюється за її адресою. Отже, на етапі компіляції програми виділяється оперативна пам'ять для збереження адреси динамічної змінної, а пам'ять для збереження її значення виділяється під час виконання програми.

Розподіл оперативної пам'яті

Оперативна пам'ять комп’ютера є послідовністю байтів, або комірок. Розташування таких комірок є впорядкованим, і тому їх можна перенумерувати. Послідовна нумерація байтів цілими числами є зручною з погляду людини, проте процесор використовує інший спосіб доступу до комірок пам'яті – доступ за допомогою адрес. Адреса складається з двох шістнадцятирозрядних чисел, що називаються базисом сегмента та зсуненням. Сегмент – це неперервна область оперативної пам'яті обсягом 64 Кбайт (65536 байт), що починається з комірки, номер якої є кратним 16. Базис сегмента – це номер 16-байтової групи, з якої починається сегмент. Таким чином, якщо базис сегмента дорівнює x, то цей сегмент починається з комірки, що має номер 16x. Зсунення дорівнює відстані в байтах, на яку комірка віддалена від початку сегмента. В адресі комірки базис сегмента та зсунення записуються, як правило, у шістнадцятковому вигляді і розділяються символом «:».

Наприклад, 000А:001А – це адреса комірки, номер якої дорівнює:

16∙1016)+1А16= ВА16=1110∙161+1010∙160=18610

Адреси можна зіставляти не лише з окремими комірками, а і з довільними неперервними ділянками пам'яті. Адресою ділянки вважається адреса її найпершої комірки. Принци адресації комірок пам'яті ілюструє рис.1.

Середовище програмування Borland Pascal 7.0 підтримує роботу з базовою оперативною пам'яттю, обсяг якої становить 640 Кбайт. Цього обсягу достатньо для розв’язання задач, в яких використовуються структури даних досить великої розмірності. Під час роботи програми базова пам'ять комп’ютера розподіляється так.

Перед виконанням будь-якого exe-файлу виділяється спеціальна область пам'яті, що називається префіксним сегментом програми. Дані з цієї області, обсяг якої становить 256 байт, використовуються для керування процесом виконання програми. Зокрема, ця область містить адреси підпрограм, що здійснюють обробку переривань під час натискання клавіш Ctrl+Break, обробку критичних помилок операційної системи, завершення програми тощо.

Після префіксного сегмента програми розміщується сегмент коду основної програми,обсяг якого становить 64 Кбайт. Кожному програмному модулю також виділяється

22

сегмент коду обсягом 64 Кбайт. Крім того, сегмент коду виділяється для модуля SYSTEM, який автоматично приєднується до будь-якої програми, створеної в середовищі Borland Pascal 7.0.

 

 

 

 

Типізовані константи та глобальні змінні,

 

0000:0000

байт

оголошені в розділі const

і var основної програми,

 

 

розташовуються в сегменті даних, що має обсяг 64

 

 

16

 

Кбайт. За сегментом даних розташований сегмент

 

 

 

 

 

 

0001:0000=0000:0010

16байт

стеку, обсяг якого становить 16 Кбайт, але може бути

 

 

змінений директивою компілятора {$M}.

 

 

 

 

 

 

 

Сегмент

стеку

використовується

для

 

 

 

 

 

тимчасового зберігання адреси, за якою здійснюється

 

 

 

 

повернення

до основної

програми

під

час

 

0000:0000

16байт

 

Сегмент

 

виклику процедури або функції, параметрів, що

 

64Кбайт

 

передаються підпрограмі, а також для

 

зберігання значень оголошених в ній локальних

змінних.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Решта базової пам'яті є динамічною

 

 

 

 

 

пам'яттю, що використовується

для

збереження

значень

 

динамічних змінних. Тому в загальному випадку за

 

Рис.1 Адресація комірок

 

оперативної пам'яті

допомогою динамічних змінних у пам'яті можна зберігати

 

 

більші обсяги даних, ніж за допомогою статичних.

 

 

Отже, динамічна пам'ять є неперервним масивом байтів. Ця область пам'яті називається купою, або Heap-областю (від англ. heap - купа). Початкова адреса Heap-області зберігається в стандартній змінній HeapOrg, кінцева адреса – у стандартній змінній HeapEnd. Адреса, якою розділяються зайнята та вільна частина купи, зберігається в стандартній змінній HeapPtr.

Кожного разу після виділення динамічної пам'яті значення покажчика HeapPtr збільшується. У базовій пам'яті виділено також спеціальну область для збереження записів, що реєструють рух ділянок купи. У стандартній змінній FreePtr зберігається початкова адреса цієї області пам'яті, а кожний запис у ній містить інформацію про розташування певної динамічної

змінної.

Покажчики в Pascal

Для збереження адрес динамічних змінних використовуються покажчики – статичні змінні посилального типу. Значенням покажчика є адреса області пам'яті, в якій зберігається певний елемент даних. Цим елементом може бути значення змінної або константи, адреси іншої змінної тощо. Для збереження значення покажчика виділяється 4 байти пам'яті. У перших двох байтах записується базис сегмента, а у двох інших – зсунення. Якщо область пам'яті, в якій зберігається значення змінної, складається з кількох байтів, покажчик адресує її перший байт.

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

У мові Pascal розрізняють типізовані та нетипізовані покажчики. Покажчик, який може посилатися на дані лише певного типу, називається типізованим, а відповідний тип даних називається базовим. Для оголошення типізованого покажчика використовується символ «^», який записується перед іменем базового типу даних.

var <ім’я покажчика> : ^<ім’я базового типу>;

Лексема ^<ім’я базового типу> є ідентифікатором певного посилального типу. Проте в розділі оголошень типів даних type для посилального типу можна оголосити і окреме ім’я, яке згодом використовуватиметься для оголошення покажчиків у розділі var:

23

type <ім’я посилального типу>=^<ім’я базового типу>; var <ім’я покажчика>: ^<ім’я посилального типу>;

Існує правило, згідно з яким будь-який ідентифікатор у мові Pascal вперше має згадуватись у його оголошенні. Це правило не розповсюджується на ідентифікатори типів даних, що згадуються як базові типи в оголошеннях покажчиків. Інакше кажучи, можна оголосити покажчик на змінні ще не оголошеного базового типу.

Оголошення типізованих покажчиків демонструється в наступних прикладах:

type

 

{оголошення типів}

vector

= array[1..5] of integer;

filetype

= file of integer;

BytePtr

= ^Byte;

{тип покажчика на змінні типу Byte}

IdentPtr

= ^IdentRec;

{тип покажчика, що посилається на ще не

 

 

оголошений тип запису}

IdentRec

= record

{тип запису, компонентом якого є покажчик

Ident : string[15];

типу IdentRec }

 

Next

: IdentRec

 

end;

 

 

ArrayPtr

= ^vector;

{тип покажчика на масиви типу vector}

TextPtr

= ^text;

{тип покажчика на текстові файли}

FilePtr

= ^ filetype

{тип покажчика на типізовані файли}

var

 

 

pbyte: BytePtr;

 

prec

: IdentPtr;

 

parray

: ArrayPtr;

 

pfile

: FilePtr;

 

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

var <ім’я покажчика> : pointer;

Операції над покажчиками в Pascal

Над покажчиками допустимі три операції: присвоєння, порівняння та розіменування. Покажчику можна присвоїти значення адреси статичної змінної або підпрограми,

значення іншого покажчика, а також значення адреси, що його повертає функція Ptr, яка розглядатиметься пізніше. У такому разі для отримання адреси змінної або підпрограми використовується операція визначення адреси, що позначається символом «@»:

<ідентифікатор покажчика>:=@<ідентифікатор>;

Тут <ідентифікатор> - ім’я змінної будь-якого типу, процедури або функції.

Покажчики можна порівнювати, використовуючи при цьому операції «=» (рівність) та «<>» (нерівність). Інші операції порівняння не застосовані до операндів посилального типу. Результат порівняння покажчиків, як і результат будь-якого іншого порівняння, належить до логічного типу даних, і тому операції порівняння покажчиків можна використовувати в булевих виразах, наприклад,

if Ptr1=Ptr2 then writeln(‘pointers are equal’);

24

Порівнювати значення покажчиків, а також присвоювати значення одного покажчика іншому можна лише в тих випадках, коли:

принаймні один із покажчиків є не типізованим;

обидва покажчики посилаються на один і той самий базовий тип даних.

Щоб отримати значення, на яке посилається покажчик, потрібно виконати операцію розіменування покажчика. Ця операція позначається символом «^», що записується після імені покажчика. Її можна записувати як у правій, так і лівій частині оператора присвоєння:

<ідентифікатор змінної>:=<ідентифікатор покажчика>^; < ідентифікатор покажчика >^:=<вираз>;

Операцію розіменування можна застосовувати лише до покажчиків, яким надано значення певної адреси динамічної пам'яті.

Приклад. Розглядаються дві змінні: variable типу integer та ptr – покажчик на дані типу integer. Нехай покажчику ptr надано адресу змінної variable, тобто виконано операцію:

ptr:=@variable;

Тоді значення виразів variable і ptr^ завжди будуть однаковими. Якщо виконати операцію ptr^:= variable, то змінні ptr^ та variable будуть різними, хоча, можливо, і матимуть протягом певного часу однакові значення.

Результат розіменування нетипізованого покажчика має невизначений тип, і тому вираз ptr^ де ptr – не типізований покажчик, не може входити до складу інших виразів.

Якщо покажчик поки що не ініціалізований, то у більшості випадків компілятор надає йому значення, що позначається зарезервованим словом nil. Якщо значення певного покажчика становить nil, то вважається, що покажчик не посилається на жоден елемент даних. Тому значення nil називають ще порожньою адресою. Результат розіменування покажчика, значення якого дорівнює nil, є непередбачуваним.

Покажчики не можна використовувати як аргументи підпрограм. Із цього, зокрема, випливає висновок про неможливість надрукувати значення покажчика за допомогою стандартних процедур write та writeln. Спроба зробити це призведе до появи помилки Error 64: Cannot Read or Write variables of this type (Не можна вводити або виводити змінні даного типу). Тому значення покажчиків можна переглядати лише під час налагодження програми у вікні Watch. Зв'язок між покажчиком і динамічною змінною, на яку він посилається, проілюстрований на рис.1.

Статична пам'ять

Динамічна пам'ять

Покажчик parray

 

Масив arr

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис.1 Покажчик у статичній пам'яті та змінна в динамічній пам'яті Наступна програма призначена для демонстрації принципів використання операцій над

покажчиками.

program Yrok3; {$APPTYPE CONSOLE} uses SysUtils;

type

vector = array[1..5] of integer;

25

typefile = file of integer;

 

IdentPtr =^IdentRec;

 

IdentRec = record

 

Ident: string[15];

 

Next : IdentPtr;

 

end;

 

var pbyte : ^Byte;

 

pint1,pint2 : ^integer;

 

parray : ^vector;

 

pfile

: ^typefile;

 

untyped : pointer;

 

prec : IdentPtr;

 

x : byte;

 

y : integer;

 

arr : vector;

Рис.2. Результати роботи програ ми Yrok3

f : file of integer;

list : IdentRec;

 

begin

 

 

y :=1;

pint1 :=@y;

 

y :=2;

pint2 :=@y; {покажчики pint1 та pint2 посилаються на ту сам у змінну - y}

write('pint1 and pint2 are the same: '); writeln('(pint1=pint2)=',pint1=pint2 ); {порівняння покажчиків} writeln('pint1^=',pint1^,' pin t2^=',pint2^,' y=',y);

pbyte := @x;

untyped := pbyte; {використання нетипізованого покажчика,} pint1 := untyped; {pint1 та pbyte посилаються на ту саму змінну} x :=4;

writeln('pbyte^=',pbyte^,' pint1^=',pint1^,' x=',x);

{використання покажчик ів для посилання на елементи масивів та записів} parray :=@arr;

parray^[1] :=1; prec := @list;

prec^.Ident :='Pointers!!!'; writeln('arr[1]=',arr[1],' parray^[1]=',parray^[1]); write('list.Ident=',list.Ident);

write(' prec^.Ident=',prec^.Ident); {використання покажчик а на файлову змінну} assign(f,'newfile.txt');

pfile :=@f;

 

rewrite(f);

 

write(pfile^,y);

{до файлу 'newfile.txt' буде записано значення змінної y}

close(pfile^);

{}

readln;

 

end.

 

Результати роботи програми наведені на рис.2.

26

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.