Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_Паскал.doc
Скачиваний:
2
Добавлен:
21.09.2019
Размер:
1.21 Mб
Скачать

Процедурні типи

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

Найпростіший випадок опису змінної, значеннями якої можуть бути процедури, приведений нижче.

var

Р: procedure;

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

type

Func = function(х,у:integer):integer;

var

Fl, F2 : Func;

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

function Add( a,b:integer ) : integer;

begin

Add := a+b

end;

те припустиме присвоювання виду Fl := Add;

Зверніть увагу, що в останньому операторі змінній Fl як значення присвоюється ФУНКЦІЯ Fl; у даному випадку виконання цієї функції не відбувається. Після такого присвоювання мається можливість викликати функцію Add як безпосередньо по її імені, так і за допомогою вказівки змінної F 1. Так, наприклад виконання операторів

WriteLn(Add(l,2))

WriteLn(Fl(l,2))

надрукують число 3.

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

Приклади:

type

Proc = procedure;

BinOperation = function (х, у: real) :real;

OnOperation = function(х:real):real;

Reading = procedure(var F:text;var Elem:char);

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

Таким чином. Turbo Pascal дозволяє визначати змінні, значеннями яких можуть бути процедури і функції. Для таких змінних припустимі оператори присвоювання, у правих частинах яких знаходяться ідентифікатори інших процедурних змінних чи ідентифікатори підпрограм. Змінна процедурного типу в різні моменти виконання програми може мати як значення різні підпрограми. Такі змінні можна, зокрема, використовувати для виклику підпрограм, що присвоєні цим змінної. Наприклад, якщо в програмі маються наступні описи:

var

Operation : function (х, у : real) :real;

function Add ( a,b:real ) : real;

begin

Add := a+b

end;

function Sub ( a,b:real ) : real;

begin

Sub := a-b

end;

то виконання послідовності операторів

if Condition then

Operation := Add

elseOperation := Sub;

Write(Operation(2.05,3+X))

приведе до присвоєння змінної Operation, у залежності від істинності умови, або функції Add, або функції Sub. Конструкція Operation (2.05, 3+X) викликає активізацію тієї функції, що була присвоєна змінній Operation. Таким чином, у викликах функцій і в операторах процедури, крім ідентифікаторів відповідних підпрограм, можуть стояти імена змінних процедурних типів. Виконання таких конструкцій буде полягати у виклику підпрограм, що були присвоєні цим змінним.

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

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

type

Proc = procedure ( Т:real );

NoticePtr = ^Notice;

Notice = record

Next : NoticePtr;

Time : real;

Action : Proc

end;

var

NewNotices : array[1..10] of Proc;

Notices : NoticePtr;

з урахуванням цих описів припустимі наступні оператори процедур:

MewNotices[7]((Х+17.5) *2) ;

Notices^.Action(0.25) ;

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

1. Підпрограма, що присвоюється процедурній змінний, повинна бути відтрансльована в режимі "далекого типу викликів". Не поглиблюючи в подробиці системного характеру, досить пояснити, що необхідного ефекту можна досягти, розташувавши перед чи підпрограмою групою підпрограм директиву компілятора $F зі знаком '+' (плюс), а наприкінці цієї групи - таку ж директиву зі знаком '-' (мінус), наприклад:

{$?+} function Add ( a,b:real ) : real;

begin

Add := a+b

end;

function Sub ( a,b:real ) : real;

begin

Sub := a-b

end;

{$F-}

Якщо необхідно поширити дію цієї директиви на всю програму, досить помістити одну директиву {$F+} на самому початку тексту.

Еквівалентом директиви компілятора {$F+} є , службове слово far, що, будучи записаним перед блоком підпрограми, задає для неї далекий тип виклику. (Інший можливий тип виклику - "ближній" - задається службовим словом near у тім же місці опису підпрограми). Таким чином, перша процедура з попереднього приклада може бути записана в наступному виді:

function Add ( a,b:real ) : real; far;

begin

Add := a+b

end;

2. Підпрограма, що присвоюється процедурної змінний, не повинна бути стандартною чи процедурою функцією. Це обмеження при необхідності легко обійти, обеживши виклик стандартної підпрограми "оболонкою", після чого її можна використовувати в присвоюванні, наприклад:

var

Func : function ( S:string ) : byte;

function MyLength ( S:string ) : byte; far;

begin .

MyLength := Length(S)

end;

Func := MyLength;

3. Важливим обмеженням є те, що обговорювані підпрограми НЕ МОЖУТЬ БУТИ вкладеними в інші підпрограми.

