Пример 1. Вычислить определенный интеграл произвольной функции в интервале с погрешностью, не превышающей заданного значения .
Как известно, определенный интеграл - это площадь фигуры, ограниченной подынтегральной функцией , осью абсцисс и вертикальными прямыми и . Для определения площади можно отрезок разделить на частей и вычислять элементарные площади тем или иным приближенным способом. Наиболее простым и в то же время наименее точным способом является вычисление элементарной площади по формуле прямоугольника; более точным является представление элементарной фигуры как трапеции; еще точнее - аппроксимация трех смежных точек кривой уравнением параболы.
Предположим, что мы реализовали программу вычисления определенного интеграла каким-либо способом, но в задаче требуется несколько раз вычислять интеграл для различных функций. В этом случае целесообразно такую программу оформить в виде подпрограммы-функции, предусмотрев в списке ее параметров имя формальной функции, а при обращении к ней передавать имя конкретной функции. Следовательно, возникает вопрос о способе представления в Паскаль-программе имени функции как параметра.
Пример 2. Предположим, что составленная нами программа содержит два больших фрагмента, почти полностью совпадающих между собой. Различными являются лишь несколько строк, содержащих не совпадающие последовательности операторов. Здесь целесообразно поступить следующим образом:
- оформить несовпадающие строки операторов как две различные процедуры, например, с именами Proc1 и Proc2;
- объединить два фрагмента в одну процедуру Frag, реализовав несовпадающие строки как обращение к формальной процедуре Proc;
- при обращении к процедуре Frag в первом случае указывать имя фактической процедуры Proc1, во втором - имя Proc2.
Следовательно, в этом примере возникает вопрос о способе представления в Паскаль-программе имени процедуры как параметра.
Турбо Паскаль позволяет интерпретировать процедуры и функции как объекты, которые можно присваивать переменным и передавать их в качестве параметров. Следовательно, если переменной можно присвоить имя процедуры или имя функции, то эта переменная должна иметь в программе тип процедурной или функциональной переменной. В дальнейшем, если это не оговорено особо, термин "процедурная переменная" будем относить как к собственно процедурным переменным, так и к функциональным переменным. Ниже записано несколько примеров описания процедурных и функциональных типов.
Пример 3.
TypeProc = procedure;
StringProc = procedure(S:string);
IntProc = procedure(Var m,n:integer; k:integer);
MastFunc = function(a,b:real):real;
Имена формальных параметров здесь играют иллюстративную роль.
Синтаксис записи процедурного (функционального) типа совпадает с записью заголовка процедуры (функции), но здесь не указывается имя процедуры (функции).
Пример процедурных переменных:
VarF1,F2 : MastFunc;
P1,P2 : Proc;
Sp1,Sp2 : StringProc;
Процедурной переменной можно присвоить значение процедурного типа. Таким значением может быть другая процедурная переменная или процедурная константа. Процедурной константой является идентификатор процедуры или функции.
Пример 4.
TypeStringProc = procedure(S:string);
MastFunc = function(a,b:real):real;
VarF1,F2 : MastFunc;
Sp1,Sp2 : StringProc;
S : string;
{$F+}
Procedure Rant(St:string);
Begin
......
End { Rant };
Function Vent(p,q:real):real;
Begin
......
End{ Vent };
{$F-}
Begin
F1:=Vent; Sp1:=Rant;
...................
Тогда обращения
Rant(S); y:=Vent(5,10);
будут эквивалентны обращениям
Sp1(S); y:=F1(5,10);
Внутреннее представление процедурной переменной - это указатель, адрес которого определяет точку входа соответствующей процедуры или функции. Если мы присваиваем процедурной переменной значение процедурной константы, то внутреннее представление этой константы должно быть таким же, т.е. это должно быть четырехбайтное поле, первое слово которого - адрес сегмента, а второе - смещение.
Переменные и константы в Турбо Паскале могут иметь или полный, или краткий адрес. Полный адрес - это обычная запись адреса в виде сегмента и смещения (4 байта), в кратком адресе содержится только смещение (2 байта). Если переменная или константа (в том числе процедурная константа) используются лишь внутри одного модуля, то они имеют краткий адрес, в противном случае для них формируется полный адрес. В частности, адрес входа внутренней процедуры всегда формируется кратким. Использование кратких адресов существенно сокращает общий объем программы.
Если процедура может применяться как процедурная константа для присваивания значения процедурной переменной или для передачи ее в качестве фактического параметра, то адрес входа такой процедуры должен быть полным вне зависимости от того, используется она в одном или в нескольких модулях. Чтобы обеспечить при любых условиях формирование полного адреса точки входа процедуры, применяется директива компилятора {$F+}. Вместо директивы $F можно использовать директиву Far (дальний тип вызова), записываемую после заголовка процедуры или функции.
Для присваивания процедурной переменной какого-либо значения необходимо, как и при любом другом присваивании, чтобы переменная в левой части и значение в правой части оператора присваивания были совместимы по присваиванию.
Процедурные типы, чтобы быть совместимыми по присваиванию, должны иметь одно и то же количество параметров, а параметры на соответствующих позициях должны быть одинакового типа.
Если имя процедуры или функции присваивается процедурной переменной, то дополнительно должны быть выполнены следующие требования:
- процедура должна компилироваться в состоянии {$F+};
- это не должна быть стандартная процедура или функция;
- такая процедура или функция не должна быть вложенной.
Стандартными процедурами и функциями считаются процедуры и функции, описанные в модуле System (writeln, sin, ord и др.). Процедуры и функции из других модулей (ClrScr, ReadKey, GetTime и др.) - это предописанные (предопределенные) процедуры и функции.
Чтобы использовать стандартную процедуру или функцию в качестве процедурной константы, для нее нужно написать специальную "оболочку".
Пример 5.
TypeProcInt = procedure(n:integer);
FuncReal = function(x:real):real;
Vark : integer;
x,y : real;
P : ProcInt;
F : FuncReal;
{$F+}
ProcedureWriteInt(m:integer);
Begin
Writeln(m);
End{ WriteInt };
Function RealSin(x:real):real;
Begin
RealSin:=sin(x);
End{ RealSin };
{$F-}
Begin
...........
P:=WriteInt;
P(k);
F:=RealSin;
y:=F(x);
............
Требование о программной оболочке связано с тем, что большинство стандартных процедур и функций могут иметь параметры различного типа, а некоторые из них также переменный по количеству список параметров. Например, аргумент функции sin(x) может быть как вещественный, так и целочисленный; в списке вывода процедуры Write может быть любое количество переменных различного типа. Наличие программной оболочки фиксирует как количество, так и типы параметров.
Процедурный тип может участвовать в описании составного типа.
Пример 6.
Type IntProc = procedure(n:integer);
StringProc = procedure(S:string);
ProcAr = array[1..10] of IntProc;
ProcRec = record
Ap : IntProc;
Bp : StringProc
end;
Var Ss : ProcAr;
Rr : ProcRec;
Пример 7. Вычислить
, и ,
В программе численного интегрирования для вычисления площади будем использовать метод прямоугольников.
Для получения решения в Int1 потребовалось в 4 раза больше делений интервала, чем в Int2. Это связано с двумя обстоятельствами:
1) синусоидальная функция гораздо быстрее изменяется по сравнению с экспоненциальной, в связи с чем для ее численного интегрирования требуются более мелкие шаги;
2) для получения значения S1 задана более высокая точность по сравнению со значением S2.
В тексте функции Integ следует обратить внимание на такую деталь. Формальному параметру Func типа FuncType при обращении к функции Integ соответствуют фактические параметры Func1 и Func2, являющиеся именами конкретных функций, т.е. функциональными константами. Следовательно, формальный параметр Func в этом случае может быть только параметром-значением, т.е. перед именем Func в списке формальных параметров нельзя ставить слово Var.