Глава 10. ПРОЦЕДУРЫ И ФУНКЦИИ
В языке Паскаль имеется две разновидности подпрограмм - процедуры и функции.
Структура любой подпрограммы аналогична структуре всей программы. Подпрограмма должна быть
описана до того, как она будет использована в программе или другой подпрограмме.
Все параметры, которые использует подпрограмма, можно разбить на две категории: локальные
параметры, объявленные внутри подпрограммы и доступные только ей самой, и глобальные -
объявленные в основной программе и доступные как программе, так и всем ее подпрограммам.
Обмен информацией между основной программой и подпрограммой может осуществляться только
с помощью глобальных параметров.
Подпрограмма может использовать глобальные параметры двояким образом:
- непосредственно обращаясь к глобальному параметру по его имени или используя механизм
формальных параметров.
- Подпрограмма может непосредственно использовать любые глобальные параметры за
исключением тех, которые имеют те же имена, что и ее локальные параметры.
Механизм формальных параметров будет рассмотрен в п. 10.3.
10.1. Процедура
Подпрограмма-процедура предназначена для выполнения какой-то законченной последовательности
действий. Любая процедура начинается с заголовка. В отличие от основной программы заголовок
в процедуре обязателен. Он состоит из зарезервированного слова procedure, за которым
следует идентификатор имени процедуры, а далее в круглых скобках - список формальных
параметров:
procedure <имя процедуры>(<список формальных параметров>);
За заголовком могут идти такие же разделы, что и в основной программе.
В отличие от основной программы процедура завершается не точкой, а точкой с запятой.
Пример. Процедура ввода N целых чисел.
Пусть в основной программе определен тип
type tArr = array[1..100] of Integer;
Процедура может иметь вид:
procedure InpInt(var Mas: tArr; N: Integer);
{заголовок процедуры со списком формальных параметров, проверка N <= 100 -
в основной программе}
var
i: Integer; {локальный параметр - параметр цикла}
begin
WriteLn('Введите ', N, ' целых чисел');
for i := 1 to N do
Read(Mas[i])
end;
Для вызова процедуры из основной программы или другой подпрограммы следует записать
оператор, состоящий из имени процедуры и списка фактических параметров, которые должны
совпадать по количеству и типам с формальными параметрами процедуры.
Например: InpInt(M,K);
означает, что вызывается процедура Inplnt для ввода К целых чисел в массив М. Естественно,
что в этом случае параметр К целого типа, а М - массив типа tArr.
10.2. Функция
Подпрограмма-функция предназначена для вычисления какого-либо параметра, у этой
подпрограммы два основных отличия от процедуры.
- Первое отличие функции в ее заголовке. Он состоит из слова function, за которым следует
имя функции, далее в круглых скобках - список формальных параметров (о формальных
параметрах см. п. 10.3), затем через двоеточие записывается тип функции - тип
возвращаемого параметра. Функция может возвращать параметры следующих типов: любого
порядкового, любого вещественного, стандартного типа string, любого указателя, в том
числе и типа PChar.
- Второе отличие заключается в том, что в теле функции хотя бы раз имени функции должно
быть присвоено значение.
Пример. Функция вычисления факториала числа N.
function Factorial(N: Byte): Longint;
var
Fact: Longint;
i: Byte;
begin
Fact := N;
for i := N-l downto 2 do
Fact := Fact * i;
Factorial := Fact
end;
Если имя функции внутри ее описания используется в правой части оператора присваивания,
то это означает, что функция вызывает себя рекурсивно (см. п. 10.6).
Для вызова функции из основной программы или другой подпрограммы следует в выражении, где
необходимо использовать значение функции, указать имя функции со списком фактических
параметров, которые должны совпадать по количеству и типам с формальными параметрами
функции, например:
Part:=Sqr(T)/Factorial(i) ;
В этом операторе:
Sqr(T) - вызов стандартной функции возведения в квадрат с фактическим параметром Т;
Factorial (i) - вызов функции, вычисляющей факториал с фактическим параметром i.
10.3. Формальные и фактические параметры
Формальные параметры подпрограммы указывают, с какими параметрами следует обращаться к
этой подпрограмме (количество параметров, их последовательность, типы). Они задаются в
заголовке подпрограммы в виде списка формальных параметров, разбитого на группы,
разделенные точками с запятыми. В группу формальных параметров включаются однотипные
параметры одной категории.
Все формальные параметры можно разбить на четыре категории:
- параметры-значения (значения этих параметров основной программы подпрограммой не
меняются);
- параметры-переменные (значения этих параметров основной программы подпрограмма может
изменить);
- параметры-константы (используются только в версии 7.0);
- параметры-процедуры и параметры-функции (т. е. процедурного типа).
Для каждого формального параметра следует указать имя и, как правило, тип, а в случае
параметра-переменной или параметра-константы - его категорию. Имена параметров могут быть
любыми, в том числе и совпадать с именами объектов программы. Необходимо лишь помнить, что
в этом случае параметр основной программы с таким именем становится недоступным для
непосредственного использования подпрограммой. Тип формального параметра может быть
практически любым, однако в заголовке подпрограммы нельзя вводить новый тип. Например,
нельзя писать:
function Max(A: array[1..100] of Real): Real;
Чтобы правильно записать этот заголовок, следует в основной программе ввести тип-массив,
а затем использовать его в заголовке:
type tArr = array[1..100] of Real;
function Max(A: tArr): Real;
При обращении к подпрограмме формальные параметры заменяются на соответствующие
фактические вызывающей программы или подпрограммы.
10.3.1. Параметры-значения
Параметры-значения передаются основной программой в подпрограмму через стек в виде их
копий и, следовательно, собственный (т.е. фактический) параметр программы подпрограммой
измениться не может.
Параметр-значение указывается в заголовке подпрограммы своим именем и через двоеточие -
типом. Тип параметра-значения может быть любым за исключением файлового.
Если параметров-значений одного типа несколько, их можно объединить в одну группу,
перечислив их имена через запятую, а затем уже указать общий тип. Как отмечалось выше,
отдельные группы параметров отделяются друг от друга точкой с запятой.
Пример.
procedure Inp(Max, Min: Real; N: Word);
function Mult(X, Y: Integer): Real;
В качестве фактического параметра на месте параметра-значения при вызове подпрограммы
может выступать любое выражение совместимого для присваивания типа (см. п. 9.3), не
содержащее файловую компоненту, например:
Inp(Abs(Z), -Abs(T), 2 * К);
M:=Mult(X + Y, X - Y);
MA:=Max(B, 5);
Пример. Функция вычисления максимального элемента в массиве.
Пусть в основной программе определен тип-массив, массив этого типа и переменная целого
типа
type
tArr = array[l..100] of Integer;
var
Massiv: tArr;
Maxim: Integer;
Функция в этом случае может иметь вид:
function Max(Mas: tArr; N: Byte): Integer;
var Ma: Integer;
i: Byte;
begin
Ma := Mas[1];
for i := 2 to N do
if Ma < Mas[i] then
Ma := Mas[i];
Max := Ma
end;
Теперь, например, для определения максимального числа из первых пяти чисел массива Massiv
и записи его в переменную Maxim можно записать оператор:
Maxim := Max(Massiv,5);
Следует иметь в виду, что подпрограмма может работать только с массивами типа tArr. Для
массивов другого типа придется создавать другую аналогичную подпрограмму. Кроме того, при
работе подпрограммы в стеке будет создана копия исходного массива, что приводит к
уменьшению быстродействия и заполнению стека излишней информацией.
10.3.2. Параметры-переменные
При передаче параметров-переменных в подпрограмму фактически через стек передаются их
адреса в порядке, объявленном в заголовке подпрограммы. Следовательно, подпрограмма имеет
доступ к этим параметрам и может их изменять.
Параметр-переменная указывается в заголовке подпрограммы аналогично параметру-значению, но
только перед именем параметра записывается зарезервированное слово var. Действие слова var
распространяется до ближайшей точки с запятой, т. е. в пределах одной группы.
Пример.
procedure MaxMin(A: tArr; var Max, Min: Real; N: Word);
Здесь Max, Min - параметры-переменные, А и N - параметры-значения.
Тип параметров-переменных может быть любым, включая и файловый.
При вызове подпрограммы на месте параметра-переменной в качестве фактического параметра
должна использоваться переменная идентичного типа (см. п. 9.1). Так, если формальный
параметр имеет тип, определенный следующим образом:
type tArr = array[l..100] of Integer;
то и фактический параметр должен быть переменной или типизированной константой типа tArr.
Пример. Функция вычисления максимального элемента в массиве.
Модифицируем подпрограмму примера п. 10.3.1, используя в качестве первого параметра
параметр-переменную:
function Max(var Mas: tArr; N: Byte): Integer;
var Ma: Integer;
i: Byte;
begin
Ma := Mas[1];
for i := 2 to N do
if Ma < Mas[i] then
Ma := Mas[i];
Max := Ma
end;
Этот вариант лучше предыдущего тем, что в данном случае в стеке не создается копия
исходного массива, что улучшает быстродействие и экономит память. Однако при такой
передаче параметра возможно его нежелательное изменение (такой вариант передачи параметра
допустим только в таких небольших подпрограммах, как в данном примере, когда программист
может проконтролировать отсутствие несанкционированного изменения параметра). Недостаток
же, связанный с тем, что подпрограмма может работать только с одним типом массивов,
остается.
10.3.3. Параметры-константы
Часто в качестве параметра в подпрограмму следует передать ту или иную переменную, но
изменять ее подпрограмма не должна. В этом случае нежелательно передавать этот параметр
как параметр-переменную. Можно его передать как параметр-значение, однако, если эта
переменная имеет большой размер (массив, запись и т. д.), то копия такого параметра займет
большую часть стека и даже может его переполнить. Это же приводит и к уменьшению
быстродействия программы. В этой ситуации параметр лучше передать как параметр-константу.
Такой параметр, если он структурированного типа, передается своим адресом, но
предусматривается защита от его изменения. Использовать параметр-константу можно только в
версии 7.0.
Параметр-константа указывается в заголовке подпрограммы аналогично параметру-значению, но
перед именем параметра записывается зарезервированное слово const. Действие слова const
распространяется до ближайшей точки с запятой, т. е. в пределах одной группы.
Пример.
function NewString(const S: string): string;
Тип параметра-константы может быть любым за исключением файлового.
При вызове подпрограммы на месте параметра-константы в качестве фактического параметра
можно использовать любое выражение совместимого для присваивания типа (см. п. 9.3),
не содержащего файловую компоненту.
Параметр-константу нельзя передавать в другую подпрограмму в качестве фактического
параметра.
Пример. Функция вычисления максимального элемента в массиве.
В примере п. 10.3.1 используем в качестве первого параметра параметр-константу:
function Max(const Mas: tArr; N: Byte): Integer;
var
Ma: Integer;
i: Byte;
begin
Ma := Mas[l] ;
for i := 2 to N do
if Ma < Mas[i] then
Ma := Masli];
Max := Ma
end;
10.3.4. Параметры без типа
В Turbo Pascal можно использовать параметры-переменные и параметры-константы без указания
типа. В этом случае фактический параметр может быть переменной любого типа, а
ответственность за правильность использования того или иного параметра возлагается на
программиста.
Пример.
function Equal(var Paraml, Param2; Len: Word): Boolean;
Здесь Paraml, Param2 - параметры-переменные без типа (вместо них можно использовать,
например, любые переменные простого типа, типа массив, типа запись и т. д.);
Len - параметр-значение.
Следует иметь в виду, что параметр без типа внутри подпрограммы типа не имеет и его перед
использованием следует преобразовать к конкретному типу, применяя идентификатор
соответствующего типа так, как это указывалось в п. 9.4, при этом полученный результат
может быть любого размера.
Пример. Функция вычисления максимального элемента в массиве.
Рассмотрим другой вариант подпрограммы примера п. 10.3.1, используя в качестве первого
параметра параметр-переменную без типа:
function Max(var Mas; N: Byte): Integer;
type
tArray = array[1..Maxint] of Integer;
{тип массива максимального размера}
var
Ma: Integer;
i: Byte;
begin
Ma := tArray(Mas)[1];
for i := 2 to N do
if Ma < tArray(Mas)[i] then
Ma := tArray(Mas)[i];
Max := Ma
end;
В этом случае в качестве первого передаваемого параметра можно использовать любой массив
(и не только массив), так что подпрограмма становится более универсальной. Тем не менее
здесь необходимо передавать в качестве второго параметра фактический размер информации,
что не очень удобно.
10.3.5. Массивы и строки открытого типа
В версии 7.0 можно в качестве параметров-переменных использовать массивы и строки
открытого типа, у которых не задаются размеры. В качестве фактического параметра в этом
случае можно использовать массив или строку любого размера, однако массив должен состоять
из тех же компонент, что и компоненты открытого массива. Такие параметры введены для того,
чтобы подпрограмма могла обрабатывать массив или строку любого размера. Фактический размер
массива в этом случае может быть определен с помощью функции High (см. п. 16.1). Открытый
массив задается как и обычный массив, но только без указания типа индекса. Следует иметь в
виду, что индексация элементов открытого массива всегда начинается с нуля, а максимальный
индекс элемента равен значению функции High.
Пример. Функция вычисления максимального элемента в массиве.
Рассмотрим вариант подпрограммы примера п. 10.3.1, используя в качестве передаваемого
параметра массив открытого типа;
function Max(var Mas: array of Integer): Integer;
var
Ma: Integer;
i: Byte;
begin
Ma := Mas[0];
for i := 1 to High(Mas) do {цикл до наибольшего индекса}
if Ma < Mas[i] then
Ma := Mas[i];
Max := Ma
end;
В этом примере в подпрограмму передается только один параметр и она может работать с любым
одномерным массивом целых чисел. Однако следует иметь в виду, что при работе подпрограммы
для открытого массива в стеке опять-таки создается его копия, что может его переполнить.
Разновидность открытого массива - открытая строка, которая может задаваться либо с помощью
стандартного типа OpenString, либо с помощью типа string и использования ключа компилятора
{$Р+} (см. п. 17.7.1), например заголовок процедуры, заполняющей каким-либо символом
строку, может иметь вид:
procedure FillChar(var Str: OpenString; Ch: Char);
или
{$P+}
procedure FillChar(var Str: string; Ch: Char);
10.3.6. Параметры-процедуры и параметры-функции
Передаваемым параметром может быть также параметр-процедура или параметр-функция, т. е.
параметр процедурного типа. Фактически этот параметр является параметром-значением, т. к.
записывается без зарезервированного слова var
В качестве фактического параметра в этом случае используется соответствующая процедура или
функция, имеющая необходимое количество параметров требуемых типов.
Для параметров-процедур и параметров-функций существуют те же правила, что и для других
переменных процедурного типа: подпрограммы должны компилироваться с ключом {$F+} или иметь
директиву far, не должны быть стандартными подпрограммами, не должны объявляться внутри
других подпрограмм, не иметь директив inline или interrupt.
Пример. Программа, печатающая таблицы сложения и умножения двух целых чисел в заданном
диапазоне.
program EXAMPLE15;
type
Func = function(X, Y: Integer): Integer;
{$F+}
function Add(X, Y: Integer): Integer;
begin
Add := X + Y
end;
function Multiply(X, Y: Integer): Integer;
begin
Multiply := X * Y
end;
{$F-}
procedure PrintTable(A, B: Integer; Operation: Func);
{процедура печати таблицы}
var
i, j: Integer;
begin
for i := 1 to A do
begin
for j := 1 to В do
Write(0peration(i, j): 5);
WriteLn
end;
WriteLn
end;
begin {начало основной программы}
PrintTable(10, 10, Add);
PrintTable(10, 10, Multiply)
end.
10.4. Процедура EXIT
Как указывалось раньше (см. п. 5.1.2), оператор GOTO нельзя использовать для досрочного
выхода из подпрограммы. В Turbo Pascal с этой целью используется процедура Exit.
Пример. Функция, определяющая первое отрицательное число в массиве.
function Minus(var Massiv; N: Integer): Real;
{Massiv - параметр без типа}
type
Т = array [1..1000] of Real;
var
i: Integer;
begin
Minus := 0;
for i := 1 to N do
if T(Massiv)[i] < 0 then {преобразование типа}
begin
Minus := T(Massiv)[i];
Exit {досрочный выход из функции}
end
end;
10.5. Директивы подпрограмм
Директивы дают дополнительную информацию транслятору о размещении подпрограмм.
10.5.1. Директива FORWARD
Если одна подпрограмма использует другую, а та, в свою очередь, эту первую, то возникает
проблема размещения этих подпрограмм в программе (ни одну из них нельзя поместить перед
другой). Чтобы устранить это противоречие, используется директива forward, позволяющая как
бы разбить на две части одну из подпрограмм.
При использовании директивы forward сначала записывается полный заголовок первой
подпрограммы. Тело этой подпрограммы заменяется директивой forward. Затем полностью
описывается вторая подпрограмма, а уже после этого полностью описывается первая
подпрограмма. При этом можно записать сокращенный заголовок подпрограммы, который включает
слово procedure или function и ее имя. Список формальных параметров и тип подпрограммы
(если это подпрограмма-функция) не указывается.
Пример. Процедура First вызывает процедуру Second, а та, в свою очередь, процедуру First.
procedure First(A, В: Integer); forward; {заголовок первой процедуры}
procedure Second(C: Real); {вторая процедура}
var
X, Y; Integer
begin
. . .
First(X, Y);
. . .
end;
procedure First; {первая процедура}
var Z: Real
begin
. . .
Second(Z);
. . .
end;
Директиву forward можно использовать и просто для более удобного размещения подпрограмм:
сначала описать все заголовки, а затем - сами подпрограммы. Эту директиву не следует
использовать в модулях (см. п. 16) для подпрограмм, объявленных в интерфейсе модуля.
10.5.2. Директивы FAR и NEAR
Как правило, компилятор Turbo Pascal автоматически выбирает адресацию к подпрограмме.
Например, если подпрограмма находится в одном файле с основной программой, то она
компилируется с "ближним" (near) адресом входа и возврата, состоящим только из смещения в
текущем сегменте, а если она находится в модуле, то формируется "дальний" (far) адрес,
состоящий из адреса сегмента и смещения.
В некоторых случаях нужен нестандартный вариант компиляции. Так, например, если
подпрограмма используется для переменных процедурного типа, она независимо от своего
расположения должна компилироваться с получением "дальнего" адреса. В этом случае в
подпрограмме можно использовать директиву far, которая сообщит компилятору, что нужно
формировать именно такой адрес. Эта директива эквивалентна ключу компилятора {$F+}, однако
в отличие от этого ключа действие директивы распространяется только на одну подпрограмму.
Реже используется директива near, которая сообщает компилятору, что подпрограмму следует
компилировать с получением именно такого адреса. Эта директива эквивалентна ключу
компилятора {$F-}, который выбирается по умолчанию. Действие директивы распространяется
только на одну подпрограмму.
10.5.3. Директива EXTERNAL
Директива external позволяет использовать в программе подпрограммы, написанные на языке
ассемблера и скомпилированные отдельно. Эти подпрограммы должны быть скомпонованы с
основной программой, используя ключ
{$L <имя файла>}
( Здесь имя файла - имя того файла (с расширением .OBJ), в котором находятся
скомпилированные объектные модули подпрограмм, написанных на языке ассемблера.
Пример.
function Max(X, Y: Real): Real; external;
procedure Search(var Mas; N: Integer; var Max, Min: Integer); external;
{$L ASMBL.OBJ}
Директиву external следует использовать, если подпрограммы на ассемблере имеют большой
размер и их лучше скомпилировать отдельно, а не использовать встроенный ассемблер или
писать их в кодах процессора, используя директиву inline (см. п. 10.5.5).
10.5.4. Директива ASSEMBLER
Директива assembler позволяет написать подпрограмму полностью на языке
ассемблера. При этом во время компиляции подпрограмма будет автоматически скомпилирована
встроенным ассемблером пакета Turbo Pascal. При отладке такой подпрограммы можно
использовать встроенный отладчик пакета.
Пример. Функция, определяющая максимальное из двух чисел.
function MaxTwo(X, Y: Integer): Integer; assembler;
asm
MOV AX,X
CMP AX,Y
JG @1
MOV AX,Y
@1:
end;
10.5.5. Директива INLINE
Директива позволяет включить в текст программы команды, записанные непосредственно в
машинных кодах. В отличие от других подпрограмм подпрограмма с директивой inline
непосредственно добавляется всюду, где есть ее вызов (фактически она является
макроопределением). Такие подпрограммы могут иметь параметры, которые можно использовать
в тексте подпрограммы, получая их из стека.
Машинные коды в процедуре записываются в круглых скобках побайтно через прямой слеш (/).
Пример. Функция, вычисляющая максимальное из двух чисел.
function MaxTwo(X, Y: Integer): Integer;
inline(
$58/ { POP AX - получение Y из стека }
$5A/ { POP DX - получение Х из стека }
$3B/$C2/ { CMP AX,DX }
$7F/$02/ { JG - переход через одну команду }
$8В/$С2); { MOV AX.DX }
В связи с тем, что такие подпрограммы являются макроопределениями и помещаются всюду, где
осуществляется их вызов, нет необходимости организовывать стандартный вход в подпрограмму
и выход из нее с сохранением и восстановлением регистров. По этой причине такие
подпрограммы являются довольно эффективными по быстродействию и объему занимаемой памяти.
Однако использовать их целесообразно лишь в случаях, когда они являются достаточно
короткими (не более десятка команд).
10.5.6. Директива INTERRUPT
Директива interrupt предназначена для процедур, обрабатывающих прерывания. Такие процедуры
имеют стандартный заголовок:
procedure IntHandler(Flags, CS, IP, AX,
BX, CX, DX, SI, DI, DS, ES, BP: Word); interrupt;
begin
. . .
end;
В заголовке отдельные параметры можно опускать (но только с начала списка), промежуточные
параметры удалять нельзя, например:
procedure IntHandler(DI, ES, BP: Word); interrupt; (неправильный заголовок)
procedure IntHandler(DI, DS, ES, BP: Word); interrupt; (правильный заголовок)
Нельзя в заголовке процедуры обработки прерываний записывать и какие-либо другие параметры.
10.6. Рекурсивные процедуры и функции
Язык Паскаль допускает, чтобы подпрограмма вызывала саму себя (рекурсивное обращение).
Эта возможность связана с тем, что при каждом новом обращении к подпрограмме параметры,
которые она использует, заносятся в стек, причем параметры предыдущего обращения также
сохраняются.
В ряде случаев (обычно для рекурсивных алгоритмов) рекурсивное оформление подпрограммы
может быть более компактным и эффективным, но не следует забывать об опасности
переполнения стека.
Пример. Вариант функции, рекурсивно вычисляющей факториал числа N.
function Factorial(N: Byte): Longint;
begin
if N in [0..1] then
Factorial := 1 else
Factorial := N * Factorial(N - 1)
end;