Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
2- Введение в программирование.doc
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
753 Кб
Скачать
☆

Особенности функций

Идентификатор функции возвращает после вызова простое (порядковое или вещественное) или строковое значение заданного типа (возвращаемое значение не может быть файлом, массивом, записью, множеством, объектом). Для присвоения функции значения ее имя должно появиться хотя бы однажды в левой части оператора присваивания в теле самой функции.

Функция, как и процедура, может обмениваться значениями с программой и изменять глобальные переменные непосредственно или через параметры-переменные. Обычно, когда функция, кроме выдачи своего значения, меняет какие-либо глобальные значения или производит другие действия, не связанные с вычислениями своего значения, говорят, что она имеет побочный эффект. Функция вычисления Х в степени Y по формуле (ХY=exp(Y  ln(X)):

Var

X,Y : Real;

Function Power (Arg, P : Real) : Real;

begin

if Arg <> 0 then {если аргумент  0}

Power := exp(P * ln(abs(Arg)))

else

if P = 0 then {если аргумент = 0 и степень = 0}

Power := 1

else {если аргумент = 0 и степень  0}

Power := 0

end;

Begin

Readln(X,Y);

Writeln(Power(X,Y))

End.

Процедуры и функции могут быть вложенными друг в друга (на общем примере в начале параграфа). Число уровней вложенности может быть достаточно большим, но на практике обычно не превышает второго уровня. Вложенная процедура или функция относится к охватывающей ее подпрограмме точно также, как сама подпрограмма относится к основной программе.

Опережающее описание

Как известно, текст программы транслируется в выполняемый машинный код последовательно сверху вниз. При этом все элементы программы (переменные, типы, константы, подпрограммы) должны описываться до того, как начнутся их упоминания в операторах и выражениях. В противном случае компилятор объявит имя неизвестным. В случаях с процедурами и функциями этого можно избежать, используя опережающее описание директивой Forward:

Procedure <имя> (<параметры>); forward;

...

Procedure <имя> {список параметров уже не нужен}

<тело процедуры>;

Эта директива объявляет заголовок подпрограммы, откладывая описание содержимого «на потом». Местоположение этого описания не играет роли, и в нем можно не указывать параметры, а ограничиться лишь именем. Директива Forward существует в языке, в основном, для развязки закольцованных вызовов (одна подпрограмма вызывает вторую, которая в свою очередь вызывает первую).

Кроме стандартной директивы Forward можно использовать и другие стандартные директивы, которые должны указываться за заголовком подпрограммы. Эти директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее.

Assembler - отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и выходе из нее. Тело подпрограммы должно в этом случае реализовываться с помощью команд встроенного ассемблера (ассемблера, встроенного компилятор ТР);

External - с помощью этой директивы объявляется внешняя подпрограмма. При этом появляется возможность вызова процедур и функций, написанных и откомпилированных с помощью внешнего ассемблера (например, TurboAssembler Borland)). Возможен импорт объектных кодов, полученных с помощью Turbo C и других языков, но на практике он труднореализуем;

Far, Near - компилятор должен создавать код программы, рассчитанный на дальнюю либо ближнюю модель памяти;

Inline - указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных команд;

Interrupt - используется при создании процедур обработки прерываний.

Процедурные типы. Параметры-функции и параметры-процедуры

Начиная с версии 5.5 в ТР был введен новый тип - процедурный тип. Этим был сделан важный шаг от языка Turbo Pascal как средства для обучения программированию к языку Turbo Pascal как средству профессионального программирования. Процедурный тип, являясь расширением стандартного Pascal и позволяет интерпретировать процедуры и функции как объекты, которые можно использовать при передачи параметров (понятие "данные" сюда меньше подходит).

Описание процедурного типа выполняется в соответствии с уже принятым синтаксисом (процедурный тип - общее понятие, объединяющее и функциональный тип):

Type

ProcType = Procedure (S : String; Var B : Byte);

