Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Паскаль / tp3 / tp3 / 8

.doc
Скачиваний:
16
Добавлен:
10.12.2013
Размер:
107.52 Кб
Скачать

Глава 8. Процедуры и функции

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

В данной главе обсуждаются различные способы описания процедуры или функции и их параметры.

Описания процедур

Описание процедуры позволяет связать идентификатор с процедурным блоком. Процедуру можно затем активизировать с помощью оператора процедуры.

┌───────────┐ ╒═╕ ┌────────────────┐ ╒═╕

описание ──│ заголовок ├─│;├─│ тело процедуры ├─│;├───────

процедуры │ процедуры │ ╘═╛ └────────────────┘ ╘═╛

└───────────┘

╒═══════════╕ ┌─────────────┐

заголовок -│ procedure ├─│идентификатор├──┬─────────────────

процедуры ╘═══════════╛ └─────────────┘ │ ‑

┌───────┘ │

│ ┌────────────────┐ │

│ │ список │ │

└─┤ формальных │─┘

│ параметров │

└────────────────┘

┌────────────────────┐

тип параметра ───────│ идентификатор типа ├────┬────

‑ └────────────────────┘ │

│ ╒════════╕ │

└───────────┤ строка │─────────┘

╘════════╛

┌──────┐

тело ┌─────────────────────────────┬─│ блок │──────

процедуры │ ‑ │ └──────┘ ‑

│ ╒════════════╕ ╒═══╕ │ │ │

├──│ interrupt ├───│ ; ├─┘ │ │

│ ╘════════════╛ ‑ ╘═══╛ │ │

│ ╒════════════╕ │ │ │

├──│ near ├─┤ │ │

│ ╘════════════╛ │ │ │

│ ╒════════════╕ │ │ │

├──│ far ├─┤ │ │

│ ╘════════════╛ │ │ │

│ ╒════════════╕ │ │ │

├──│ export ├─┘ │ │

│ ╘════════════╛ │ │

│ ┌────────────────┘ │

│ │ ╒═════════╕ │

│ ├─│ forward ├───────────────│

│ │ ╘═════════╛ │

│ │ ╒══════════╕ │

│ ├─│ external ├──────────────│

│ │ ╘══════════╛ │

│ │ ┌──────────────┐ │

│ └──│ блок asm ├─────────│

│ └──────────────┘ │

│ ╒══════════════════════════╕ │

└────│ директива inline ├─────────┘

╘══════════════════════════╛

В заголовке процедуры указывается имя процедуры и описывается список формальных параметров (если он присутствует).

Примечание: Синтаксис для списка формальных параметров показан далее в этой главе в разделе "Параметры".

Процедура активизируется с помощью оператора процедуры, в котором содержатся имя процедуры и необходимые параметры. Операторы, которые должны выполняться при запуске процедуры, содержатся в операторной части модуля процедуры. Если в содержащемся в процедуре операторе внутри модуля процедуры используется идентификатор процедуры, то процедура будет выполняться рекурсивно (будет при выполнении обращаться сама к себе).

Приведем пример описания процедуры:

procedure NumString(N: integer; var S: string);

var

V: integer;

begin

V := Abs(N);

S := '';

repeat

S := Chr(N mod 10 + Ord('0')) + S;

N := N div 10;

until N = 0;

if N < 0 then S := '-' + S;

end;

Описания near и far

Турбо Паскаль поддерживает две модели вызова процедур - ближнюю (near) и дальнюю (far). С точки зрения объема программы и скорости выполнения ближняя модель вызова более эффективна, но с ней связаны ограничения: процедуры типа near могут вызываться только в том модуле, где они описаны. Процедуры же с дальним типом вызова можно вызывать из любого модуля, но они несколько менее эффективны.

Примечание: О вызовах ближнего и дальнего типа рассказывается в Главе 18 "Вопросы управления".

