Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лекції для 3ОТ1.doc
Скачиваний:
10
Добавлен:
21.02.2016
Размер:
16.31 Mб
Скачать

Способы вызова подпрограмм

Подпрограммы с точки зрения прикладного программиста вызываются всегда оди-наково, но машинный код, который создается компилятором, для разных подпро¬грамм может отличаться. Это зависит от целей применения конкретной подпро¬граммы. Она может использоваться: >> в рамках разрабатываемой прикладной программы; >> как функция, доступная из динамической библиотеки .DLL; >> как процедура, вызываемая из внешних программ или из Windows и т. д. Для каждого из таких нестандартных случаев вслед за заголовком подпрограммы (за точкой с запятой) должно следовать одно из ключевых слов. Таблица 1.10. Ключевые слова в заголовке подпрограмм ____________________________________________________________________ Ключевое слово Способ передачи параметров _____________________________________________________________________ pascal Стандартный (параметры помещаются в стек) register Способ, применяемый по умолчанию. Аналогичен использованию  ключевого слова pascaL но параметры передаются с помощью трех регистров  процессора, а не помещаются в стек (область оперативной памяти), что обычно приводит к повышению быстродействия программы

cdecl В соответствии с соглашениями компиляторов для языков  программирования С и С++. Применяется, когда происходит обращение к  динамическим библиотекам DLL, написанным на этих языках stdcall В соответствии с соглашениями Windows safecall Используется при работе с компонентными технологиями _____________________________________________________________________ При использовании ключевых слов register и pascal вычисление параметров выпол¬няется слева направо и располагаются они в оперативной памяти перед вызовом подпрограммы в таком же порядке. При использовании ключевых слов cdeclstdcall и safecall параметры располагаются в обратном порядке (справа налево).

procedure Sum( A: array of const ); stdcall;

Существует еще одно зарезервированное слово Паскаля, forward, которое, при ука¬зании вслед за заголовком, говорит компилятору о том, что в данном месте распо¬ложен только заголовок подпрограммы, а все ее описание находится в исходном тексте далее. Такое описание обычно применяют, если в тексте имеется несколько подпрограмм, которые вызывают друг друга по кругу. Например, из процедуры Р1 вызывается процедура Р2, а из процедуры Р2 — процедура Р1.

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

Неправильно: Подпрограммы: procedure PI; begin P2; end; procedure P2; begin PI; end; Правильно: procedure P2; forward; procedure PI;  begin P2;  end; procedure P2 ;  begin PI;  end; Теперь компилятор знает, как выглядит заголовок процедуры Р2, и может корректно сгенерировать машинный код для обращения к ней. Перегружаемые подпрограммы Хотя в Паскале не допускается использование одинаковых названий для пере¬менных, констант и других идентификаторов, для локальных переменных и под¬программ делается исключение. Так как в Паскале предъявляются строгие требова¬ния к типам данных, обращаться к подпрограмме, формальные параметры которой имеют тип Integer, с фактическими параметрами, имеющими тип Real, нельзя. Однако при решении задачи подчас бывает необходимо, чтобы подпрограмма с одним и тем же именем работала с разными типами данных. Здесь есть два способа действий: либо использовать данные типа Variant (что чре¬вато ошибками преобразования, снижает общую эффективность программы и требует от разработчика повышенной бдительности), либо применить перегружае¬мые подпрограммы. Они отличаются от обычных подпрограмм тем, что имеют совпадающие имена, а различаются только типами аргументов. Чтобы указать ком¬пилятору, что конкретная подпрограмма — перегружаемая, надо вслед за ее заго¬ловком указать зарезервированное слово overload. При вызове такой подпрограммы компилятор по типам параметров автоматически определит, какую же подпрограмму конкретно надо использовать в данном месте.

procedure Ovl( X: Real ); overload

begin . . . end ; procedure Ovl( X: Byte ); overload

begin . . . end ; Ovl( 1 ); // вызывается процедура Ovl( X: Byte ) 

Ovl( 1.0 ); // вызывается процедура Ovl( X: Real )

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

procedure Ovl( X: Byte; Y: Real = 1 ); overloadbegin . . . end; procedure Ovl( X: Byte ); overloadbegin . . . end; При вызове  Ovl(1)

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

Локальное описание

Сразу за заголовком подпрограммы следует локальное описание типов, переменных и констант, локальных для данной подпрограммы и существующих только в ее грани¬цах. Такое описание подчиняется обычным правилам Паскаля. В нем разрешается использовать слова type, var и const. Локальное описание может быть опущено.

Вложенные подпрограммы

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

procedure Demo; type Tl = array [1..2] of Real;  var D, Dl: Tl; S: Real; procedure InDemo;  begin Dl := D; S := Dl[l] + Dl[2]  end; begin . . . end;

Уровень вложенности локальных подпрограмм неограничен. Тело  Тело подпрограммы

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

