Содержание     Область видимости объектов Механизм передачи параметров Предварительные и внешние описания Реккурсия Процедурные типы  
 

 

Процедуры и функции

 

1. Блочная структура программ

Подпрограмма - обособленная именованная часть программы со своим собственным локальным контекстом имён.

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

2. Общая структура подпрограммы

Структура подпрограммы в Турбо-Паскале почти буквально повторяет структуру всей программы, и в общем случае состоит из 3-х основных компонент:

  • Интерфейс подпрограммы (информация, необходимая для её вызова). Сосредоточен в заголовке.

  • Локальный контекст подпрограммы (совокупность описаний объектов, с которыми осуществляются действия)

  • Собственно действия, составляющие смысл подпрограммы (последовательность операторов и вызовов подпрограмм, то есть программа на Паскале).

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

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

Смысл функций - определить алгоритм вычисления нового значения некоторого простого или ссылочного типа. Функция, это подпрограмма, которая, как и процедура, что-то делает, но, помимо этого, она обязательно возвращает значение, тип которого задается при описании заголовка процедуры. Вызов функции является одним из допустимых операндов выражения, обозначая в нём то значение, которое вычисляет функция, то есть, если подпрограмма с идентификатором “Function1” – функция, то можно произвести следующие действия:

X:=Function1(“параметры – аргументы функции”);
X:=2*Function1(a,e,…,m,…)-1/Function1(c,f,…,k,…); и т.д.

3. Области видимости объектов

  • Имена объектов, описанных в некотором блоке, считаются известными в пределах данного блока, включая и все вложенные блоки.

  • Имена объектов, описанные в блоке, должны быть уникальными в пределах данного блока и могут совпадать с именами объектов из других блоков.

  • Если в некотором блоке описан объект, имя которого совпадает с именем объекта, описанного в объемлющем блоке, то последнее становится недоступным в данном блоке (экранировка).

Пример:
Program X;
Var
  I,j:integer;
  z:real;

  Procedure x1(i:integer;);
  Var
    j:integer;
    begin
    End;
  Begin
  End;

  Procedure x2;
  Var
     z:integer;

     Procedure x3(z:string);
     Begin
     End;

     Function y4(z:string):integer;
     Begin
     End;

  Begin
  End;

Begin
End.

Приведенная ниже диаграмма показывает видимость идентификаторов в примере. Каждый идентификатор виден в пределах того прямоуголльника, в котором он описан и в прямоугольниках, содержащихся в данном. За пределами этого прямоугольника идентификатор либо неизвестен, либо имеет другое значение, тип. Так, например, функция y4 может быть вызвана лишь внутри процедур x2 и x3, аналогично и x3. Переменные z в теле пограммы и в каждой из x2, x3, y4 имеют абсолытно разные и независимые значения, а в x1 видима таже z, что и в теле программы.В тоже время переменные i и j перекрыты в x1 новыми определениями, а в x2, x3, y4, они те же, что и в основной программе.

4. Механизм передачи параметров

В заголовке подпрограммы может быть задан список формальных параметров. Каждый параметр, заданный в заголовке, считается локальным в данной подпрограмме. Идентификаторы формальных параметров можно считать условными обозначениями реальных (фактических) параметров, которые будут переданы в подпрограмму при её вызове.

Внимание: Типы формальных параметров должны обязательно обозначаться идентификаторами.

Недопустимо:
Procedure InCorrect (Var A: Array [1..10] of Byte);
Нужно:
Type
MyArray = Array [1..10] of Byte;
Procedure Correct (Var A: MyArray);

Допустимы по крайней мере три способа задания формальных параметров:

  1. параметры, перед которыми отсутствует служебное слово Var и за которыми следует идентификатор типа;

  2. параметр, перед которым Var и далее тип;

  3. параметр со словом Var и не имеющие типа.

Эти три способа задания формальных параметров отражают три различных способа передачи параметров a - по значению; b - по ссылке; c - передача нетипизированных параметров по ссылке (b, c - параметры-переменные).

Параметры - значения.
Наиболее распространенный и простой способ. Параметр - обычная локальная переменная. Может использовать выражение. Любые действия внутри подпрограммы никак не отражаются на значениях переменной вне подпрограммы.

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