На основе описания процедуры Турбо Паскаль будет автоматически выбирать правильную модель вызова. Для процедур, описанных в интерфейсной части модуля (interface), используется дальняя модель вызова - их можно вызывать из других модулей. Процедуры, описанные в секции реализации модуля (implementation), имеют ближний тип вызова. Вызываться они могут только из программ данного модуля.

Для некоторых специальных целей может потребоваться использовать модель с дальним типом вызова. Например, в оверлейных задачах обычно требуется, чтобы все процедуры и функции имели дальний тип вызова. Аналогично, если процедура или функция присваивается процедурной переменной, то она также должна использовать дальний тип вызова. Чтобы переопределить автоматический выбор модели вызова компилятором, можно использовать директиву компилятора {$F+}. Процедуры и функции, компилируемые в состоянии {$F+}, всегда будут иметь дальний тип вызова (far), а в состоянии {$F-} компилятор автоматически выбирает корректную модель. По умолчанию используется директива {$F-}.

Чтобы задать конкретную модель вызова, в описании процедуры перед ее блоком можно указать директиву near или far. При наличии такой директивы она переопределяет директиву $F компилятора и автоматический выбор модели вызова.

Описания export

Директива export делает процедуру или функцию "экспортируемой", принудительно используя дальний тип вызова и подготавливая подпрограмму для экспорта путем генерации специального кода входа и выхода.

В Windows требуется, чтобы процедуры и функции были экспортируемыми в следующих случаях:

- процедуры и функции, которые экспортируются DLL (динамически компонуемой библиотекой);

- процедуры и функции вызова.

В Главе 10 "Динамически компонуемые библиотеки" описывается, как можно экспортировать процедуры и функции в DLL. Заметим, что даже если процедура или функция компилируется с директивой export, фактический экспорт процедуры или функции не происходит до тех пор, пока подпрограмма не указывается в операторе exports библиотеки.

Процедуры и функции вызова - это те процедуры и функции в вашей прикладной программе, которые вызываются не вашей прикладной программой, а Windows. Процедуры и функции вызова компилируются с директивой export, но их не нужно указывать в операторе exports. Приведем некоторые общие примеры процедур и функций вызова:

- процедуры Windows;

- диалоговые процедуры;

- перечислимые процедуры вызова;

- процедуры описания памяти;

- специальные процедуры Windows (фильтры).

Описания interrupt

В описании процедуры перед блоком операторов может указыватся директива прерывания (interrupt). Процедура в этом случае рассматривается, как процедура прерывания.

Примечание: Полное описание процедур прерывания приводится в Главе 18 "Вопросы управления".

В данный момент отметим, что процедуры обработки прерывания нельзя вызывать с помощью операторов процедуры, и что в каждой из них задается список параметров, который обязательно должен иметь следующий вид:

procedure MyInt(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP :

word);

interrupt;

Вместо блока операторов в описании процедуры или функции можно записать опережающее описание (описание forward), внешнее описание (описание external) или описание inline.

Описание forward

Описание процедуры, содержащее вместо блока операторов директиву forward, называется опережающим описанием. В каком-либо месте после этого описания с помощью определяющего описания должна определяться процедура. Определяющее описание - это описание, в котором используется тот же идентификатор процедуры, но опущен список формальных параметров и в которое включен блок операторов. Описание forward и определяющее описание должны присутствовать в одной и той же части описания процедуры и функции. Между ними могут описываться другие процедуры и функции, которые могут обращаться к процедуре с опережающим описанием. Таким образом возможна взаимная рекурсия.

Опережающее описание и определяющее описание представляют собой полное описание процедуры. Процедура считается описанной с помощью опережающего описания.

Приведем следующий пример опережающего описания:

procedure Walter(m,n : integer); forward;

procedure Clara(x,y : real);

begin

.

.

.

end;

procedure Walter;

begin

.

.

Clara(8.3,2.4);

.

.

