Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
самост1_1new_druk!!!.doc
Скачиваний:
22
Добавлен:
13.11.2019
Размер:
1.61 Mб
Скачать

Тема: Вказівники.

Вказівники.

Змінні, оголошені в розділах описів основної програми і підпрограм, називають статичними. Для глобальних змінних, тобто змінних, оголошених в розділах описів змінних основної програми, пам'ять відводиться на початку виконання програми і зберігається за ними до завершення виконання програми. Глобальні змінні зберігаються в спеціальній області пам'яті, котра називається сегментом даних. Сегмент даних має максимальний розмір 64 Кб. Для локальних змінних пам'ять виділяється в момент виклику підпрограми, а по завершенні її виконання ця пам'ять звільнюється. Іноді під час виконання програми доводиться працювати з невідомим наперед обсягом даних. Pascal дозволяє створювати нові змінні і знищувати їх безпосередньо під час виконання програми. Змінні, створенням і знищенням яких можна явно керувати під час виконання програми, називаються динамічними змінними. Створення таких змінних відбувається шляхом відведення для них пам'яті в спеціальній області пам'яті, яка називається областю динамічно розnоділюваної пам'яті (heap). Область динамічно розподілюваної пам'яті – це вся пам'ять, яку операційна система робить доступною для програми і яка не використовується її кодом, сегментом даних і стеком.

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

  • Якщо програма працює з великими обсягами даних, загальний обсяг яких перевищує 64Кб.

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

  • Якщо програма використовує тимчасові буфери для зберігання даних.

  • Якщо програма повинна трактувати ті самі дані, як дані різних типів.

  • Якщо програма використовує зв'язані списки даних.

Опис типу вказівник.

Опис типу вказівник складається символу ^, після якого стоїть ідентифікатор базового типу або зарезервоване слово pointer:

^ <базовий тип>

або

^роіntег,

де <базовий тип> – це ідентифікатор типу. Якщо базовий тип є ще не описаним ідентифікатором, то він повинен бути описаний в тому самому розділі опису типів, що і тип вказівник.

Наприклад,

var

C: ^іntеgег;

D: ^rеаl;

type

Student = ^Studentrecord;

Studentrecord = record

Priz:string;

Name: string;

Next: Student

end;

Змінна С є вказівником на дані цілих чисел, змінна D є вказівником на дані дійсного типу. При описі типу Student відбулось посилання на тип Studentrecord, який попередньо в програмі не був описаний.

Вбудований тип Pointer означає нетипований вказівник, тобто вказівник, який не вказує ні на який конкретний тип даних. З їх допомогою зручно динамічно розташовувати дані, структура і тип яких змінюються в ході роботи програми. Значення типу Pointer сумісні з усіма іншими типами вказівників.

Розіменування вказівників.

Змінні типу вказівник можуть містити адреси даних певного типу або значення, яке позначається словом nil. Зарезервоване слово nil означає константу зі значенням вказівника, який ні на що не вказує (порожній вказівник). Одержання доступу до даних, на які вказують змінні вказівного типу, називають розіменуванням вказівників. Щоб розіменувати вказівник, потрібно розташувати символ "^" після змінної вказівного типу. З розіменованим вказівником можна працювати так само, як і зі змінною того типу, на який він вказує.

Приклади.

type

Pinteger = ^іntеgег;

Mas = аrrау[ 1..10] of rеаl;

Pmas = ^Маs;

PString = ^Strіng;

Tzap = record {базовий тип запис}

Prizv : string[20];

RikNar : word;

end;

var

і : pinteger; {вказівник на ціле число}

а: pmas; {вказівник на масив цілих чисел}

s: pstring; {вказівник на рядок}

pzaps : ^ tzар; {вказівник на запис}

begin ...

{вважаємо, що змінним І, А, S, Pzaps вже присвоєні відповідні адреси}

