| BEGIN { основной (вызывающий) блок }
| GetSQR ( 5, SqGlobal );
| WriteLn( SqGlobal, ' Флаг: ', GlobalFlag )
| END.
Рис. 6.7
Оставим ненадолго процедуры и рассмотрим функции. Идентификатор функции возвращает после вызова скалярное значение заданного типа. Для присвоения функции значения ее имя должно хотя бы однажды появиться в левой части оператора присваивания в теле самой функции. Вызов функции производится уже не обособленно, а в том месте, где необходимо значение функции (в выражениях, вызовах других подпрограмм и т.п.). Например, процедуру GetSQR на рис. 6.7 можно переписать в виде функции (рис. 6.8).
| VAR GlobalFlag : Boolean; { глобальный флаг }
| FUNCTION GetSQR( X : Real ) : Real;
| CONST SQRMAX =100;
| BEGIN
| X := X*X;
| GlobalFlag:=(X>SQRMAX);
| if GlobalFlag then X:=SQRMAX;
| GetSQR := X { возвращение значения }
| END;
Рис. 6.8
- 111 -
| BEGIN { основной (вызывающий) блок }
| WriteLn( GetSQR( 5 ), ' Флаг: ', GlobalFlag )
| END.
Рис. 6.8 (окончание)
Вызов заметно упростился, не говоря уже о сокращенной переменной SqGlobal. Возвращаемое функцией значение должно быть скалярного или строкового типа, т.е. не может быть файлом, массивом, записью, множеством, объектом.
Функция, как и процедура, может обмениваться значениями с программой и изменять глобальные переменные непосредственно или через параметры-переменные. Обычно, когда функция, кроме выдачи своего значения, меняет какие-либо глобальные значения или производит другие действия, не связанные с вычислениями своего значения, говорят, что она имеет побочный эффект.
Большое значение имеет соблюдение правил соответствия типов при подстановке параметров. Нельзя (да и не получится — компилятор не пропустит!) конструировать типы в описаниях параметров. Можно использовать только уже известные идентификаторы типов. То же самое можно сказать о типе возвращаемого значения функции. И, конечно, число и порядок следования параметров в вызове должен соответствовать описанию процедуры или функции. Кроме того, в Турбо Паскале существует правило, требующее, чтобы параметры, имеющие файловый тип (или сложный тип с файловыми компонентами), были обязательно описаны как VAR-параметры.
Процедуры и функции могут быть вложенными друг в друга (см. рис. 6.5). Число уровней вложенности может быть достаточно большим, но на практике не превышает второго уровня. Вложенная процедура или функция относится к охватывающей ее подпрограмме точно так же, как сама подпрограмма относится к основной программе. Вложенные процедуры или функции могут вызываться только внутри охватывающей подпрограммы. Переменные, вводимые в них, будут по-прежнему локальными, а глобальными будут считаться все локальные переменные охватывающей подпрограммы и, как и ранее, все действительно глобальные переменные (а также типы и константы), объявленные в основной программе перед описанием подпрограмм.
Область действия меток переходов всегда локальна, и нельзя планировать переходы с помощью оператора Goto из вложенной, например, процедуры, в охватывающую или в основной блок программы, или из программы в процедуру.
- 112 -
6.9.2. Опережающее описание процедур и функций
Текст программы транслируется в выполнимый код последовательно сверху вниз. При этом переменные, типы, константы и подпрограммы должны описываться до того, как начнутся их упоминания в операторах или выражениях. В противном случае компилятор объявит имя неизвестным, и придется перемещать описания подпрограмм вверх по тексту программы. В случаях с процедурами и функциями этого можно избежать, используя опережающее описание директивой FORWARD:
PROCEDURE ИмяПроцедуры(параметры); FORWARD;
FUNCTION ИмяФункции( параметры ) : ТипЗначения; FORWARD;
......
PROCEDURE ИмяПроцедуры; { список параметров уже не нужен }
Тело процедуры
FUNCTION ИмяФункции; { достаточно указать только имя }
Тело функции
......
Эта директива объявляет заголовок подпрограммы, откладывая описание содержимого «на потом». Местоположение этого описания уже не играет роли, и в нем можно не указывать параметры, а ограничиться лишь именем подпрограммы. Основное описание не может иметь никаких директив (FORWARD, EXTERNAL и др.).
Директива FORWARD существует в языке в основном для развязки закольцованных вызовов. Так, ситуацию на рис. 6.9 можно разрешить только с ее помощью:
PROCEDURE a( у : TypeXY ); FORWARD;
PROCEDURE b( x : TypeXY );
BEGIN
...
a(p); {процедура b вызывает a}
END;
PROCEDURE a;
BEGIN
...
b( q ); {но сама a вызывает b }
END;
Рис. 6.9
- 113 -
6.9.3. Объявление внешних процедур
Турбо Паскаль — язык не слишком коммуникабельный по отношению к прочим языкам. Он не поддерживает генерацию объектных файлов в формате OBJ и вследствие этого не может поставлять написанные на нем процедуры и функции для связи с другими языками. Единственное, что можно — это использовать при компиляции и компоновке программ на Турбо Паскале внешние подпрограммы в виде OBJ-файлов, созданных другими компиляторами. OBJ-файлы должны при этом удовлетворять определенным требованиям к используемой модели памяти и способу передачи значений. Гарантируется совместимость кодов, полученных компилятором Turbo Assembler. He должно быть проблем и с кодами от ассемблера MASM или ему подобных. Возможен импорт объектных кодов, полученных в Turbo C и других языках, но на практике он труднореализуем.
Команды подстыковки объектных файлов в программу на Турбо Паскале задаются директивами компилятора {$L ИмяФайла.OBJ}, установленными в подходящих местах программы. А те процедуры и функции, которые реализованы в этих файлах, должны быть объявлены своими заголовками и специальным словом EXTERNAL, например:
{$L memlib.obj} { включение объектного кода }
procedure MemProc1; external;
PROCEDURE MemProc2( X,Y : Byte ); EXTERNAL;
FUNCTION MemFunc1( X :Byte; VAR Y :Byte ): Word; EXTERNAL;
Подключенные таким образом внешние функции или процедуры в дальнейшем ничем не отличаются от написанных в тексте. Обычно директиву включения OBJ-файла и объявления внешних подпрограмм удобно размещать рядом. Порядок следования директивы $L и описаний заголовков может быть произвольным.
6.9.4. Процедуры и функции как параметры
Отличительной особенностью Турбо Паскаля является разрешение передавать в процедуры и функции имена других подпрограмм, оформляя их как параметры. И точно так же, как передавалось значение, может передаваться некая функция его обработки. Особенно важным это становится при программной реализации алгоритмов вычислительной математики (хотя можно назвать и ряд других областей). Например, становится возможным написать процедуру интегрирования любой функции вида f(t) по следующей
- 114 -
схеме (рис. 6.10). Неочевидным здесь может показаться только введение функционального типа и то, как он определяется.
PROCEDURE Integrate LowerLimit, UpperLimit : Real;
VAR
Result : Real;
Funct : Функциональный тип);