Заголовок процедуры имеет вид:
PROCEDURE <имя> [(<сп.ф.п.>)];
Заголовок функции:
FUNCTION <имя> {(<сп.ф.п.>)] :<тип>;
Здесь <имя> - имя подпрограммы (правильный
идентификатор);
<сп.ф.п.> - список формальных
параметров;
<тип> - тип возвращаемого функцией
результата.
Сразу за заголовком
подпрограммы может следовать одна из
стандартных директив ASSEMBLER, EXTERNAL, FAR,
FORWARD, INLINE, INTERRUPT, NEAR. Эти директивы
уточняют действия компилятора и
распространяются на всю
подпрограмму и только на нее, т.е. ели
за подпрограммой следует другая
подпрограмма, стандартная директива,
указанная за заголовком первой, не
распространяется на вторую.
ASSEMBLER - эта директива отменяет
стандартную последовательность
машинных инструкций, вырабатываемых
при входе в процедуру и перед выходом
из нее. Тело подпрограммы в этом
случае должно реализоваться с
помощью встроенного ассемблера (см. п.
11.8).
EXTERNAL - с помощью этой
директивы объявляется внешняя
подпрограмма (см. п.11.1).
FAR - компилятор должен создавать код
подпрограммы, рассчитанный на
дальнюю модель вызова. Директива NEAR
заставит компилятор создать код,
рассчитанный на ближнюю модель
памяти. По умолчанию see подпрограммы,
объявленные в интерфейсной части
модулей, генерируются с расчетом на
дальнюю модель вызова, а все
остальные подпрограммы - на ближнюю
модель.
В соответствии с архитектурой
микропроцессора ПК, в программах
могут использоваться две модели
памяти: ближняя и дальняя. Модель
памяти определяет возможность
вызова процедуры из различных частей
программы: если используется ближняя
модель, вызов возможен только в
пределах 64 Кбайт (в пределах одного
сегмента кода, который выделяется
основной программе и каждому
используемому в ней модулю); при
дальней модели вызов возможен из
любого сегмента. Ближняя модель
экономит один байт и несколько
микросекунд на каждом вызове
подпрограммы, поэтому стандартный
режим компиляции предполагает эту
модель памяти. Однако при передаче
процедурных параметров (см.п.8.4), а
также в оверлейных модулях (см. п. 11.б)
соответствующие подпрограммы должны
компилироваться с расчетом на
универсальную - дальнюю модель
памяти, одинаково пригодную при
любом расположении процедуры и
вызывающей ее программы в памяти.
Явное объявление модели
памяти стандартными директивами
имеет более высокий приоритет по
сравнению с настройкой среды (см.прил.1).
FORWARD - используется при опережающем
описании (см. п.8.6) для сообщения
компилятору, что описание
подпрограммы следует где-то дальше
по тексту программы (но в пределах
текущего программного модуля).
INLINE - указывает на то, что тело
подпрограммы реализуется с помощью
встроенных машинных инструкций (см. п.
11.2).
INTERRUPT - используется при создании
процедур обработки прерываний (см. п.
11.4).
8.3.2. Параметры
Список формальных
параметров необязателен и может
отсутствовать. Если же он есть, то в
нем должны быть перечислены имена
формальных параметров и их тип,
например:
Procedure SB (a : real; b : integer: с : char):
Как видно из примера,
параметры в списке отделяются друг
от друга точками с запятой.
Несколько следующих подряд
однотипных параметров можно
объединять в подсписки, например,
вместо
Function F (а : real; b : real) : real:
можно написать проще:
Function F (a, b : real) : real;
Операторы тела
подпрограммы рассматривают список
формальных параметров как
своеобразное расширение раздела
описаний: все переменные из этого
списка могут использоваться в
любых выражениях внутри
подпрограммы. Таким способом
осуществляется настройка
алгоритма подпрограммы на
конкретную задачу.
Рассмотрим следующий
пример. В языке Турбо Паскаль нет
операции возведения в степень,
однако с помощью встроенных
функций LN(X) и ЕХР(Х) нетрудно
реализовать новую функцию с именем,
например, РО WER, осуществляющую
возведение любого вещественного
числа в любую вещественную степень.
В программе (пример 14) вводится
пара чисел X и Y и выводится на экран
дисплея результат возведения X
сначала в степень+У, а затем - в
степень - Y. Для выхода из программы
нужно ввести Ctrl—Z и «Ввод». Пример
14.
var
х, у : real;
FUNCTION Power(a,b : real) : real;
begin (Power)
if a > 0 then Power := exp(b * ln(a))
else
If a < 0 then
Power := exp(b * In(abs(a))) else
If b=0 then Power := 1
else Power := 0
end (Power);
BEGIN {main}
repeat
readIn(x.y);
wrIteln(power(x,y):12:10, power(x,-y):15:10)
untiI EOF
END {main}.
Для вызова функции POWER мы
просто указали ее в качестве
параметра при обращении к
встроенной процедуре WRITELN.
Параметры и Y в момент обращения к
функции - это фактические
параметры. Они подставляются
вместо формальных параметров A и B
заголовке функции и затем над ними
осуществляются нужные действия.
Полученный результаты
присваивается идентификатору
функции - именно он и будет
возвращен как значение функции при
выходе из нее. В программе функции
POWER вызывается дважды - сначала с
параметрами X и Y, а затем X и -У,
поэтому будут получены два разных
результата.
Механизм замены формальных
параметров на фактические
позволяет нужным образом
настроить алгоритм, реализованный
в подпрограмме. Турбо Паскаль
следит за тем, чтобы количество и
тип формальны параметров строго
соответствовали количеству и
типам фактических параметров в
момент обращения к подпрограмме.
Смысл используемых фактических
параметров зависит от того, в каком
порядке они перечислены при вызове
подпрограммы. В примере 14 первый по
порядку фактический параметр
будет возводиться в степень,
задаваемую вторым параметром, а не
наоборот. Пользователь должен сам
следить за правильным порядком
перечисления фактических
параметров при обращении к
подпрограмме.
Любой из формальных
параметров подпрограммы может
быть ли параметром-значением, либо
параметром-переменной. В
предыдущие примере параметры А и В
определены как параметры-значения.
Есть параметры определяются как
параметры-переменные, перед ними
необходимо ставить
зарезервированное слово VAR,
например:
Function Power (var a : real; b : real) : real;
Здесь параметр А - параметр-переменная,
а В -параметр-значение. Определение
формального параметра тем или иным
способом существенно только для
вызывающей программы: если
формальный параметр объявлен как
параметр-переменная, то при вызове
подпрограммы ему должен
соответствовать фактический
параметр в виде переменной
структурного типа; если формальный
параметр объявлен как параметр-значение
то при вызове ему может
соответствовать произвольное
выражение. Koнтроль за
неукоснительным соблюдением этого
правила осуществляет компилятором
Турбо Паскаля. Если бы для
предыдущего примера был
использован такой заголовок
функции:
Function Power ( var a, b : real) : real;
то при втором обращении к функции
компилятор указал бы на
несоответствие типа фактических и
формальных параметров (параметр -Y
ecть выражение, в то время как
соответствующий ему формальный
параметр описан как параметр-переменная).
Для того чтобы понять, в каких
случаях использовать параметры
значения, а в каких - параметры-переменные,
рассмотрим, как осуществляется
замена формальных параметров на
фактические в момент обращения к
подпрограмме.
Если параметр определен как
параметр-значение, то перед
вызовом подпрограммы это значение
вычисляется, полученный результат
копируется во временную память и
передается подпрограмме. Важно
учесть, что даже если в качестве
фактического параметра указано
простейшее выражение в виде
переменной или константы, все
равно подпрограмме будет передана
лишь копия переменной (константы).
Если же параметр определен как
параметр-переменная, то при вызове
подпрограммы передается сама
переменная, а не ее копия. Любые
возможные изменения в
подпрограмме параметра-значения
никак не воспринимаются
вызывающей программой, так как в
этом случае изменяется копия
фактического параметра, в то время
как изменение параметра-переменной
приводит к изменению самого
фактического параметра в
вызывающей программе.
Представленный ниже пример
15 поясняет изложенное. В программе
задаются два целых числа 5 и 7, эти
числа передаются процедуре INC2,в
которой они удваиваются. Один из
параметров передается как
параметр-переменная, другой - как
параметр-значение. Значения
параметров до и после вызова
процедуры, а также результат их
удвоения выводятся на экран.
Пример 15.
const
а :Integer = 5;
Ь :integer = 7;
{——————}
PROCEDURE Inc2 ( var с : Integer; b : Integer);
begin {Inc2}
c:=c+c;b:=b+b;
wrlteln('удвоенные :'. c:5, b:S)
end{lnc2};
BEGIN {main}
writeln('исходные :', a:5, b:S);
Inc2(a.b);
wrlteln('результат :', a:5. b:S);
END {main).
В результате прогона программы
будет выведено:
Исходные: 5 7
Удвоенные: 10 14
результат: 10 7
Как видно из примера,
удвоение второго формального
параметра в процедуре INC2 не
вызвало изменения фактической
переменной В, так как этот параметр
описан в заголовке процедуры как
параметр-значение.
Этот пример может служить
еще и иллюстрацией механизма «закрывания»
глобальной переменной одноименной
локальной: хотя переменная
объявлена как глобальная (она
описана в вызывающей программе
перед , описанием процедуры), в теле
процедуры ее «закрыла» локальная
переменная В, объявленная как
параметр-значение.
Итак, параметры-переменные
используются как средство связи
алгоритма, реализованного в
подпрограмме, с «внешним миром»: с
помощью этих параметров
подпрограмма может передавать
результаты своей работы
вызывающей программе. Разумеется,
в распоряжении программиста
всегда есть и другой способ
передачи результатов - через
глобальные переменные. Однако
злоупотребление глобальными
связями делает программу , как
правило, запутанной, трудной в
понимании и сложной отладке. В
соответствии с требованиями
хорошего стиля программирования
рекомендуется там, где это
возможно, использовать передачу
результатов через фактические
параметры-переменные.
С другой стороны, описание
всех формальных параметров как napаметров-переменных
нежелательно по двум причинам. Во-первых,
это исключает возможность вызова
подпрограммы с фактическими
параметрами в виде выражений, что
делает программу менее компактной.
Во-вторых, и главных, в
подпрограмме возможно случайное
использование формального
параметра, например, для
временного хранения
промежуточного результата, т.е.
всегда существует опасность
непреднамеренно «испортить»
фактическую переменную. Вот почему
параметрами-переменные следует
объявлять только те, через которые
подпрограмма в действительности
передает результаты вызывающей
программе. Чем меньше napаметров
объявлено параметрами-переменными
и чем меньше в подпрограмме
используется глобальных
переменных, тем меньше опасность noлучения
непредусмотренных программистом
побочных эффектов, связанных с
вызовом подпрограммы, тем проще
программа в понимании и отладке.
По той же причине не
рекомендуется использовать
параметры-переменные в заголовке
функции: если результатом работы
функции не может быть единственное
значение, то логичнее использовать
процедуру или нужным образом
декомпозировать алгоритм на
несколько подпрограмм.
Существует одно
обстоятельство, которое следует
учитывать при выборе вида
формальных параметров. Как уже
говорилось, при объявлении
параметра-значения осуществляется
копирование фактического
параметра во временную память.
Если этим параметром будет массив
большой размерности, то
существенные затраты времени и
памяти на копирование при
многократных обращениях к
подпрограмме могут стать peшающим
доводом в пользу объявления такого
параметра параметром-nepеменной или
передачи его в качестве глобальной
переменной.
8.4. ПАРАМЕТРЫ-МАССИВЫ И ПАРАМЕТРЫ-СТРОКИ
Может сложиться
впечатление, что объявление
переменных в списке формальных
параметров подпрограммы ничем
не отличается от объявления их в
разделе описания переменных.
Действительно, в обоих случаях
много общего, но есть одно
существенное различие: типом
любого napаметра в списке
формальных параметров может
быть только стандартный или
ранее объявленный тип. Поэтому
нельзя, например, объявить
следующую процедуру:
Procedure S (а : array (1..10] of real);
так как в списке формальных
параметров фактически
объявляется тип диапазон,
указывающий границы индексов
массива.
Если мы хотим передать
какой-то элемент массива, то
проблем, как правило, не
возникает, но если в
подпрограмму передается весь
массив, то следует первоначально
описать его тип. Например:
type
atype = array [1..10) of real;
PROCEDURE S (a : atype);
Поскольку строка является
фактически своеобразным
массивом, ее передача в
подпрограмму осуществляется
аналогичным образом:
type
i ntype =stri ng[15];
outype = string[30];
FUNCTION St (i : intype) : outype:
Требование описать любой
тип-массив или тип-строку перед
объявлением подпрограммы на
первый взгляд кажется
несущественным. Действительно, в
рамках простейших
вычислительных задач обычно
заранее известна структура всех
используемых в программе данных,
поэтому статическое описание
массивов не вызывает проблем.
Однако разработка программных
средств универсального
назначения, например, типа
широко используемых в среде
Фортрана ОС ЕС библиотек
подпрограмм для научных и
инженерных расчетов, связана со
значительными трудностями. По
существу, речь идет о том, что в
Турбо Паскале невозможно
использовать в подпрограммах
массивы с «плавающими»
границами изменения индексов.
Например, если разработана
программа, обрабатывающая
матрицу из 10 х 10 элементов, то для
обработки матрицы из 9 х 11
элементов необходимо
переопределить тип, т.е.
перекомпилировать всю программу
(сейчас речь идет не о
динамическом размещении
массивов в куче, а о статическом
описании массивов и передаче их
как параметров в подпрограммы).
Этот недостаток, как и
отсутствие в языке средств
обработки исключительных
ситуаций (прерываний),
унаследован из стандартного
Паскаля и представляет собой
объект постоянной и вполне
заслуженной его критики.
Разработчики Турбо Паскаля не
рискнули кардинально изменить
свойства базового языка, но, тем
не менее, включили в него
некоторые средства, позволяющие
в известной степени смягчить
отмеченные недостатки.
Прежде всего, в среде
Турбо Паскаля можно установить
режим компиляции, при котором
отключается контроль за
совпадением длины фактического
и формального параметра-строки (см.
прил.1). Это позволяет легко
решить вопрос о передаче
подпрограмме строки
произвольной длины. При передаче
строки меньшего размера
формальный параметр будет иметь
ту же длину, что и параметр
обращения; передача строки
большего размера приведет к ее
усечению до максимального
размера формального параметра.
Следует сказать, что контроль
включается только при передаче
строки, объявленной как
формальный параметр-переменная.
Если соответствующий параметр
объявлен параметром-значением,
эта опция игнорируется и длина
не контролируется.
Значительно сложнее
обстоит дело с передачей
массивов произвольной длины.
Наиболее универсальным приемом
в этом случае будет, судя по
всему, работа с указателями и
использование индексной
арифметики. Несколько проще
можно решить эту проблему при
помощи не типизированных
параметров (см. п.8.5).
8.5. ПРОЦЕДУРНЫЕ ТИПЫ. ПАРАМЕТРЫ-ФУНКЦИИ V ПАРАМЕТРЫ-ПРОЦЕДУРЫ
Процедурные типы - это
нововведение фирмы Borland (в
стандартном Паскале таких
типов нет). Основное
назначение этих типов - дат
программисту гибкие средства
передачи функций и процедур в
качеств фактических
параметров обращения к другим
процедурам и функциям.
Для объявления
процедурного типа
используется заголовок
процедур (функции), в котором
опускается ее имя, например:
type
Proc = Procedure (a, b, с : real; Var d : real);
Proc2 = Procedure (var a, b);
РгосЗ = Procedure;
Func = Function : string;
Func2 =Function (var S : string) : real;
Как видно из
приведенных примеров,
существует два процедурных
типа: тип-процедура и тип-функция.
Пример 16 иллюстрирует
механизм передачи процедур в
качеств фактических
параметров вызова. Программа
выводит на экран таблиц двух
функций: sinl(x) - (sin(x) + 1) * ехр(-х) и
cosl(x) - (cos(x) + 1) ехр(-х). Вычисление
и печать значений этих функций
реализуются процедуре PR1NTFUNC,
которой в качестве параметров
передаются на мер позиции N на
экране, куда будет выводиться
очередной результат (c помощью
этого параметра реализуется
вывод в две колонки), и имя
нужной функции.
Пример 16.
Uses CRT;
type
Func = Function (x : real) : real;
FUNCTION Sin1(x : real) : real; far;
BEGIN
sinl := (sin(x) + 1) * exp(-x)
END;
FUNCTION Cos1(x : real) : real; far;
BEGIN
cosl := (cos(x) + 1) * exp(-x)
END;
PROCEDURE PrintFunc(n : byte; F : Func);
const
np = 20; {количество
вычислений функций)
var
х : real;
I : Integer;
BEGIN {PrintFunc}
for I := 1 to np do
begin
x := I * (2 * pi / np);
GotoXY (n, WhereY);
writeln (x:5:3, F(x): 18:5)
end
END; {PrintFunc}
BEGIN {основная программа}
ClrScr; {очистить экран}
PrintFunc (1, sin1);
GotoXY (1,1); {курсор в левый
верхний угол}
PrintFunc (40, cosl)
END.
Обратите внимание: для
установления правильных
связей функций S1N1 и COS1 с
процедурой PRINTFUNC они должны
компилироваться с. расчетом на
дальнюю модель памяти. Вот
почему в программу вставлены
стандартные директивы FAR сразу
за заголовками функций. В
таком режиме должны
компилироваться любые
процедуры (функции), которые
будут передаваться в качестве
фактических параметров вызова.
Стандартные процедуры (функции)
Турбо Паскаля не могут
передаваться рассмотренным
способом.
В программе могут быть
объявлены переменные
процедурных типов, например,
так:
var
р1 : Proc;
f1. f2 : Func2;
ар : array [1..N] of Proc2;
Переменным процедурных
типов допускается присваивать
в качестве значений имена
соответствующих подпрограмм.
После такого присваивания имя
переменной становится
синонимом имени подпрограммы,
например:
type
Proc = Procedure (n : word; var a : byte): var
ProcVar : Proc; x, У : byte;
Procedure Prod(x : word; var у : byte); far;
begin
if x > 255 then у := x mod 255
else у := byte(x)
end;
begin
ProcVar := Proc1;
for x := 150 to 180 do
begin
ProcVar (x + 100, y);
write (у : 8)
end
end.
Разумеется, такого рода
присваивания допустимы и для
параметров функций, например:
Type
FuncType = Function (i : integer) : integer;
var
VarFunc : FuncType;
I : Integer;
Function MyFunc (count : Integer) : integer; far;
Begin
………………………….
end; {MyFunc}
begin {Основная программа}
…………………………
I := MyFunc(1); {Обычное
использование результата
функции)
VarFunc := MyFunc; {Присваивание
переменной процедурного типа
имени функции MyFunc}
Отметим, что присваивание
VarFunc := MyFunc(1):
будет недопустимым, так как
слева и справа от знака
присваивания используются
несовместимые типы: слева -
процедурный тип, а справа - INTEGER;
имя функции со списком
фактических параметров МуFипс(1)
трактуется Турбо Паскалем как
обращение к значению функции,
в время как имя функции без
списка параметров
рассматривается как имя
функции.
В отличие от стандартного
Паскаля, в Турбо Паскале
разрешается использовать в
передаваемой процедуре (функции)
как параметры-значения, так и
параметры-переменные.
8.7. РАСШИРЕННЫЙ СИНТАКСИС ВЫЗОВА ФУНКЦИЙ