FuncType = Function (X,Y : Integer) : Real;

В описании процедурного типа приводится полное описание параметров, а в случае функции указываются и ее атрибуты (важно, что символические имена параметров роли не играют и в дальнейшем могут не использоваться). Разница с синтаксисом обычного заголовка процедуры (функции) - в отсутствии их идентификатора. Значением переменной процедурного типа является конкретная процедура или функция. После объявления процедурного типа, его можно использовать в описаниях параметров подпрограмм. При этом естественно нужно написать реальные процедуры и функции, которые будут передаваться как параметры. Основные элементы программы, вычисляющей интеграл некоторой функции:

Type

FuncType = Function (X : Real) : Real; {процедурный тип}

Procedure Integral (LowerLimit, UpperLimit : Real; {вычисл. интеграла функции

Var Result : Real; Funct от точки LowerLimit до UpperLimit с

Funct : FuncType); результатом в параметре-переменной Result}

…

begin

…

end;

Function SinExp (Arg : Real) : Real; {некоторая функция}

begin

SinExp := Sin(Arg) + Exp(Arg)

end

…

Begin

…

Integral (0, 1, Res, SinExp); {процедура вычисления интеграла с

… записью в фактическую переменную Res}

End.

Описав другую функцию, можно определить ее интеграл, подставив на место соответствующего параметра.

Не разрешается объявлять функции, возвращающие значение процедурного типа. Требование к процедурам и функциям, которые передаются как параметры: они должны компилироваться в режиме {$F+}, что обеспечивает использование дальней модели обращения Far (в примере эта директива должна стоять перед описанием функции SinExp). Не всякую функцию (процедуру) можно подставить в вызов. Нельзя подставлять: процедуры с директивами Inline и Interrupt, вложенные процедуры и функции, стандартные процедуры и функции (не годится Integral (0, 1, Res, Sin)).

Применение процедурного типа не ограничивается одним лишь описанием параметров-процедур и параметров-функций. Например, переменная-процедура может являться частью структуры:

Type

RecType = record

X,Y : Integer;

P : ProcType;

end;

Var

Rec1,Rec2 : RecType;

Естественно, в процессе работы программы требуется присвоить некоторое значение полю Rec1.P. Этим значением должна быть специально описанная процедура. Т.е. в такое записи можно хранить не только данные, но и процедуру их обработки. Причем в любой момент можно сменить ту процедуру, которая понимается под именем Rec1.P.

Процедурные переменные по формату совместимы с переменными типа Pointer и после приведения типов могут обмениваться с ними значениями.

Используя директиву Absolute можно обмениваться данными между подпрограммами путем совмещения областей памяти их локальных переменных с адресом глобальной переменной.

Обыкновенные локальные переменные в подпрограммах всегда "забывают" свои значения и при повторном вызове стартовые значения локальных переменных совершенно случайны. Для сохранности информации между вызовами можно использовать статические локальные переменные, которые вводятся как локальные типизированные константы. Const A : Word = 240; Значение типизированной константы может меняться в теле подпрограммы. После чего оно запоминается до повторного вызова (восстановление стартового значения не происходит).

Процедуры и функции могут содержать в своих заголовках параметры-переменные (т.е. те, которые передаются как адрес, а не как значение) без указания их типа. Procedure P (Var V1,V2); Через такое объявление можно передавать подпрограммам строки, массивы, записи и т.д. Но при этом процедура (или функция) должна явно задавать тип, к которому внутри нее приравниваются бестиповые переменные.

Традиционное преимущество языка ТР - это использование рекурсии, под которой понимается вызов функции (процедуры) из тела этой же функции (процедуры). Например, вычисление факториала:

Function Fact (n : Word) : LongInt;

begin

if n = 0 then

Fact := 1

else Fact := n * Fact(n-1);

end;

Внесение рекурсивности придает программе изящество, но всегда оно же "заставляет" программы расходовать больше памяти. Дело в том, что каждый "отложенный" вызов подпрограммы это свой набор значений всех локальных переменных этой функции, размещенной в стеке. В ТР размер стека не превышает 64 К.