Если описывается функция, то в ее теле надо определить, как значение будет воз-вращено в вызываемую программу. Для этого есть два способа. 1. Соответствующее значение присваивается переменной, имя которой совпадает с названием функции. function Sum( А, В: Integer ): Integer;  begin Sum := A + В  end; Имя функции для возврата значения разрешается указывать только в левой части оператора присваивания. 2. Соответствующее значение присваивается специальной локальной перемен¬ной Result (эту переменную описывать не надо). function Sum( А, В: Integer ): Integer;  begin Result := A + В  end; Вызов подпрограммы Когда в тексте программы указывается имя ранее описанной подпрограммы с фак-тическими параметрами, то выполнение основной части программы останавлива¬ется и управление передается подпрограмме, до тех пор пока в ходе работы не будет достигнут ее конец (зарезервированное слово end). После этого управление пере¬дается обратно в программу (или другую подпрограмму), вызывавшую данную подпрограмму. Параметры должны следовать в строгом соответствии с порядком их описания в заголовке подпрограммы. Типы их так же должны точно совпадать с указанными. Если параметров у подпрограммы нет, то записывается только название подпро¬граммы и следующая за ней точка с запятой. Demo ; Функции, возвращающие значение, могут использоваться так же, как и процедуры. Например, описанную выше функцию Sum можно вызывать так: X := Sum( 2,2 ); а можно и так: Sum( 2,2 ); В последнем случае значение, возвращаемое функцией, просто теряется.

Процедуры, играющие роль операторов

С развитием языка Паскаль в рамках среды Delphi 7 в него добавлялось множество новых полезных возможностей, нередко заимствованных из других языков про¬граммирования. Эти возможности вводились в Паскаль не в виде новых операторов, что нарушило бы идеологию языка, а в виде стандартных подпрограмм, которые, хотя и не выделяются цветом наравне с другими ключевыми словами, тем не менее, фактически являются таковыми. И реализуются подобные подпрограммы не в виде обращений к машинному коду, хранимому в программной библиотеке. Компиля¬тор не добавляет в генерируемый код ссылку, а превращает данную «процедуру», подобно обычным операторам, в небольшой набор машинных инструкций (а иногда и в одну такую инструкцию).

Одна из таких весьма полезных процедур — Exit (без параметров). Exit; При ее выполнении происходит немедленное завершение текущей подпрограммы и передача управления вызывающей программе. Такая возможность часто требу¬ется, когда логика, реализуемая в подпрограмме, достаточно сложна и организовать линейный выход из подпрограммы (по достижении ее конца) затруднительно.

Полезна подпрограмма Exit и в тех случаях, когда при определенных значениях параметров вычислить значение функции удается сразу. Например, если при вычис-лении факториала числа параметр равен 1, можно сразу определить возвращаемое значение, также равное 1, и покинуть подпрограмму. Процедурные типы

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

type TSumFun = function( А, В: Integer ): Integer;  TEmptyProc = procedure;  TMyProc = procedure( X: Real ); var SumP: TSumFun; EmptyProc: TEmptyProc;  MyPr: TMyProc; Далее, если в тексте описана подпрограмма, то ее можно присвоить переменной соответствующего типа: SumP := Sum;  EmptyProc := Demo;

Для обращения к нужной подпрограмме теперь можно указывать не только ее имя, но и имя переменной, хранящей «описание» этой подпрограммы: X := SumP( 2,2 ); 

Реально здесь произойдет вызов функции Sum с аргументами 2 и 2. Подпрограмму можно передавать в другую подпрограмму как параметр. Это удобно, когда над аргументами надо выполнять различные сложные действия в зависимости от некоторых условий. В приведенном ниже примере функция MathAction будет вычислять или сумму, или разность параметров, в зависимости от того, какая функ¬ция указана в качестве параметра. type TMathFun = function( X,Y: Integer ): Integer; function Add( X,Y: Integer ): Integer; begin Add := X+Y end; function Sub( X,Y: Integer ): Integer; begin Sub := X-Y end; function MathAction( X,Y: Integer; Proc: TMathFun ): Integer; begin Result := Proc( X,Y )  end; Теперь к функции MathAction можно обращаться так: WriteLn( MathAction) 10, 4, Add ) );  при этом будет напечатано число 14, или так: WriteLn( MathAction( 10, 4, Sub ) );

В этом случае будет напечатано число 6. Чтобы проверить, содержит ли переменная процедурного типа описание конкретной подпрограммы, используется стандартная функция Assigned(), которая в качестве аргумента получает процедурную переменную, а возвращает значение типа Boolean (если оно равно True, то переменная имеет корректное значение). С переменными, хранящими указатели на функции, надо обращаться осторожно. Переменной, хранящей указатель на процедуру, можно присвоить значение такой же переменной. В то же время, если в левой части оператора присваивания стоит пере¬менная типа, совпадающего с типом значения, возвращаемого функцией, то про¬изойдет вызов функции. Когда надо выполнять копирование указателей, а когда — вызывать функции, решает компилятор в зависимости от контекста

type TP = procedure; TF = function: integer; var pi, p2: TP;  fl, f2: TF;  N: Integer; procedure A;  begin  end; function B: integer;  begin  В := 0  end; p2 := A;  p1 := p2; // произойдет копирование указателя f2 := В;  f1 := f2; // произойдет копирование указателя  N := f2; // произойдет вызов функции В  //и запись значения в переменную N

Указатели на подпрограммы

Хотя переменные, описанные как процедурные типы, фактически являются указате-лями, от программиста эта их особенность скрыта. Но Паскаль разрешает также явно описывать переменные-указатели на подпрограммы. Для этого в языке введен особый тип данных Pointer, представляющий собой указатель на информацию, не имеющую конкретного типа. Получение адреса начала подпрограммы выполняется с помощью операции @, так же, как и для получения адреса любых других данных, 

var X: Pointer;

X := @МуProcedure;

Подобным способом в программе определяются и процедурные константы-указатели: const Р: Pointer = @MyFunction;

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]