end;

Определяющее описание процедуры может быть внешним описанием (external). Однако, оно не может быть описанием inline или другим опережающим описанием. Определяющее описание также не может содержать директиву interrupt.

Опережающие описания не допускаются в интерфейсной части модуля.

Описания external

Описания external позволяют компоновать отдельно скомпилированные процедуры и функции, написанные на языке Ассемблера. С помощью команды "$L имя_файла" для внешней программы должны быть установлены связи с программой или модулем на языке Паскаль.

Примечание: Более детальное описания компоновки с программой на языке Ассемблера содержится в Главе 23.

Приведем следующие примеры описаний внешних процедур:

procedure MoveWord(var source,dest; count: longint);

external;

procedure MoveLong(var source,dest; count: longint);

external;

procedure FillWord(var dest,data: integer; count: longint);

external;

procedure FillLong(var dest,data: integer; count: longint);

external;

{$L BLOCK.OBJ}

Директиву external можно также использовать для импорта процедур и функций из DLL (динамически компонуемой библиотеки). Например, слкедующее описание external импортирует функцию с именем GlobalAlloc из библиотеки DLL с именем 'KERNEL' (ядро Windows).

function GlobalAlloc(Flags: Word; Bytes: LongInt:

THandle; far; external 'KERNEL' index 15;

Директива external занимает место описания и операторной части импортируемой процедуры или функции. Импортируемые процедуры и функции должны использовать дальний тип вызова, задаваемый директивой far или директивой компилятора {$F}.

Кроме этого требования импортируемые процедуры и функции аналогичны обычным процедурам и функциям.

Примечание: Более подробную информацию об импортировании процедур и функций из библиотек DLL можно найти в Главе 10 "Динамически компонуемые библиотеки".

Описания assembler

Описания assembler позволяют вам написать всю процедуру или функцию на Ассемблере.

Примечание: Более подпробно о процедурах и функциях на Ассемблере рассказывается в Главе 23 "Встроенный Ассемблер".

╒═════════╕ ╒═╕ ┌───────────────┐ ┌────────────┐

блок asm ──┤assembler├─│;├─│раздел описаний├──│оператор asm├─

╘═════════╛ ╘═╛ └───────────────┘ └────────────┘

Описания inline

Директивы inline позволяют записывать вместо блока операторов инструкции в машинном коде. При вызове обычной процедуры компилятор создает код, в котором параметры процедуры помещаются в стек, а затем для вызова процедуры генерируется инструкция CALL. Когда вы вызываете процедуру inline, компилятор генерирует код с помощью директивы inline, а не с помощью инструкции CALL. Таким образом, процедура inline "расширяется" при каждом обращении к ней, аналогично макроинструкции на языке Ассемблера. Приведем два небольших примера процедур типа inline:

procedure DisableInterrupts: inline($FA); { CLI }

procedure EnableInterrupts; inline($FB); { STI }

Примечание: Процедуры типа inline описаны подробно в Главе 22 "Встроенный Ассемблер".

Описания функций

Описание функции определяет часть программы, в которой вычисляются и возвращается значение.

┌───────────┐ ╒═╕ ┌────────────┐ ╒═╕

описание──│ заголовок ├─│;├──│тело функции│──│;├───────────

функции │ функции │ ╘═╛ └────────────┘ ╘═╛

└───────────┘

╒══════════╕ ┌───────────────┐

заголовок -│ function ├─│ идентификатор ├─┬─────────────┐

функции ╘══════════╛ └───────────────┘ │ ‑ │

┌────┘ │ │

│ ┌────────────┐ │ │

│ │ список │ │ │

└│ формальных ├─┘ │

│ параметров │ │

└────────────┘ │

┌───────────────────────────────────────────┘

│ ╒═══╕ ┌────────────────────┐

└─│ : ├────│ тип результата ├──────────────

╘═══╛ └────────────────────┘

┌────────────────────┐

тип результата ──────┬────│ идентификатор типа ├──────┬───────

│ └────────────────────┘ │

│ ╒════════╕ │

└─────────│ строка ├─────────────┘

╘════════╛

┌──────┐

тело функции ────┬───────────────────────────────┬─│ блок ├────

│ ‑ │ └──────┘ ‑

│ ╒══════╕ ╒═╕ │ │ │

├──│ near ├───────────│;├───┘ │ │

│ ╘══════╛ ‑ ╘═╛ │ │

│ ╒══════╕ │ │ │

├──│ far ├──────┤ │ │

│ ╘══════╛ │ │ │

│ ╒══════╕ │ │ │

├──│export├──────┘ │ │

│ ╘══════╛ │ │

│ │ │

│ ┌───────────────────────────┘ │

│ │ │

│ │ ╒═════════╕ ┌──────────┘

│ ├────│ forward ├────────────│

│ │ ╘═════════╛ │

│ │ ╒══════════╕ │

│ ├────│ external ├───────────│

│ │ ╘══════════╛ │

│ │ ┌──────────┐ │

│ └─────│ блок asm ├──────────│

│ └──────────┘ │

│ ┌──────────────────┐ │

└────│ директива inline ├────────┘

└──────────────────┘

В заголовке функции определяется идентификатор функции, формальные параметры (если они имеются) и тип результата функции.

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

В операторной части блока функции задаются операторы, которые должны выполняться при активизации функции. В модуле должен содержаться по крайней мере один оператор присваивания, в котором идентификатору функции присваивается значение. Результатом функции является последнее присвоенное значение. Если такой оператор присваивания отсутствует или он не был выполнен, то значение, возвращаемое функцией, не определено.

Если идентификатор функции используется при вызове функции внутри модуля-функции, то функция выполняется рекурсивно.

Приведем далее примеры описаний функции:

function Max(a: Vector; n: integer): extended;

var

x: extended;

i: integer;

begin

x := a(1);

for i := 2 to n do if x < a[i] then x := a[i];

Max := x;

end;

function Power(x: extended; y: integer): extended;

var

z: extended;

i: integer;

begin

z := 1.0; i := y;

while i > 0 do

begin

if Odd(i) then z := z*x;

x := Sqr(x);

end;

Power := z;

end;

Аналогично процедурам функции могут описываться, как с ближним типом вызова (near), с дальним типом вызова (far), опережающие (forward), внешние (external), ассемблерные (assembler) или встраиваемые (inline). Однако функции прерываний (interrupt) не допускаются.

Описания методов

Объявление метода внутри объектного типа соответствует опережающему объявлению (forward) этого метода. Таким образом, метод должен быть реализован где-нибудь после объявления объектного типа и внутри той же самой области действия метода путем определения объявления.

Для процедурных и функциональных методов определение описания имеет форму обычного объявления процедуры или функции, за тем исключением, что в этом случае идентификатор процедуры или функции рассматривается как идентификатор метода.

Для методов типа конструкторов и деструкторов определение описаний принимает форму описания процедурного метода, за тем исключением, что зарезервированное слово procedure заменяется на зарезервированное слово constructor или destructor. Определение описания метода может повторять (но не обязательно) список формальных параметров заголовка метода в объектном типе. В этом случае заголовок метода должен в точности повторять заголовок в объектном типе в порядке, типах и именах параметров и в типе возвращаемого функцией результата, если метод является функцией.

В определяющем описании метода всегда присутствует неявный параметр с идентификатором Self, соответствующий формальному параметру-переменной, обладающему объектным типом. Внутри блока метода Self представляет экземпляр, компонент метода которого был указан для активизации метода. Таким образом, любые изменения значений полей Self отражаются на экземпляре.

Область действия идентификатора компонента объектного типа распространяется на блоки процедур, функций, конструкторов и деструктора, которые реализуют методы данного объектного типа. Эффект получается тот же, как если бы в начало блока метода был вставлен оператор with в следующей форме:

with Self do

begin

...

end;

Исходя из этих соображений, написание идентификаторов компонентов, формальных параметров метода, Self и любого идентификатора, введенного в исполняемую часть метода, должно быть уникальным.

Ниже приводятся несколько примеров реализаций методов:

procedure Rect.Intersect(var R: Rect);

begin

if A.X < R.A.X then A.X := R.A.X;

if A.X < R.A.Y then A.Y := R.A.Y;

if B.X > R.B.X then B.X := R.B.X;

if B.Y < R.B.Y then B.Y := R.B.Y;

if (A.X >= B.X) or (A.Y >= B.Y) then Init (0, 0, 0, 0);

end;

procedure Field.Display;

begin

GotoXY(X, Y);

Write(Name^, ' ', GetStr);

end;

function NumField.PutStr(S: string): boolean;

var

E: integer;

begin

Val(S, Value, E);

PutStr := (E = 0) and (Value >= Min) and (Value <= Max);

end;

Конструкторы и деструкторы

Конструкторы и деструкторы являются специализированными формами методов. Используемые в связи с расширенным синтаксисом стандартных процедур New и Dispose, конструкторы и деструкторы обладают способностью размещения и удаления динамических объектов. Кроме того, конструкторы имеют возможность выполнить требуемую инициализацию объектов, содержащих виртуальные методы. Как и все другие методы, конструкторы и деструкторы могут наследоваться, а объекты могут содержать любое число конструкторов и деструкторов.

Конструкторы используются для инициализации вновь созданных объектов. Обычно инициализация основывается на значениях, передаваемых конструктору в качестве параметров. Конструктор не может быть виртуальным, так как механизм пересылки виртуального метода зависит от конструктора, который первым совершил инициализацию объекта.

Приведем несколько примеров конструкторов:

constructor Field.Copy(var F: Field);

begin

Self := F;

end;

constructor Field.Init(FX, FY, FLen: integer; FName: string);

begin

X := FX;

Y := FY;

GetMem(Name, Length (FName) + 1);

Name^ := FName;

end;

constructor StrField.Init(FX, FY, FLen: integer; FNAme:

string);

begin

Field.Init(FX, FY, FLen, FName);

GetMem(Value, Len);

Value^ := '';

end;

Главным действием конструктора порожденного (дочернего) типа, такого как указанный выше StrField.Init, почти всегда является вызов соответствующего конструктора его непосредственного родителя для инициализации наследуемых полей объекта. После выполнения этой процедуры, конструктор инициализирует поля объекта, которые принадлежат только порожденному типу.

Деструкторы (сборщики мусора) являются противоположностями конструкторов и используются для очистки объектов после их использования. Обычно очистка состоит из удаления всех полей-указателей в объекте.

Примечание: Деструктор может быть виртуальным и часто является таковым. Деструктор редко имеет параметры.

Приведем несколько примеров деструкторов:

destructor Field.Done;

begin

FreeMem(Name, Length (Name^) + 1);

end;

destructor StrField.Done;

begin

FreeMem(Value, Len);

Field.Done;

end;

Деструктор производного типа, такой как указанный выше StrField.Done, обычно сначала удаляет введенные в порожденном типе поля указателей, а затем в качестве последнего действия вызывает соответствующий деструктор непосредственного родителя для удаления наследованных полей указателей объекта.

Параметры

В описании процедуры или функции задается список формальных параметров. Каждый параметр, описанный в списке формальных параметров, является локальным по отношению к описываемой процедуре или функции и в модуле, связанным с данной процедурой или функцией на него можно ссылаться по его идентификатору.

╒═══╕ ┌─────────────────────┐ ╒═══╕

список ────│ ( ├──│ описание параметров │─┬│ ) ├──────

формальных ╘═══╛ ‑ └─────────────────────┘ │ ╘═══╛

параметров │ ╒═══╕ │

└─────────┤ ; │──────────┘

╘═══╛

┌────────────────────────┐

описание ───┬──────────│ список идентификаторов ├─┬─────────

параметров │ ‑ └────────────────────────┘ │ ‑

│ ╒═════╕ │ ┌─────────────────────┘ │

└│ var ├─┘ │ ╒═══╕ ┌───────────────┐ │

╘═════╛ └│ : ├─│ тип параметра ├─┘

╘═══╛ └───────────────┘

┌────────────────────┐

тип параметра ────┬───────┤ идентификатор типа ├──────────────

│ └────────────────────┘ ‑

│ ╒════════╕ │

├───────────│ string ├────────────┤

│ ╘════════╛ │

│ ╒════════╕ │

└───────────│ file ├────────────┘

╘════════╛

Существует три типа параметров: значение, переменная и нетипизованная переменная. Они характеризуются следующим:

1. Группа параметров, перед которыми отсутствует ключевое слово var и за которыми следует тип, является списком параметров-значений.

2. Группа параметров, перед которыми следует ключевое слово var и за которыми следует тип, является списком параметров-переменных.

3. Группа параметров, перед которыми стоит ключевое слово var и за которыми не следует тип, является списком нетипизованных параметров-переменных.

Параметры-значения

Формальный параметр-значение обрабатывается, как локальная по отношению к процедуре или функции переменная, за исключением того, что он получает свое начальное значение из соответствующего фактического параметра при активизации процедуры или функции. Изменения, которые претерпевает формальный параметр-значение, не влияют на значение фактического параметра.

Соответствующее фактическое значение параметра-значения должно быть выражением и его значение не должно иметь файловый тип или какой-либо структурный тип, содержащий в себе файловый тип.

Фактический параметр должен иметь тип, совместимый по присваиванию с типом формального параметра-значения. Если параметр имеет строковый тип, то формальный параметр будет иметь атрибут размера, равный 255.

Параметры-переменные

Параметр-переменная используется, когда значение должно передаваться из процедуры или функции вызывающей программе. Соответствующий фактический параметр в операторе вызова процедуры или функции должен быть ссылкой на переменную. При активизации процедуры или функции формальный параметр-переменная замещается фактической переменной, любые изменения в значении формального параметра-переменной отражаются на фактическом параметре.

Внутри процедуры или функции любая ссылка на формальный параметр-переменную приводит к доступу к самому фактическому параметру. Тип фактического параметра должен совпадать с типом формального параметра-переменной (вы можете обойти это ограничение с помощью нетипизованного параметра-переменной). Если формальный параметр имеет строковый тип, ему присваивается атрибут длины, равный 255, и фактический параметр должен также иметь строковый тип и атрибут длины, равный 255.

Файловый тип может передаваться только, как параметр-переменная.

При ссылке на фактический параметр-переменную, связанную с индексированием массива или получением указателя на объект, эти действия выполняются перед активизацией процедуры или функции.

Объекты

Правила совместимости объектных типов по присваиванию также применимы к параметрам-переменным объектного типа: для формального параметра типа T1 фактический параметр должен быть типа T2, если тип T2 является наследником типа T1. Например, методу Field.Copy может передаваться экземпляр типа Field, NumField, ZipField или любого другого типа, являющегося производным по отношению к типу Field.

Нетипизованные параметры-переменные

Когда формальный параметр является нетипизованным параметром-переменной, то соответствующий фактический параметр может представлять собой любую ссылку на переменную, независимо от ее типа.

В процедуре или функции у нетипизованного параметра-переменной тип отсутствует, то есть он несовместим с переменными всех типов, пока ему не будет присвоен определенный тип с помощью присваивания типа переменной.

Приведем пример нетипизованных параметров-переменных:

Соседние файлы в папке tp3