Модули

Стандартный Pascal не предусматривает механизмов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, принцип обязательного описания объекта перед его использованием делает практически невозможным разработку разнообразных библиотек прикладных программ. Точнее, такие библиотеки в рамках стандартного Pascal могут существовать только в виде исходных текстов и программист должен сам включать в программу большие тексты различных поддерживающих процедур (например, процедуры численного интегрирования, математической статистики и т.д.)

Отсюда понятно стремление разработчиков коммерческих компиляторов включать в Pascal средства, повышающие его модульность. В ТР с этой целью включены компоненты, которые так и называются - модули.

Модуль - это автономно компилируемая программная единица, включающая в себя различные компоненты раздела описаний (типы, константы, переменные, процедуры и функции) и, возможно, некоторые исполняемые операторы. Важная особенность модулей заключается в том, что компилятор ТР размещает их программный код в отдельном сегменте памяти. Максимальная длина сегмента не может превышать 64 К, однако количество одновременно используемых модулей ограничивается лишь доступной памятью, что дает возможность создавать весьма крупные программы.

Модуль имеет следующую структуру:

Unit <имя>;

Interface

… {интерфейсная часть}

Implementation

… {выполнение - исполняемая часть}

Begin

… {инициирующая часть}

End.

Т.о. модуль состоит из заголовка и трех составных частей, любая из которых, в принципе, может быть пустой.

Для правильной работы среды ТР имя модуля должно совпадать с именем дискового физического файла, в который помещается исходный текст модуля. Например, модуль с именем Unit Unit_1 размещает свой текст в файле unit_1.pas. Кроме того, имя модуля служит для его связи с другими модулями и основной программой. Эта связь устанавливается специальным предложением:

Uses Unit_1,…;