Procedure Swap (Var X,Y: Real);
Var
  T: Real;
Begin
  T:= X;
  X:= Y;
  Y:= T;
End;

Переменные файловых типов могут передаваться в подпрограмму только как параметры - переменные.

Безтиповые параметры.
Var Ident, где Ident - идентификатор формального параметра.
Фактический параметр, соответствующий формальному нетипизированному, должен представлять собой переменную любого типа (но не выражение).
Единственным способом использования таких параметров является “наделение” их определённым типом.

Применить операцию приведения типа.
Описать в подпрограмме локальную переменную определённого типа с совмещением её в памяти с нетипизированным параметром.

Пример1: функция для сравнения значений двух переменных любых типов и, соответственно, любых размеров:
Function Equal (Var Source, Dest; Size: Word): Boolean;
Type
  Bytes = Array [0..MaxInt] of Byte;
Var
  N: Integer;
Begin
  N:= 0;
  While (N < Size) and (Bytes(Dest)[N] - Bytes(Source)[N]) do
    N:=N+1;
  Equal:= (N = Size);
End;

{Имеются описания:}

Type
  Vector = Array [1..10] of Integer;
  Point = Record
   X,Y: Integer;
  End;
Var
  Vec1, Vec2: Vector;
  N: Integer;
  P: Pointer;
Begin
  . . . . . . . . . . . . . . . . . . . . . . . . .
  Equal(Vec1, Vec2, SizeOf(Vector)); {сравниваются все элементы Vec1 c Vec2}
  Equal(Vec1, Vec2, SizeOf(Integer)*N); {сравниваются N первых элементов Vec1 и Vec2}
  Equal(Vec1[1], Vec2[6], SizeOf(Integer)*5); {сравниваются 5 первых элементов Vec1 c пятью последними Vec2}
  Equal(Vec1[1], P, 4); {сравнивает Vec1[1] c P.x и Vec2 с P.y}

Пример2: задание различных действий в зависимости от размера переданного нетипизированного параметра:
Procedure Size (Var X);
Var
  V1: Byte absolute X;
  V2: Word absolute X;
  V3: LongInt absolute X;

Begin
  Case SizeOf(X) of
    SizeOf(Byte):
    Begin
    . . V1 . .
    End;
    SizeOf(Word):
    Begin
    . . V2 . .
    End;
    SizeOf(LongInt):
    Begin
    . . V3 . .
    End;
  End;
End;

Вычисление значения функции, завершение подпрограммы.
Смысл функции заключается в задании алгоритма вычисления некоторого значения и организации возврата этого значения в точку вызова.

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

Работа процедуры или функции по определению завершится после выполнения последнего оператора её тела. Однако Турбо-Паскаль имеет оператор Exit, возвращающий программу в точку вызова. Перед выполнением Exit в теле функции должен выполнится оператор присваивания имени функции значения.

5. Предварительные и внешние описания подпрограмм

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

Две подпрограммы, описанные на одном уровне, содержат взаимные вызовы друг друга.
Procedure A(X,Y: Real);
Begin
  . . . . . .
  B (2, 2);
  . . . . . .
End;

Procedure B (A,B: Integer);
Begin
  . . . . . .
  A (1.5, 2.8);
  . . . . . .
End;

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

Procedure A(X,Y: Real): Forward;
Procedure B(A,B: Integer): Forward;
. . . . . . . . .
Procedure A;
Begin
. . . . . . . .
End;

Procedure B;
Begin
. . . . . . . .
End;
В случае предварительного описания подпрограммы далее в тексте должно обязательно содержаться её определяющее описание, даже если нигде в программе не встречается вызов этой подпрограммы.

Подпрограмма или группа подпрограмм разработана вне системы Турбо-Паскаля, на другом языке, и необходимо использовать её в данной Pascal-программе.
Внешнее описание. Объектный код подпрограммы содержится в OBJ- файле.
В Pascal-программе необходимо описать заголовок подключаемой подпрограммы, после которого должно идти слово external, и указать имя файла, содержащего подпрограмму: {$L <имя .OBJ-файла>}
{$L ABC.OBJ}
Procedure A(C,D,E: Real); External;
Procedure B(I,F,J: Integer); Extenal
Для обеспечения корректности такого подключения необходимо соблюдать определённые межъязыковые соглашения о связях принятые в Турбо-Паскале.