І^ := 10; {у пам'ять за адресою, на яку вказує змінна І, записати значення 10}

А^[10] := 2.2; {десятому елементу масиву дійсних чисел, адреса якого знаходиться в змінній А, присвоїти значення 2.2}

wrіtеln(S^); {вивести на екран рядок символів, на який вказує адреса, що міститься в змінній S}

Рzарs^.RіkNаr:= 1999; {у поле RikNar запису, адреса якого знаходиться в Pzaps, записати 1999}

end.

Змінні типу Pointer а також вказівники, що вказують на певний тип даних, але приймають значення nil, не можуть бути розіменовані: символ " ^ " після такої змінної призводить до помилки.

Присвоєння вказівникам адреси статичних змінних.

Змінним вказівного типу може присвоюватися адреса оголошених в програмі змінних, які розташовані в сегменті даних. Найчастіше так поступають тоді, коли одні і ті самі дані в програмі потрібно інтерпретувати, як дані різного типу. У програмі оголошують змінну певного типу, у якій зберігатимуться ці дані і вказівник на змінну іншого типу. Адресу змінної присвоюють вказівнику, що забезпечує подвійний доступ до одних і тих самих даних і дозволяє різну їх інтерпретацію. Адреса присвоюється змінній вказівнику операцією @:

<Ідентифікатор вказівника> := @<Ідентифікатор змінної>

Операція @ присвоює змінній вказівнику адресу області пам'яті, що містить статичну змінну.

Приклад.

type

Ваrrау = аrrау [1..4] of byte;

var

Р: ^Ваrrау;

К : Longint; {К - змінна довгого цілого типу. Довжина – 4 байти.}

Begin

Р := @К; {Після цього присвоєння звертання Р^[l], Р^[2], Р^[3], Р^[4] реалізують доступ до кожного байту змінної К окремо}

end.

Створення динамічних змінних.

Динамічні змінні і значення вказівників створюються за допомогою стандартних процедур New і GetMem.

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

New(var Р : <змінна-вказjвник>)

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

Крім процедури New існує ще й функція New.

Опис функції має вигляд:

New( <тип вказівника>):<значення адреси>;

Особливістю цієї функції є те, що її аргументом є не константа чи змінна певного типу, а власне тип динамічної змінної, яку вона повинна створити. Результатом виконання функції є вказівник конкретного типу.

Наприклад:

tуре

Pinteger = ^Intеgег;

var Ipoint: Pinteger;

{наступні два оператори еквівалентні:}

New(IPoint); { IPoint – вказівник на ціле число, створений процедурою New}

IPoint := New(PInteger); {Змінній IPoint присвоюється вказівник на ціле число, створений функцією New}

Динамічні змінні, створені процедурою чи функцією New, знищуються процедурою Dispose.

Опис процедури: Dispose(var Р : pointer).

Параметр Р є змінною-вказівником і може мати тип, що відповідає будь-якому типу вказівника. Змінна Р повинна бути попередньо створена з допомогою процедури New, або їй було присвоєне значення такої змінної за допомогою оператора присвоювання. Процедура знищує змінну, на яку вказує Р і повертає зайняту нею пам'ять в область динамічно розподілюваної пам'яті. Значення Р стає невизначеним.

Іншу можливість створювати динамічні змінні надає процедура GetMem. Процедура GetMem створює нову динамічну змінну вказаного розміру і розташовує адресу блоку в змінну-вказівник.

Опис процедури: GetMem(var Р : pointer; Sіze: word).

Параметр Р є змінною-вказівником і може відноситися до будь-якого типу вказівників. Параметр Size має тип word і задає розмір у байтах області пам'яті, яку слід виділити для динамічної змінної. Найбільший блок, що може бути виділений в області динамічно розподілюваної пам'яті складає 65521 байт (64Кб).

Динамічні змінні, створені процедурою чи функцією GetMem, знищуються процедурою FreeMem. Процедура FreeMem знищує динамічну змінну вказаного розміру.

Опис процедури: FreeMem(var Р : pointer; Size : word)

Параметр Р є вказівником на змінну, для якого було попередньо виконане присвоювання за допомогою процедури GetMem, чи якому було присвоєно значення за допомогою оператора присвоювання. Параметр Size – це вираз, результат якого має тип word, що задає розмір у байтах динамічної змінної, котра знищується. Його значення повинне точно співпадати з обсягом пам'яті, відведеним раніше для цієї змінної процедурою GetMem. Процедура FreeMem знищує змінну, на яку вказує Р, і звільняє зайняту нею область пам'яті в області динамічно розподілюваної пам'яті. Якщо вказівник Р не посилається на область динамічно розподілюваної пам'яті, то під час виконання програми виникає помилка. Після звертання до процедури FreeMem значення вказівника Р стає невизначеним, а наступні посилання вигляду P^ будуть призводити до помилки.

Найчастіше динамічні змінні використовуються у списках.

Списки.

Списки використовують для опрацювання і тимчасового зберігання даних у випадках, коли застосування традиційних структур даних, наприклад масивів, недоцільне або неможливе. Список – це ланцюжок пов'язаних один з одним елементів списку, розташованих в області динамічно розподілюваної пам'яті. Елемент списку має тип запис (базовий тип). Одне з полів елемента списку обов'язково є вказівником на базовий тип. Адреса першого елемента списку обов'язково зберігається у змінній, що є вказівником на базовий тип. У полях цього запису зберігається необхідна інформація, а в полі, що є вказівником на базовий тип, зберігається адреса наступного запису або значення nil, якщо цей елемент списку є останнім. Такий список називають однонаправленим. У записі базового типу можна запровадити ще одне поле, що є вказівником на базовий ТИП, і записувати в ньому адресу попереднього елемента списку або значення nil, якщо цей елемент списку є першим. Такий список називають двонаправленим.

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

Розглянемо детальніше деякі аспекти роботи зі списками на прикладі однонаправлених списків. У розділі описі в типів повинні буди описані тип елемента списку і вказівник на тип елемента списку.

type

<тип елемента> = record

<список полів даних>;

<поле-вказівник> : <вказівник на тип елемента>;

еnd;

<вказівник на тип елемента> = ^<тип елемента>;

<тип елемента> – ідентифікатор типу елемента, <список полів даних> – описи типів полів запису, <поле-вказівник> – ідентифікатор поля для вказівника на наступний елемент, <вказівник на тип елемента> – ідентифікатор типу, що вказує на тип даних <тип елемента>. В розділі оголошення змінних повинні бути оголошені принаймні дві змінні типу <вказівник на тип елемента>. Одна з них повинна завжди вказувати на адресу першого елемента списку. Друга потрібна для того, щоб вказувати на поточний елемент списку. Наприклад:

type

TVkaz = ^ТурZар

TypZap = record

роlе 1 : string[20];

роlе2 : word;

роlе3 : string[5];

роlе4: bооlеаn;

polepointer : TVkaz;

end;

var

First, element, NewElement : ТVkaz;

Спершу створюють перший елемент списку. Адресу, що вказує на цей елемент, розташовують у спеціально відведену для цього змінну, яку не змінюють до кінця роботи з цим списком. У поля цього елемента списку заносять інформацію. У поле, що вказує на наступний елемент cписку, доцільно відразу після створення елемента списку занести значення nil.

Наприклад:

New(NewElement);

element := NewElement;

First := NewElement;

Fігst^.роlероіпtеr := nіl;

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

New(NewElement); {створюється нова динамічна змінна}

Еlеmеnt^.роlероіntеr := NewElement; { адреса нової змінної записується у поле, що вказує на наступний елемент в останньому елементі списку}

Element := NewElement; {адресу нової змінної записують у змінну, що вказує на поточний елемент у списку}

Еlеmеnt^.роlероіntеr:= nil; {ознака останнього елементу в списку}.

Після того, як робота зі списком закінчена, його можна знищувати. Для цього слід знищити кожну динамічну змінну, що входить у список. Найпростіше це зробити, організувавши цикл. У циклі переглядається кожен елемент, починаючи з першого у списку, записується у змінну адреса наступного елемента списку, а поточний елемент списку знищується. Значення nіl в полі, що вказує на наступний елемент списку, є ознакою кінця списку. Наприклад:

Element := First;

while Element <> nil do begin

NewElement := Element^.polepointer;

Dispose(Element);

Element:= NewElement;

end;

Після виконання цього циклу кожний елемент списку буде знищено і значення змінних First, Element, NewElement стають невизначені.

Приклади програм.

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

program Vkazivnyky;

type

Tzap = rесоrd {тип запису у файлі}

Prizv : string[25]; {прізвище}

R_pryjomu: word; {рік прийому на роботу}

Adresa: string[35]; {домашня адреса}

end;

ТР = ^Tspys; {тип, що вказує на запис Tspys }

Tspys = record {тип елемента у списку}

Prizv : string[25]; {прізвище}

R _pryjomu : word; {рік прийому на роботу}

Adresa : string[35]; {домашня адреса}

Р : ТР; {вказівник на наступний елемент списку}

end;

var

zapys : Tzap;

f: filе of Tzap; {файлова змінна}

i: integer;

Adrl, AdrN, AdrN1, AdrP : ТР; {змінні, які містять адреси на елементи списку}

begjn

assign( f,' d: \Sріvrob .Zap');

reset(f); { відкривається файл}

Adrl :=nіl; {значення адреси першого елемента списку встановлюється nіl}

Adrp:= nil; {значення адреси поточного елемента списку встановлюється nil}

{читання файлу i створення списку з його елементів}

while not eof(f) do begjn

read(f, zapys); {читання запису файла}

new(AdrN); { створення нового елемента списку, адреса якого поміщується в AdrN}

with zapys do begin {переписується інформація, прочитана з файла}

АdrN^.Ргіzv:=Ргіzv; {у створений елемент списку}

АdrN^.R_pryjomu :=R_pryjomu;

АdrN^.Аdгеsа:= Adresa;

АdrN^.Р := nіl; { адреса наступного елементу = nіl }

end;

if Adrl = nіl then begin {якщо створено перший елемент списку}

Adrl := AdrN; {присвоюється значення змінній Adrl, що містить адресу першого елемента}

AdrP:= AdrN; {присвоюється значення змінній AdrP, що містить адресу поточного елемента}

end else begin {якщо це не перший елемент списку}

АdrР^.Р := AdrN; {записується адреса нового елемента списку у поле Р попереднього елемента списку}

AdrP:= AdrN; {AdrP містить адресу поточного елемента списку}

end;

end;

{виведення списку на екран}

writeln; { пропуск рядка}

AdrP := Adr1; {поточним елементом списку стає перший елемент}

while AdrP <> nіl do begin {перегляд списку до кінця}

{виведення елемента списку на екран}

writеln(АdrР^.Ргіzv, " :(25-lеngth(АdrР^.Ргіzv)), АdrР^.R_рrуjоmu, ", АdrР^.Аdгеsа);

АdrР:=АdrР^.Р; { поточним елементом списку стає наступний елемент }

end;

{вилучення зі списку всіх записів про співробітників, що працюють більше 10 років}

AdrP := Adrl; {AdrP містить адресу поточного елемента списку}

{вилучення зі списку першого запису про співробітника, що працює більше 10 років} while AdrP <> nіl do { перегляд списку до кінця}

if 2001-АdrР^.R_pгуjоmu < 10 then break {знайдено першого співробітника, що працює < 10 років}

else begin {ще не знайдено першого співробітника, що працює < 10 років}

AdrN:=AdrP; {адреса поточного запису розташовується в AdrN}

АdrР:=АdrР^.Р; {в AdrP поміщається адреса наступного запису}

Dispose(AdrN); {знищується динамічна змінна, на яку вказує вказівник в AdrN}

Adrl := AdrP; {адреса першого елемента списку співпадає з адресою поточного елементу}

end;

AdrN := Adrl; { у AdrN розташовується адреса останнього елемента списку, який задовольняє умові < 10 р.}

if AdrN <> nil then AdrP := Adrl^.Р else AdrP := nil; { AdrP – адреса поточного елементу} {вилучається зі списку поточний запис (не перший), про співробітника, що працює більше 10 років}

while AdrP <> nil do begin

if 2001-АdrР^.R_ргуjоmu < 10 then begin {умова виконується. Елемент залишається в списку}

AdrN := AdrP; { адреса поточного елемента стає адресою попереднього елементу }

АdrР := АdrР^.Р; { нова адреса поточного елемента }

end

else begin {умова не виконується. Елемент викидається зі списку}

AdrN1 :=AdrP; {адреса поточного елемента списку поміщається в AdrN1}

АdrР:=АdrР^.Р; {поточним стає наступний елемент списку}

Dispose( AdrN1); {знищується динамічна змінна, на яку вказує вказівник в AdrN1}

АdrN^.Р := AdrP; {попередній елемент списку вказує на поточний}

end;

end;

{виведення списку на екран}

writeln;

AdrP := Adr1;

while AdrP <> nil do begin

writеln(АdrР^.Ргіzv, ":(25-lепgth(АdrР^.Ргіzv)), АdrР^.R_ргуjоmu, " , АdrР^.Аdгеsа);

АdrР:=АdrР^.Р;

end;

{Звільнення пам'яті}

AdrP := Adrl; {поточним елементом списку стає перший елемент}

while AdrP <> nil do begin { перегляд списку до кінця }

AdrN:=AdrP; {адреса поточного елемента списку поміщається в AdrN}

АdrР:=АdrР^.Р; {поточним стає наступний елемент списку}

Dispose(AdrN); {знищується динамічна змінна, на яку вказує вказівник в AdrN}

end;

end.

Питання для самоконтролю:

  1. Які змінні називаються статичними?

  2. Що таке динамічні змінні?

  3. Що називають областю динамічно розподілюваної пам’яті?

  4. За допомогою чого відбувається доступ до статичних змінних?

  5. Що таке вказівники?

  6. В яких випадках доцільно використовувати вказівники?

  7. З чого складається опис типу вказівник?

  8. Що означає вбудований тип Pointer?

  9. Що називають розіменуванням вказівників?

  10. Якою операцією адреса присвоюється змінній вказівника?

  11. За допомогою яких процедур створюються динамічні змінні і значення вказівників?

  12. Який синтаксис процедури New?

  13. Якою процедурою знищуються динамічні змінні, створені процедурою New?

  14. Який опис процедури GetMen?

  15. Якою процедурою знищуються динамічні змінні, створені процедурою GetMen?

  16. Для чого використовують списки?

  17. Що таке список?

  18. Що таке однонаправлений список?

  19. Який список називається двонаправлений?

  20. В якому розділі і як описують списки?