которое открывает раздел описаний в основной программе, а в других модулях сразу за словом Interface, либо сразу за словом Implementation, либо и там и там (т.е. допускаются два предложения Uses.

В интерфейсной части (за словом Interface) содержатся объявления всех глобальных элементов модуля (типов, констант, переменных, функций, процедур), которые должны стать доступными основной программе и другим модулям, к которым подключен данный модуль. При объявлении глобальных подпрограмм указывается только их заголовок. Например:

Unit Unit_1;

Interface

Type

Complex = record

Re, Im : Real

end;

Var

Zmain : Complex;

Procedure P_1 (X,Y : Complex; Var Z : Complex);

Implementation

Procedure P_1; {сложение двух комплексных чисел}

begin

Z.Re := X.Re + Y.Re;

Z.Im := X.Re + Y.Re

end;

Begin

Zmain.Re := 1; Zmain.Im := -1

End.

Теперь, если в основной программе в разделе Uses есть подключение модуля Unit_1, то в ней будет доступен тип Complex, процедура P_1 и переменная Zmain. Объявление подпрограмм в интерфейсной части автоматически сопровождается их компиляцией с использованием дальней модели памяти. Все константы и переменные, объявленные в интерфейсной части модуля, вместе с глобальными переменными основной программы помещаются компилятором в общий сегмент памяти размером 64 К (65536 байт). В интерфейсной части модуля нельзя использовать опережающее описание. Если при объявлении типов, данных и подпрограмм используются типы и данные, введенные в других модулях, то они должны быть указаны в разделе Uses сразу после слова Interface.

Исполняемая часть (начинается словом Implementation) обычно содержит описания подпрограмм, объявленных в интерфейсной части. В заголовке, предшествующем описанию подпрограммы, можно опустить список формальных параметров (и тип результата для функции), т.к. он был описан в интерфейсной части (см. пример). Часто этим и ограничиваются. Кроме того, в исполняемой части могут объявляться локальные для модуля типы, константы, переменные, подпрограммы, а также метки, если они используются в инициирующей части. Эти типы и данные будут являться глобальными по отношению к подпрограммам этого раздела и операторам раздела инициализации, если он имеется. Естественно, что в программе, которая подключает данный модуль, объявленные в разделе Implementation типы и данные будут недоступны.

Завершает текст модуля раздел инициализации. Если он отсутствует, то просто ставится слово End с точкой. В противном случае он открывается словом Begin, после чего следуют программные действия, которые будут произведены перед выполнением основной программы (работа скомпилированной программы всегда начинается с выполнения блоков инициализации используемых ею модулей, а лишь потом выполняется основной блок самой программы). Обычно в разделе инициализации происходит заполнение стартовыми значениями библиотечных переменных (см. пример) и какие-то одноразовые действия, которые должны выполниться в начале программы (например, открываться нужные файлы и т.д.).

Имеются особенности компиляции модулей. Определены три возможных режима компиляции:

В режиме COMPILE все упоминающиеся в разделе Uses модули должны быть предварительно откомпилированы и результаты компиляции помещены в одноименные файлы с расширением tpu (Turbo Pascal Units) (для нашего примера при компиляции основной программы должен быть файл unit_1.tpu. Такой файл появляется при соответствующей компиляции модуля (по аналогии с компиляцией основной программы, когда появляется исполняемый файл с расширением exe). Используется при отладке основной программы.

В режиме MAKE компилятор проверяет наличие tpu-файлов для каждого объявленного модуля. И если он не обнаружен, то пытается найти одноименный файл с расширением pas, в котором находится исходный текст модуля. Если такой файл найден, то этот модуль компилируется. Кроме того, в этом режиме системы следит за возможным изменением текста модуля. Если он изменялся, то независимо от существования tpu-файлов модуль компилируется заново. Т.о. режим MAKE удобен, т.к. избавляет от необходимости следить за соответствием tpu-файлов исходному тексту. Упрощает процесс обработки многофайловых программ, когда компилируется только необходимый минимум.

В режиме BUILD существующие tpu-файлы игнорируются и система пытается откомпилировать соответствующий pas-файл для каждого модуля. После этого можно быть уверенным, что все изменения внесены.

Первыми подключаются системные модули из библиотеки turbo.tpl, а затем все остальные, хранящиеся в виде tpu-файлов и даже pas-файлов. Если подключаемые модули содержат элементы с одинаковым именем, то обращаться будут к модулю, описанному в разделе Uses последним. Если нужно использовать другие модули, то это нужно указывать при обращении (например, Unit_1.Zmain).

В ТР имеется восемь стандартных модулей, в которых содержится большое число разнообразных типов, констант, процедур и функций. Модули GRAPH, TURBO3, GRAPH3 выделены в отдельные tpu-файлы, а остальные (SYSTEM, DOS, CRT, PRINTER, OVERLAY) входят в состав библиотечного файла turbo.tpl. Лишь один модуль SYSTEM подключается к любой программе автоматически, остальные нужно указывать в разделе Uses.

SYSTEM – включает все стандартные математические процедуры и функции, а также обеспечивает работу с командной строкой, ввод/вывод, эмуляцию сопроцессора, работу с динамической памятью, и т.д.;

DOS – в модуле собраны процедуры и функции, открывающие доступ к средствам ОС МS-DOS, обработка файлов, работа с каталогами;

CRT – содержит библиотеку процедур, которые работают с клавиатурой и дисплеем, обеспечивая текстовый режим работы экрана (просто ввод/вывод м. и без него);

PRINTER – делает доступным вывод текстов на матричный принтер;

GRAPH – содержит обширный набор типов, данных и подпрограмм для управления графическим режимом работы экрана;

OVERLAY – необходим при разработке громоздких программ, длина которых превышает память, отводимую MS-DOS под исполняемую программу (~ 580 Кбайт без учета резидентных программ и самой системы ТР);

TURBO3, GRAPH3 – введены для совместимости с ранней версией 3.0 системы ТР.

70