4. Нарешті, підпрограми, що присвоюються процедурним змінним, не можуть бути підпрограмами спеціального виду, що містять специфікацію interrupt і конструкцію inline.

Зазначені обмеження можуть стати трохи більш зрозумілими, якщо мати на увазі, що на фізичному рівні змінна процедурного типу містить повну адресу початку коду підпрограми в пам'яті (сегмент і зсув). Фактично, процедурна змінна дуже нагадує змінну посилкового типу, тільки замість покажчика на данні вона містить покажчик на код підпрограми. Процедурна змінна завжди займає в пам'яті 4 байти (два слова), причому в першому слові зберігається зсув, у другому - сегмент.

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

program Tables;

type

Func = function (x,y :integer) : integer;

function Add ( a,b:integer ) : integer; far;

begin

Add := a+b

end;

function Mult ( a,b:integer ) : integer; far;

begin

Mult := a*b

end;

procedure MakeTable ( W,H:integer; Operation:Func );

var

i, j : integer;

begin

{ Формуємо заголовок }

Write (' ':6);

for i:=1 to W do Write(i:5);

WriteLn;

Write(' ':6) ;

for i:=1 to W do Write('-----');

WriteLn;

{ Проводимо обчислення }

for i:=1 to H do

begin

Write(i:5,'|');

for j:=1 to W do Write (Operation(j,i):5) ;

WriteLn

end;

WriteLn

end;

begin { MakeTable }

MakeTable(10,10,Add);

MakeTable(10,10,Mult)

end.

Тіло програми Tables містить два виклики процедури MakeTable, що будує таблиці, використовуючи для обчислень у першому випадку функцію Add (одержуючи таблицю додавання 10х10), а в другому випадку - функцію Mult (одержуючи таблицю множення 10х10).

Необхідно нагадати, що хоча змінні процедурних типів можна передавати як параметри підпрограмам, функції, що повертають значення процедурних типів, НЕ ДОПУСКАЮТЬСЯ.

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

type

IntFunc = function : integer;

var

F : IntFunc;

N : integer;

function Readint : integer; far;

var

i : integer;

begin

Read(i);

Readint := i

end;

begin

F := Readint; { приссвоїти значення процедури )

N := Readint; { присвоїти результат функції }

end.

Перший оператор в основній програмі присвоює значення процедури Readint (її адресу) процедурної змінний F, другий оператор ВИКЛИКАЄ функцію Readint і присвоює отримане значення змінній N. Одержання значення процедури чи виклик функції розрізняються по типу присвоюванної змінної (F чи N).

На жаль, виникають ситуації, коли компілятор з контексту не може визначити бажану дію. Наприклад, у наступному операторі для компілятора неочевидно, повинний він порівняти значення процедури в F зі значенням процедури Readint, чи потрібно викликати процедури F і Readint, а потім порівняти їх значення:

if F = Readint then Writeln('Рівні');

У подібних випадках вважається, що таке входження ідентифікатора чи процедури функції означає виклик функції, тому результатом даного оператора буде виклик F і Readint і порівняння отриманих результатів. Щоб порівняти значення змінної F зі значенням (адресою) процедури Readint, потрібно використовувати наступну конструкцію:

if @F = @ReadInt then Writein('Рівні');

У випадку застосування до ідентифікатора процедури чи функції операції взяття адреси @ запобігає виклику компілятором процедури і у той же час перетворює аргумент у покажчик. Таким чином, @F перетворить F у нетипізований покажчик (pointer), що містить адресу, а @ReadInt повертає адресу Readint. Два значення-покажчики можна порівняти і визначити, чи посилається в даний момент F на Readint.

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

Turbo Pascal цілком підтримує приведення типів змінних для процедурних типів. Наприклад, з урахуванням наступних описів:

type

Func = function ( X:integer) : integer

function MyFunc ( X:integer ) : integer;

begin

MyFunc := X*X

end;

var

F:func

p:pointer

n:integer

можна побудувати наступні присвоювання:

{ змінній F присвоюється функція MyFunc }

F := MyFunc;

( функція MyFunc викликається через змінну F }

N := F(N);

{ Р одержує покажчик на функцію MyFunc }

Р := @F;

{ функція MyFunc викликається через покажчик Р }

N := Func(P)(N);

{ присвоїти значення процедури в Р змінній F }

F := Func(P);

{ присвоїти значення процедури в F покажчику Р }

Func(P) := F;

{ присвоїти значення покажчика в Р змінної F }

@F := Р;

Зауважимо, зокрема, що оператор одержання адреси @, застосований до процедурної змінної, можна використовувати в лівій частині присвоювання. Зверніть також увагу на приведення типів у четвертому операторі при виклику функції через змінну-покажчик.