6. Специальные случаи

Описание тела подпрограммы в машинных кодах - используется специальная конструкция со словом inline или серия ассемблерных инструкций с использованием слова asm.
Следующие коды эквивалентны:
asm
         mov cx,100
metka mov i,cx
. . . . . . . .
         loop metka
end;

И:

for i:=100 downto 0 do
. . . . . . . . . .
Однако, код, написанный между asm и end выполнится быстрее, нежели всего одна строка, заменяющая его с использованием оператора цикла паскаля.

В описании процедуры перед блоком может указываться специальное слово interrupt, которое служит для определения процедур прерываний. Допускается указание “типа вызова” процедуры (Far, Near). Для функций не допускается interrupt, тоесть она не может быть обработчиком прерывания.

7. Рекурсия и побочный эффект

В теле подпрограммы доступны все объекты, описанные в объемлющем блоке, в том числе и имя самой подпрограммы. Подпрограммы, вызывающие сами себя, называются рекурсивными.

 

Пример: алгоритм вычисления факториала: N! = (N-1)!*N
Fuction Fact(N: Word): LongInt;
Begin
  If N = 1 then
    Fact:= 1
  else
    Fact:= N * Fact(N-1);
End;
В Турбо-Паскале нет никаких ограничений на рекурсивные вызовы подпрограмм, необходимо только помнить, что каждый очередной рекурсивный вызов приводит к образованию новой копии локальных объектов подпрограммы и все эти копии, соответствующие цепочке активизированных и незавершенных рекурсивных вызовов существуют независимо друг от друга.

 

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

 

Например:
Program SideEffect;
Var
  A,Z: Integer;
  F: Text;

Function Change(X: Integer): Integer;
Begin
  Z:= Z - X; { изменяем значение локальной переменной }
  Change:= Sqr (X);
End;

Begin
  Assign(F, ‘Prn’);
  Rewrite (F);

  Z:= 10;
  A:= Change (Z);
  WriteLn (F, A,’ ’,Z);

  Z:= 10;
  A:= Change (10) * Change (Z);
  WriteLn (F,A,’ ‘,Z);

  Z:= 10;
  A:= Change (Z) * Change (10);
  WriteLn (F,A,’ ‘,Z);
End;

Результаты:
100 0
10000 -10
0 0

8. Распределение памяти под данные

Память под глобальные и типизированные константы выделяется при запуске программы в одном сегменте (65520 байт - сегмент данных программы).
Локальные переменные динамически размещаются в том же сегменте при активизации подпрограммы.
При рекурсии или просто вызове подпрограммы используется стек. Через стек передаются параметры подпрограмм, в стеке сохраняются переменные, вышедшие за область видимости при передаче управления в подпрограмму. Причем дописывание информации в стек происходит при каждом рекуррентном вызове , поэтому нужно следить за глубиной рекурсии, иначе слишком длинная, или бесконечная рекурсия забьет все стековое пространство и программа не сможет выполняться дальше, произойдет ошибка времени исполнения. Максимальный объём стековой памяти в Турбо-Паскале - 1 сегмент (65520 байт). Можно устанавливать размер стека и контроль за переполнением стека - $M и $S (тоесть, компилятор может включить в программу код, следящий за переполнением стека и выдающий вовремя сообщение о переполнении стека, вместо зависания программы, обычно этот ключ установлен по умолчанию). Но не следует всегда выставлять непременно максимальный обьем стека, так как ето приводит к увеличению размера памяти, занимаемой программой при исполнении.

9. Процедурные типы

Турбо-Паскаль позволяет вводить переменные специального типа, значениями которых могут служить подпрограммы. Можно интерпретировать подпрограмму как значения, которые можно присваивать переменным и передавать их в качестве параметров (речь идёт о подпрограммах как о целостных объектах, а не о значениях, возникающих в процессе их выполнения).

 

