- •5.160 5. Программирование с использованием подпрограмм
- •5. Программирование с использованием подпрограмм
- •Процедуры
- •Объявление процедуры
- •Заголовок процедуры
- •Обращение к процедуре
- •Список формальных параметров
- •Пример 5.1 выполнения задания
- •Задания 5.1 для самостоятельной проработки
- •Функции Объявление функции
- •Заголовок функции
- •Обращение к функции
- •Пример 5.2 выполнения задания
- •Задания 5.2 для самостоятельной проработки
- •Рекурсивные подпрограммы
- •Пример выполнения задания на составление рекурсивной подпрограммы
- •Задания 5.3 для самостоятельной проработки
- •Параметры - открытые массивы
- •Динамические массивы
- •Перегружаемые подпрограммы
- •Параметры со значениями по умолчанию
- •Примеры программ с подпрограммами
Рекурсивные подпрограммы
Язык Object Pascal допускает создание и использование рекурсивных подпрограмм. Это означает, что внутри подпрограммы можно обращаться к самой подпрограмме. Однако, чтобы такое обращение имело смысл, подпрограмма должна быть организована должным образом, т.е. реализовала бы именно рекурсивный алгоритм. Например, составим функцию, вычисляющую сумму элементов одномерного массива. Если использовать известный прием накопления суммы, предусматривающий вычисление суммы в цикле, то такая функция может иметь следующий вид:
program Fun2rec;
{$APPTYPE CONSOLE}
uses
SysUtils;
const
nn=20;
type
mas=array[1..nn] of Real;
var
a:mas;
i,n:Integer;
s:Real;
function Sum(a:mas;n:Integer):Real;
var i:Integer;
begin
Result:=0;
for i:=1 to n do
Result:=Result+a[i];
end;
begin// РАЗДЕЛ ОПЕРАТОРОВ ПРОГРАММЫ
ReadLn(n);
for i:=1 to n do
Read(a[i]);
ReadLn;
s:=sum(a,n);
WriteLn('s= ',s:6:1);
ReadLn;
end.
Внутри функции для накопления суммы использовалась внутренняя переменная Result. Использование внутри цикла оператора присваиванияSum:=Sum+a[i]привело бы к ошибке, так как появление в правой части оператора присваивания имени функции означало бы обращение к этой функции. Поскольку после имени функции отсутствуют параметры, то это означало бы синтаксическую ошибку. Но поскольку внутри функции реализован циклический алгоритм, а не рекурсивный, то операторSum:=Sum(a,n)+a[i]приводил бы также к ошибке (подпрограмма зацикливается).
Для построения рекурсивного варианта подпрограммы следует сформулировать рекурсивное утверждение и условие окончания рекурсии. В нашем случае рекурсивное утверждение будет заключаться в том, что сумма элементов массива из nэлементов может быть вычислена как сумма (n-1)-го начальных элементов и последнего элемента, то естьSn =Sn-1 +an(n>1). Условие окончания рекурсииS1 =a1. Рекурсивный вариант функции приведен ниже.
function Sum(a:mas;n:Integer):Real;
begin
if n=1 then
Sum:=a[n]
else
Sum:=a[n]+Sum(a,n-1)
end;
Рекурсивное описание обычно является более компактным и выглядит более наглядно, если природа самого алгоритма рекурсивна. Однако надо принимать во внимание и то, как реализуется рекурсивный алгоритм в ЭВМ. Например, при n=4 (массив состоит из четырех элементов) производится первое обращение к функции со значениями параметров массиваaиn=4. При этих значениях выполняется операторSum:=a[4]+Sum(a,3).Затем выполняется обращение к функции со значениямиaи 3, т.е.sum(a,3). Затем выполняется обращение к функции со значениямиaи 2, т.е.Sum(a,2). При выполнении этого обращения к функции производится еще одно обращение:sum(a,1). На последнем шаге при обращении к функции с этими параметрами будет выполнен операторSum:=a[1]. Далее процесс развивается в обратном направлении, т.е. выполнится операторSum:=a[2]+Sum(a,1), что даст результатSum:=a[2]+a[1], затем выполнится операторSum:=a[3]+Sum(a,2), это дает результатSum:=a[3]+a[2]+a[1]. На последнем шаге выполнится операторSum:=a[4]+Sum(a,3), дающий окончательный результат. Рассмотрев описанный процесс, можно придти к выводу, что рекурсивная подпрограмма требует больших затрат машинного времени и памяти. Это объясняется необходимостью выполнения повторных обращений к подпрограмме и необходимостью хранения (в стеке) фактических параметров при обращениях к подпрограмме.
Рекурсия может быть и косвенной. В этом случае программа обращается сама к себе опосредованно, т.е. первая программа обращается ко второй, которая, в свою очередь, содержит обращение к первой.
procedure Pr1(a:Real;var b:Real);
begin
. . . . .
Pr2(a,b);
. . . . .
end;
procedure Pr2(c:Real;var d:Real);
begin
. . . . .
Pr1(c,d);
. . . . .
end;
Согласно правилам языка Object Pascal каждый идентификатор должен быть предварительно объявлен, поэтому такая конструкция недопустима. Чтобы разрешить использование косвенной рекурсии, в состав языка введено опережающее объявление, реализуемое директивой forward. Сначала необходимо объявлен заголовок одной из процедур, после которого поместить указанную директиву. После этого следует расположить вторую процедуру, а затем разместить первую процедуру, в заголовке которой можно опустить список формальных параметров. Таким образом, во второй процедуре можно обращаться к первой процедуре, так как к этому моменту ее заголовок уже известен второй процедуре.
procedure Pr1(a:Real;var b:Real); forward;
procedure Pr2(c:Real;var d:Real);
begin
. . . . .
Pr1(c,d);
. . . . .
end;
procedure Pr1(a:Real;var b:Real);
begin
. . . . .
Pr2(a,b);
. . . . .
end;