{$F+}
Var
P: Procedure;


Значениями Р могут быть любые процедуры без параметров. В более общем случае:

Type
  Func = Function(X,Y: Integer): Integer;
Var
  F1,F2: Func;
 

Например, если есть функция:
Function Add(A,B: Integer): Integer;
Begin
  Add:= A + B;
End;

 

то допустимо F1:= Add, в этом случае переменной F1 присваивается функция Add как таковая, но её исполнения не происходит.Теперь можно:
Write (Add (1,2)) или Write (F1 (1,2));.

 

Следует обратить внимание на строку {$F+} – она существенна, это ключ компилятора, и если он выключен, при присвоении переменным процедурного типа значений конкретных подпрограмм, возникнет ошибка присвоения типа.


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


Процедурные типы допускают также присвоение вида F1:= F2;
Такие переменные можно использовать для вызова подпрограмм, которые присвоены этим переменным.


Пример:
Var
  Oper: Function(X, Y: Real): Real;

Function Add(A,B: Real): Real;
Begin
  Add:= A + B;
End;

Function Sub (A,B: Real): Real;
Begin
  Sub:= A - B;
End;
тогда в основной программе можно использовать:
  If <условие> then
    Oper:= Add
  else
    Oper:= Sub;
  Write (Oper(2.5, X+Y));
 

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


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


Type
  Proc = Procedure(T: Real);
  Notice = Record
    Next: Integer;
    Time: Real;
    Action: Proc;
  End;
Var
  New_Notices: Array[1..10] of Proc;
  Notices: Notice;
 

Правила корректной работы с процедурными типами.

  • Подпрограмма, присваиваемая процедурной переменной должна быть странслирована в режиме “дальнего вызова”.

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

    Var
      Func: Function(R: Real): Real;
      . . . . . . . . . . . . . . . .
      Function MyExp(R: Real): Real
    Begin
      MyExp:= Exp (R);
    End;
    . . . . . . . . . . . . . . . .
    Begin
      Func:= MyExp;

  • Подпрограмма, присваиваемая процедурной переменной, не может быть вложенной в другие подпрограммы.

  • Подпрограмма, присваиваемая процедурной переменной, не может быть подпрограммой специального вида (interrupt или inline).

Процедурная переменная занимает в памяти 4 байта (2 слова). В первом хранится смещение, во втором - сегмент (т.е. указатель на код подпрограммы).

 

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


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


К сожалению есть неопределённость для компилятора, например в таком случае:
Type
  Func = Function: Real;
Var
  F: Func;

Function FFF: Real;
Begin
  FFF:= 1.25;
End;

Function FF: Real;
Begin
  FF:= 2.10;
End;
  . . . . . . . . . .
  F:= FF;
  If F = FFF then . . . .


В подобных случаях неочевидно, должен ли компилятор сравнивать значение процедуры F с FFF или нужно вызвать процедуру F и FFF и сравнить их значения. Принято, что такое вхождение идентификатора подпрограммы означает вызов функции.


Чтобы сравнить значение переменной F со значением (адресом) подпрограммы FFF нужно использовать следующую конструкцию:
If @F = @FFF then . . . .


Чтобы получить адрес самой процедурной переменной нужно написать:
@@F


Приведение типов переменных для процедурных типов.
Определены следующие типы и переменные:

Type
Func:= Function(X: Integer): Integer;
Function MyFunc(X: Integer): Integer;
Begin
  MyFunc:= X;
End;

Var
  F: Func;
  P: Pointer;
  N: Integer;
 

Можно построить следующие присваивания:
 

 F:= MyFunc  {переменной F присваивается функция MyFunc}
 N:= F(N)  {функция MyFunc вызывается через переменную F}
 P:= @F  {P получает указатель на функцию MyFunc}
 N:= Func (P)(N)  {функция MyFunc вызывается через указатель P}
 F:= Func (P)  {присвоить значение подпрограммы в P переменной F}
 Func(P):= F  {присвоить значение подпрограммы в F указателю P}
 @F:= P  {присвоить значение указателя в P переменной F}