Глава 8
                            ПРОЦЕДУРЫ И ФУНКЦИИ
 
  8.1 Локализация имён 
  8.2 Описание подпрограммы
    8.2.1 Заголовок
    8.2.2 Параметры
  8.3 Параметры массивы и параметры строки
    8.3.1. Заголовок
    8.3.2. Параметры
  8.4 Процедурные типы. Параметры- Функции и Параметры- Процедуры
  8.5 Не типизированные Параметры- Переменные 
  8.6 Рекурсия и опережающее описание
  8.7 Расширенный синтаксис вызова функций
  Тест по всей теме
  оглавление

   Процедуры и функции, как отмечалось в гл.2, представляют собой относительно самостоятельные фрагменты программы, оформленные особым образом и снабженные именем. Упоминание этого имени в тексте программы называется вызовом процедуры (функции). Отличие функции от процедуры заключается в том, что результатом исполнения операторов, образующих тело функции, всегда является некоторое единственное значение простого, строкового типа или указателя, поэтому обращение к функции можно использовать в соответствующих выражениях наряду с переменными и константами. Условимся далее называть процедуру или функцию общим именем «подпрограмма», если только для излагаемого материала указанное отличие не имеет значения.
Подпрограммы представляют собой инструмент, с помощью которого любая программа может быть разбита на ряд в известной степени независимых друг от друга частей. Такое разбиение необходимо по двум причинам.
  Во-первых, это средство экономии памяти: каждая подпрограмма существует в программе в единственном экземпляре, в то время как обращаться к ней можно многократно из разных точек программы. При вызове подпрограммы активизируется последовательность образующих ее операторов, а с помощью передаваемых подпрограмме параметров нужным образом модифицируется реализуемый в ней алгоритм.
  Вторая причина заключается в применении современных методов нисходящего проектирования программ (см. гл.2). В результате применение этих методов алгоритм представляется в виде последовательности относительно крупных подпрограмм, реализующих более или менее самостоятельные смысловые части алгоритма. Подпрограммы в свою очередь могут разбиваться на менее крупные подпрограммы, те - на подпрограммы нижнего уровня и т.д. (рис. 11). Последовательное структурирование программы продолжается до тех пор, пока реализуемые подпрограммами алгоритмы не станут настолько простыми, чтобы их можно было легко запрограммировать.
  В этой главе подробно рассматриваются все аспекты использования подпрограмм в Турбо Паскале.
 
                             Вверх

                   8.1. ЛОКАЛИЗАЦИЯ ИМЕН

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

Не делается исключения и в отношении подпрограмм: каждую свою процедуру и функцию программисту необходимо описать в разделе описаний.
 Описать подпрограмму - это значит указать ее заголовок и тело заголовке объявляются имя подпрограммы и формальные параметры, ли они есть. Для функции, кроме того, указывается тип возвращаемого результата. За заголовком следует тело подпрограммы, которое, поди программе, состоит из раздела описаний и раздела исполняемых операторов. В разделе описаний подпрограммы могут встретиться описания подпрограмм низшего уровня, в тех - описания других подпрограмм и т.д.
 
 Вот какую иерархию описаний получим, например, для программ, структура которой изображена на рис. 11 (для простоты считается, что подпрограммы представляют собой процедуры без параметров): 
Program ...; 
Procedure A; 
Procedure A1; ,
..............
begin 
end {A1}; 
Procedure A2: 
begin 
begin {A} 
end (A}; 
Procedure B; 
Procedure B1; 
begin 
end'{B1}; 
Procedure B2; 
Procedure B21;
..............
   и т.д.

  Подпрограмма любого уровня имеет обычно множество имен констант, переменных, типов и вложенных в нее подпрограмм низшего уровня. Считается, что все имена, описанные внутри подпрограммы, локализуются в ней, т.е. они как бы «невидимы» снаружи подпрограммы. Таким образом, со стороны операторов, использующих обращение к подпрограмме, она трактуется как «черный ящик», в котором реализуется тот или иной алгоритм. Все детали этой реализации скрыты от глаз пользователя подпрограммы и потому недоступны ему. Например, в рассмотренной выше примере из основной программы можно обратиться к процедурам А и В, но нельзя вызвать ни одну из вложенных в них процедур А1, А2, B1
ит.д
Сказанное относится не только к именам подпрограмм, но и вообще м к любым именам, объявленным в них - типам, константам, переменным i меткам. Все имена в пределах подпрограммы, в которой они объявлены должны быть уникальными и не могут совпадать с именем самой подпрограммы.
  При входе в подпрограмму низшего уровня становятся доступными не только объявленные в ней имена, но и сохраняется доступ ко все) именам верхнего уровня. Образно говоря, любая подпрограмма как 6ы окружена полупрозрачными стенками: снаружи подпрограммы мы н видим ее внутренности, но, попав в подпрограмму, можем наблюдать все что делается снаружи. Так, например, из подпрограммы В21 мы может  вызвать подпрограмму А, использовать имена, объявленные в основной программе, в подпрограммах В и В2, и даже обратиться к ним. Люба подпрограмма может, наконец, вызвать саму себя - такой способ вызова называется рекурсией.
Пусть имеем такое описание:
Program ..; 
var V1 : ...;
Procedure A;
var V2;:
 .. .
end (A};
 Procedure B:
 var
 V3 ;
 ...
 Procedure B1;
 var V2;
 ...
 Procedure B11;
 var V5:
Из процедуры Bll доступны все пять переменных V1,...,VS, из процедуры B1 доступны переменные V1,,..,V4, из центральной программы только V1.
 При взаимодействии подпрограмм одного у ровня иерархии вступает в  силу основное правило Турбо Паскаля: любая подпрограмма перед использованием должна быть описана. Поэтому из подпрограммы В можно  вызвать подпрограмму А, но из А вызвать В невозможно (точнее, такая
возможность появляется только с использованием опережающего описаниям, см. п.8.6 ) Продолжая образное сравнение, подпрограмму можно уподобить ящику с непрозрачными стенками и дном и полупрозрачной крышей: из подпрограммы можно смотреть только «вверх» и не«вниз», т.е. подпрограмме доступны только те объекты верхнего уровня которые описаны до описания данной подпрограммы. Эти объекты называются глобальными по отношению к подпрограмме. В отличие от стандартного Паскаля в Турбо Паскале допускается произвольная последовательность описания констант, переменных, типов, меток и подпрограмм. Например, раздел VAR описания переменных
может появляться в пределах раздела описаний одной и той же подпрогаммы много раз и перемежаться с объявлениями других объектов и подпрограмм. Для Турбо Паскаля совершенно безразличен порядок следования и количество разделов VAR, CONST, TYPE, LABEL, но при определении области действия этих описаний следует помнить, что имена
санные ниже по тексту программы, недоступны из ранее описанных
программ, например: |
var VI : ...; 
Procedure S; 
var V2 : ...;
end (s);
var V3 ;
 ...

   Из процедуры S можно обратиться к переменным VI и V2, но не использовать V3, так как описание V3 следует в программе за описанием процедуры S. 
Имена, локализованные в подпрограмме, могут совпадать с ранее объявленными глобальными именами. В этом случае считается, что локальное имя «закрывает» глобальное и делает его недоступным, например:
var 
I : integer;
PROCEDURE P; 
var 
I : integer; 
begin 
writeln(l) 
end {P}; 
BEGIN 
I := 1;

END. |
Что напечатает эта программа? Все, что угодно: значение внутренней переменной 1при входе в процедуру Р не определено, хотя однйй
ная глобальная переменная имеет значение 1. Локальная переменная «закроет» глобальную и на экран будет выведено произвольное значение содержащееся в неинициированной внутренней переменной. Если убрать описание 
   var 
  I : Integer; 
из процедуры Я, то на экран будет выведено значение глобальной переменной I, т.е. 1. 
Таким образом, одноименные глобальные и локальные переменные- это разные переменные. Любое обращение к таким переменными подпрограммы трактуется как обращение к локальным переменным т.е глобальные переменные в этом случае попросту недоступны.
 
             Вверх

8.2. ОПИСАНИЕ ПОДПРОГРАММЫ

Описание подпрограммы состоит из заголовка и тела подпрограммы.

8.2.1. Заголовок

   Заголовок процедуры имеет вид:
PROCEDURE <имя> [(<сп.ф.п.>)]; 
Заголовок функции:
FUNCTION <имя> {(<сп.ф.п.>)] :<тип>;
Здесь <имя> - имя подпрограммы (правильный идентификатор);
<сп.ф.п.> - список формальных параметров;
<тип> - тип возвращаемого функцией результата.
   Сразу за заголовком подпрограммы может следовать одна из стандартных директив ASSEMBLER, EXTERNAL, FAR, FORWARD, INLINE, INTERRUPT, NEAR. Эти директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее, т.е. ели за подпрограммой следует другая подпрограмма, стандартная директива, указанная за заголовком первой, не распространяется на вторую.
   ASSEMBLER - эта директива отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и перед выходом из нее. Тело подпрограммы в этом случае должно реализоваться с помощью встроенного ассемблера (см. п. 11.8).
   EXTERNAL - с помощью этой директивы объявляется внешняя подпрограмма (см. п.11.1).
FAR - компилятор должен создавать код подпрограммы, рассчитанный на дальнюю модель вызова. Директива NEAR заставит компилятор создать код, рассчитанный на ближнюю модель памяти. По умолчанию see подпрограммы, объявленные в интерфейсной части модулей, генерируются с расчетом на дальнюю модель вызова, а все остальные подпрограммы - на ближнюю модель.
   В соответствии с архитектурой микропроцессора ПК, в программах могут использоваться две модели памяти: ближняя и дальняя. Модель памяти определяет возможность вызова процедуры из различных частей программы: если используется ближняя модель, вызов возможен только в пределах 64 Кбайт (в пределах одного сегмента кода, который выделяется основной программе и каждому используемому в ней модулю); при дальней модели вызов возможен из любого сегмента. Ближняя модель экономит один байт и несколько микросекунд на каждом вызове подпрограммы, поэтому стандартный режим компиляции предполагает эту модель памяти. Однако при передаче процедурных параметров (см.п.8.4), а также в оверлейных модулях (см. п. 11.б) соответствующие подпрограммы должны компилироваться с расчетом на универсальную - дальнюю модель памяти, одинаково пригодную при любом расположении процедуры и вызывающей ее программы в памяти.
   Явное объявление модели памяти стандартными директивами имеет более высокий приоритет по сравнению с настройкой среды (см.прил.1).
FORWARD - используется при опережающем описании (см. п.8.6) для сообщения компилятору, что описание подпрограммы следует где-то дальше по тексту программы (но в пределах текущего программного модуля). 
INLINE - указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных инструкций (см. п. 11.2). 
INTERRUPT - используется при создании процедур обработки прерываний (см. п. 11.4).

 

                                Вверх

 
8.2.2. Параметры

   Список формальных параметров необязателен и может отсутствовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их тип, например:
Procedure SB (a : real; b : integer: с : char): 
   Как видно из примера, параметры в списке отделяются друг от друга точками с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, вместо
Function F (а : real; b : real) : real: 
можно написать проще:
Function F (a, b : real) : real;

   Операторы тела подпрограммы рассматривают список формальных параметров как своеобразное расширение раздела описаний: все переменные из этого списка могут использоваться в любых выражениях внутри подпрограммы. Таким способом осуществляется настройка алгоритма подпрограммы на конкретную задачу.
   Рассмотрим следующий пример. В языке Турбо Паскаль нет операции возведения в степень, однако с помощью встроенных функций LN(X) и ЕХР(Х) нетрудно реализовать новую функцию с именем, например, РО WER, осуществляющую возведение любого вещественного числа в любую вещественную степень. В программе (пример 14) вводится пара чисел X и Y и выводится на экран дисплея результат возведения X сначала в степень+У, а затем - в степень - Y. Для выхода из программы нужно ввести Ctrl—Z и «Ввод». Пример 14.
var
  х, у : real;
FUNCTION Power(a,b : real) : real;
begin (Power)
  if a > 0 then Power := exp(b * ln(a))
  else
  If a < 0 then
  Power := exp(b * In(abs(a))) else
  If b=0 then Power := 1 
  else Power := 0 
end (Power);

BEGIN {main} 
  repeat
  readIn(x.y);
  wrIteln(power(x,y):12:10, power(x,-y):15:10)
  untiI EOF 
END {main}.

   Для вызова функции POWER мы просто указали ее в качестве параметра при обращении к встроенной процедуре WRITELN. Параметры и Y в момент обращения к функции - это фактические параметры. Они подставляются вместо формальных параметров A и B заголовке функции и затем над ними осуществляются нужные действия. Полученный результаты присваивается идентификатору функции - именно он и будет возвращен как значение функции при выходе из нее. В программе функции POWER вызывается дважды - сначала с параметрами X и Y, а затем X и -У, поэтому будут получены два разных результата.
   Механизм замены формальных параметров на фактические позволяет нужным образом настроить алгоритм, реализованный в подпрограмме. Турбо Паскаль следит за тем, чтобы количество и тип формальны параметров строго соответствовали количеству и типам фактических параметров в момент обращения к подпрограмме. Смысл используемых фактических параметров зависит от того, в каком порядке они перечислены при вызове подпрограммы. В примере 14 первый по порядку фактический параметр будет возводиться в степень, задаваемую вторым параметром, а не наоборот. Пользователь должен сам следить за правильным порядком перечисления фактических параметров при обращении к подпрограмме.
   Любой из формальных параметров подпрограммы может быть ли параметром-значением, либо параметром-переменной. В предыдущие примере параметры А и В определены как параметры-значения. Есть параметры определяются как параметры-переменные, перед ними необходимо ставить зарезервированное слово VAR, например:
Function Power (var a : real; b : real) : real;
   Здесь параметр А - параметр-переменная, а В -параметр-значение. Определение формального параметра тем или иным способом существенно только для вызывающей программы: если формальный параметр объявлен как параметр-переменная, то при вызове подпрограммы ему должен соответствовать фактический параметр в виде переменной структурного типа; если формальный параметр объявлен как параметр-значение то при вызове ему может соответствовать произвольное выражение. Koнтроль за неукоснительным соблюдением этого правила осуществляет компилятором Турбо Паскаля. Если бы для предыдущего примера был использован такой заголовок функции:
Function Power ( var a, b : real) : real;
то при втором обращении к функции компилятор указал бы на несоответствие типа фактических и формальных параметров (параметр -Y ecть выражение, в то время как соответствующий ему формальный параметр описан как параметр-переменная).
Для того чтобы понять, в каких случаях использовать параметры значения, а в каких - параметры-переменные, рассмотрим, как осуществляется замена формальных параметров на фактические в момент обращения к подпрограмме.
   Если параметр определен как параметр-значение, то перед вызовом подпрограммы это значение вычисляется, полученный результат копируется во временную память и передается подпрограмме. Важно учесть, что даже если в качестве фактического параметра указано простейшее выражение в виде переменной или константы, все равно подпрограмме будет передана лишь копия переменной (константы). Если же параметр определен как параметр-переменная, то при вызове подпрограммы передается сама переменная, а не ее копия. Любые возможные изменения в подпрограмме параметра-значения никак не воспринимаются вызывающей программой, так как в этом случае изменяется копия фактического параметра, в то время как изменение параметра-переменной приводит к изменению самого фактического параметра в вызывающей программе. 
   Представленный ниже пример 15 поясняет изложенное. В программе задаются два целых числа 5 и 7, эти числа передаются процедуре INC2,в которой они удваиваются. Один из параметров передается как параметр-переменная, другой - как параметр-значение. Значения параметров до и после вызова процедуры, а также результат их удвоения выводятся на экран. 
Пример 15.
const 
  а :Integer = 5;
  Ь :integer = 7; 
{——————}
PROCEDURE Inc2 ( var с : Integer; b : Integer); 
begin {Inc2}
  c:=c+c;b:=b+b;
  wrlteln('удвоенные :'. c:5, b:S) 
end{lnc2};
BEGIN {main}
  writeln('исходные :', a:5, b:S);
  Inc2(a.b);
  wrlteln('результат :', a:5. b:S);
END {main). 
В результате прогона программы будет выведено: 
Исходные: 5 7 
Удвоенные: 10 14 
результат: 10 7 
   Как видно из примера, удвоение второго формального параметра в процедуре INC2 не вызвало изменения фактической переменной В, так как этот параметр описан в заголовке процедуры как параметр-значение. 
   Этот пример может служить еще и иллюстрацией механизма «закрывания» глобальной переменной одноименной локальной: хотя переменная объявлена как глобальная (она описана в вызывающей программе перед , описанием процедуры), в теле процедуры ее «закрыла» локальная переменная В, объявленная как параметр-значение.
   Итак, параметры-переменные используются как средство связи алгоритма, реализованного в подпрограмме, с «внешним миром»: с помощью этих параметров подпрограмма может передавать результаты своей работы вызывающей программе. Разумеется, в распоряжении программиста всегда есть и другой способ передачи результатов - через глобальные переменные. Однако злоупотребление глобальными связями делает программу , как правило, запутанной, трудной в понимании и сложной отладке. В соответствии с требованиями хорошего стиля программирования рекомендуется там, где это возможно, использовать передачу результатов через фактические параметры-переменные.
   С другой стороны, описание всех формальных параметров как napаметров-переменных нежелательно по двум причинам. Во-первых, это исключает возможность вызова подпрограммы с фактическими параметрами в виде выражений, что делает программу менее компактной. Во-вторых, и главных, в подпрограмме возможно случайное использование формального параметра, например, для временного хранения промежуточного результата, т.е. всегда существует опасность непреднамеренно «испортить» фактическую переменную. Вот почему параметрами-переменные следует объявлять только те, через которые подпрограмма в действительности передает результаты вызывающей программе. Чем меньше napаметров объявлено параметрами-переменными и чем меньше в подпрограмме используется глобальных переменных, тем меньше опасность noлучения непредусмотренных программистом побочных эффектов, связанных с вызовом подпрограммы, тем проще программа в понимании и отладке.
   По той же причине не рекомендуется использовать параметры-переменные в заголовке функции: если результатом работы функции не может быть единственное значение, то логичнее использовать процедуру или нужным образом декомпозировать алгоритм на несколько подпрограмм.
   Существует одно обстоятельство, которое следует учитывать при выборе вида формальных параметров. Как уже говорилось, при объявлении параметра-значения осуществляется копирование фактического параметра во временную память. Если этим параметром будет массив большой размерности, то существенные затраты времени и памяти на копирование при многократных обращениях к подпрограмме могут стать peшающим доводом в пользу объявления такого параметра параметром-nepеменной или передачи его в качестве глобальной переменной.

 

                                 Вверх

 

8.3. Параметры массивы и параметры строки


8.3.1. Заголовок


   Заголовок процедуры имеет вид:
PROCEDURE <имя> [(<сп.ф.п.>)]; 
Заголовок функции:
FUNCTION <имя> {(<сп.ф.п.>)] :<тип>;
Здесь <имя> - имя подпрограммы (правильный идентификатор);
<сп.ф.п.> - список формальных параметров;
<тип> - тип возвращаемого функцией результата.
   Сразу за заголовком подпрограммы может следовать одна из стандартных директив ASSEMBLER, EXTERNAL, FAR, FORWARD, INLINE, INTERRUPT, NEAR. Эти директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее, т.е. ели за подпрограммой следует другая подпрограмма, стандартная директива, указанная за заголовком первой, не распространяется на вторую.
   ASSEMBLER - эта директива отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и перед выходом из нее. Тело подпрограммы в этом случае должно реализоваться с помощью встроенного ассемблера (см. п. 11.8).
   EXTERNAL - с помощью этой директивы объявляется внешняя подпрограмма (см. п.11.1).
FAR - компилятор должен создавать код подпрограммы, рассчитанный на дальнюю модель вызова. Директива NEAR заставит компилятор создать код, рассчитанный на ближнюю модель памяти. По умолчанию see подпрограммы, объявленные в интерфейсной части модулей, генерируются с расчетом на дальнюю модель вызова, а все остальные подпрограммы - на ближнюю модель.
   В соответствии с архитектурой микропроцессора ПК, в программах могут использоваться две модели памяти: ближняя и дальняя. Модель памяти определяет возможность вызова процедуры из различных частей программы: если используется ближняя модель, вызов возможен только в пределах 64 Кбайт (в пределах одного сегмента кода, который выделяется основной программе и каждому используемому в ней модулю); при дальней модели вызов возможен из любого сегмента. Ближняя модель экономит один байт и несколько микросекунд на каждом вызове подпрограммы, поэтому стандартный режим компиляции предполагает эту модель памяти. Однако при передаче процедурных параметров (см.п.8.4), а также в оверлейных модулях (см. п. 11.б) соответствующие подпрограммы должны компилироваться с расчетом на универсальную - дальнюю модель памяти, одинаково пригодную при любом расположении процедуры и вызывающей ее программы в памяти.
   Явное объявление модели памяти стандартными директивами имеет более высокий приоритет по сравнению с настройкой среды (см.прил.1).
FORWARD - используется при опережающем описании (см. п.8.6) для сообщения компилятору, что описание подпрограммы следует где-то дальше по тексту программы (но в пределах текущего программного модуля). 
INLINE - указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных инструкций (см. п. 11.2). 
INTERRUPT - используется при создании процедур обработки прерываний (см. п. 11.4).

 

                           Вверх


8.3.2. Параметры


   Список формальных параметров необязателен и может отсутствовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их тип, например:
Procedure SB (a : real; b : integer: с : char): 
   Как видно из примера, параметры в списке отделяются друг от друга точками с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, вместо
Function F (а : real; b : real) : real: 
можно написать проще:
Function F (a, b : real) : real;

   Операторы тела подпрограммы рассматривают список формальных параметров как своеобразное расширение раздела описаний: все переменные из этого списка могут использоваться в любых выражениях внутри подпрограммы. Таким способом осуществляется настройка алгоритма подпрограммы на конкретную задачу.
   Рассмотрим следующий пример. В языке Турбо Паскаль нет операции возведения в степень, однако с помощью встроенных функций LN(X) и ЕХР(Х) нетрудно реализовать новую функцию с именем, например, РО WER, осуществляющую возведение любого вещественного числа в любую вещественную степень. В программе (пример 14) вводится пара чисел X и Y и выводится на экран дисплея результат возведения X сначала в степень+У, а затем - в степень - Y. Для выхода из программы нужно ввести Ctrl—Z и «Ввод». Пример 14.
var
  х, у : real;
FUNCTION Power(a,b : real) : real;
begin (Power)
  if a > 0 then Power := exp(b * ln(a))
  else
  If a < 0 then
  Power := exp(b * In(abs(a))) else
  If b=0 then Power := 1 
  else Power := 0 
end (Power);


BEGIN {main} 
  repeat
  readIn(x.y);
  wrIteln(power(x,y):12:10, power(x,-y):15:10)
  untiI EOF 
END {main}.


   Для вызова функции POWER мы просто указали ее в качестве параметра при обращении к встроенной процедуре WRITELN. Параметры и Y в момент обращения к функции - это фактические параметры. Они подставляются вместо формальных параметров A и B заголовке функции и затем над ними осуществляются нужные действия. Полученный результаты присваивается идентификатору функции - именно он и будет возвращен как значение функции при выходе из нее. В программе функции POWER вызывается дважды - сначала с параметрами X и Y, а затем X и -У, поэтому будут получены два разных результата.
   Механизм замены формальных параметров на фактические позволяет нужным образом настроить алгоритм, реализованный в подпрограмме. Турбо Паскаль следит за тем, чтобы количество и тип формальны параметров строго соответствовали количеству и типам фактических параметров в момент обращения к подпрограмме. Смысл используемых фактических параметров зависит от того, в каком порядке они перечислены при вызове подпрограммы. В примере 14 первый по порядку фактический параметр будет возводиться в степень, задаваемую вторым параметром, а не наоборот. Пользователь должен сам следить за правильным порядком перечисления фактических параметров при обращении к подпрограмме.
   Любой из формальных параметров подпрограммы может быть ли параметром-значением, либо параметром-переменной. В предыдущие примере параметры А и В определены как параметры-значения. Есть параметры определяются как параметры-переменные, перед ними необходимо ставить зарезервированное слово VAR, например:
Function Power (var a : real; b : real) : real;
   Здесь параметр А - параметр-переменная, а В -параметр-значение. Определение формального параметра тем или иным способом существенно только для вызывающей программы: если формальный параметр объявлен как параметр-переменная, то при вызове подпрограммы ему должен соответствовать фактический параметр в виде переменной структурного типа; если формальный параметр объявлен как параметр-значение то при вызове ему может соответствовать произвольное выражение. Koнтроль за неукоснительным соблюдением этого правила осуществляет компилятором Турбо Паскаля. Если бы для предыдущего примера был использован такой заголовок функции:
Function Power ( var a, b : real) : real;
то при втором обращении к функции компилятор указал бы на несоответствие типа фактических и формальных параметров (параметр -Y ecть выражение, в то время как соответствующий ему формальный параметр описан как параметр-переменная).
Для того чтобы понять, в каких случаях использовать параметры значения, а в каких - параметры-переменные, рассмотрим, как осуществляется замена формальных параметров на фактические в момент обращения к подпрограмме.
   Если параметр определен как параметр-значение, то перед вызовом подпрограммы это значение вычисляется, полученный результат копируется во временную память и передается подпрограмме. Важно учесть, что даже если в качестве фактического параметра указано простейшее выражение в виде переменной или константы, все равно подпрограмме будет передана лишь копия переменной (константы). Если же параметр определен как параметр-переменная, то при вызове подпрограммы передается сама переменная, а не ее копия. Любые возможные изменения в подпрограмме параметра-значения никак не воспринимаются вызывающей программой, так как в этом случае изменяется копия фактического параметра, в то время как изменение параметра-переменной приводит к изменению самого фактического параметра в вызывающей программе. 
   Представленный ниже пример 15 поясняет изложенное. В программе задаются два целых числа 5 и 7, эти числа передаются процедуре INC2,в которой они удваиваются. Один из параметров передается как параметр-переменная, другой - как параметр-значение. Значения параметров до и после вызова процедуры, а также результат их удвоения выводятся на экран. 
Пример 15.
const 
  а :Integer = 5;
  Ь :integer = 7; 
{——————}
PROCEDURE Inc2 ( var с : Integer; b : Integer); 
begin {Inc2}
  c:=c+c;b:=b+b;
  wrlteln('удвоенные :'. c:5, b:S) 
end{lnc2};
BEGIN {main}
  writeln('исходные :', a:5, b:S);
  Inc2(a.b);
  wrlteln('результат :', a:5. b:S);
END {main). 
В результате прогона программы будет выведено: 
Исходные: 5 7 
Удвоенные: 10 14 
результат: 10 7 
   Как видно из примера, удвоение второго формального параметра в процедуре INC2 не вызвало изменения фактической переменной В, так как этот параметр описан в заголовке процедуры как параметр-значение. 
   Этот пример может служить еще и иллюстрацией механизма «закрывания» глобальной переменной одноименной локальной: хотя переменная объявлена как глобальная (она описана в вызывающей программе перед , описанием процедуры), в теле процедуры ее «закрыла» локальная переменная В, объявленная как параметр-значение.
   Итак, параметры-переменные используются как средство связи алгоритма, реализованного в подпрограмме, с «внешним миром»: с помощью этих параметров подпрограмма может передавать результаты своей работы вызывающей программе. Разумеется, в распоряжении программиста всегда есть и другой способ передачи результатов - через глобальные переменные. Однако злоупотребление глобальными связями делает программу , как правило, запутанной, трудной в понимании и сложной отладке. В соответствии с требованиями хорошего стиля программирования рекомендуется там, где это возможно, использовать передачу результатов через фактические параметры-переменные.
   С другой стороны, описание всех формальных параметров как napаметров-переменных нежелательно по двум причинам. Во-первых, это исключает возможность вызова подпрограммы с фактическими параметрами в виде выражений, что делает программу менее компактной. Во-вторых, и главных, в подпрограмме возможно случайное использование формального параметра, например, для временного хранения промежуточного результата, т.е. всегда существует опасность непреднамеренно «испортить» фактическую переменную. Вот почему параметрами-переменные следует объявлять только те, через которые подпрограмма в действительности передает результаты вызывающей программе. Чем меньше napаметров объявлено параметрами-переменными и чем меньше в подпрограмме используется глобальных переменных, тем меньше опасность noлучения непредусмотренных программистом побочных эффектов, связанных с вызовом подпрограммы, тем проще программа в понимании и отладке.
   По той же причине не рекомендуется использовать параметры-переменные в заголовке функции: если результатом работы функции не может быть единственное значение, то логичнее использовать процедуру или нужным образом декомпозировать алгоритм на несколько подпрограмм.
   Существует одно обстоятельство, которое следует учитывать при выборе вида формальных параметров. Как уже говорилось, при объявлении параметра-значения осуществляется копирование фактического параметра во временную память. Если этим параметром будет массив большой размерности, то существенные затраты времени и памяти на копирование при многократных обращениях к подпрограмме могут стать peшающим доводом в пользу объявления такого параметра параметром-nepеменной или передачи его в качестве глобальной переменной.

                               Вверх


8.4. ПАРАМЕТРЫ-МАССИВЫ И ПАРАМЕТРЫ-СТРОКИ


   Может сложиться впечатление, что объявление переменных в списке формальных параметров подпрограммы ничем не отличается от объявления их в разделе описания переменных. Действительно, в обоих случаях много общего, но есть одно существенное различие: типом любого napаметра в списке формальных параметров может быть только стандартный или ранее объявленный тип. Поэтому нельзя, например, объявить следующую процедуру:
Procedure S (а : array (1..10] of real);
так как в списке формальных параметров фактически объявляется тип диапазон, указывающий границы индексов массива.
   Если мы хотим передать какой-то элемент массива, то проблем, как правило, не возникает, но если в подпрограмму передается весь массив, то следует первоначально описать его тип. Например:
type
  atype = array [1..10) of real; 
PROCEDURE S (a : atype);
Поскольку строка является фактически своеобразным массивом, ее передача в подпрограмму осуществляется аналогичным образом:
type
  i ntype =stri ng[15];
  outype = string[30];
FUNCTION St (i : intype) : outype:
   Требование описать любой тип-массив или тип-строку перед объявлением подпрограммы на первый взгляд кажется несущественным. Действительно, в рамках простейших вычислительных задач обычно заранее известна структура всех используемых в программе данных, поэтому статическое описание массивов не вызывает проблем.           Однако разработка программных средств универсального назначения, например, типа широко используемых в среде Фортрана ОС ЕС библиотек подпрограмм для научных и инженерных расчетов, связана со значительными трудностями. По существу, речь идет о том, что в Турбо Паскале невозможно использовать в подпрограммах массивы с «плавающими» границами изменения индексов. Например, если разработана программа, обрабатывающая матрицу из 10 х 10 элементов, то для обработки матрицы из 9 х 11 элементов необходимо переопределить тип, т.е. перекомпилировать всю программу (сейчас речь идет не о динамическом размещении массивов в куче, а о статическом описании массивов и передаче их как параметров в подпрограммы). Этот недостаток, как и отсутствие в языке средств обработки исключительных ситуаций (прерываний), унаследован из стандартного Паскаля и представляет собой объект постоянной и вполне заслуженной его критики. Разработчики Турбо Паскаля не рискнули кардинально изменить свойства базового языка, но, тем не менее, включили в него некоторые средства, позволяющие в известной степени смягчить отмеченные недостатки.
   Прежде всего, в среде Турбо Паскаля можно установить режим компиляции, при котором отключается контроль за совпадением длины фактического и формального параметра-строки (см. прил.1). Это позволяет легко решить вопрос о передаче подпрограмме строки произвольной длины. При передаче строки меньшего размера формальный параметр будет иметь ту же длину, что и параметр обращения; передача строки большего размера приведет к ее усечению до максимального размера формального параметра. Следует сказать, что контроль включается только при передаче строки, объявленной как формальный параметр-переменная. Если соответствующий параметр объявлен параметром-значением, эта опция игнорируется и длина не контролируется.
   Значительно сложнее обстоит дело с передачей массивов произвольной длины. Наиболее универсальным приемом в этом случае будет, судя по всему, работа с указателями и использование индексной арифметики. Несколько проще можно решить эту проблему при помощи не типизированных параметров (см. п.8.5).

                  Вверх


8.5. ПРОЦЕДУРНЫЕ ТИПЫ. ПАРАМЕТРЫ-ФУНКЦИИ V ПАРАМЕТРЫ-ПРОЦЕДУРЫ


   Процедурные типы - это нововведение фирмы Borland (в стандартном Паскале таких типов нет). Основное назначение этих типов - дат программисту гибкие средства передачи функций и процедур в качеств фактических параметров обращения к другим процедурам и функциям.
   Для объявления процедурного типа используется заголовок процедур (функции), в котором опускается ее имя, например:
type
  Proc = Procedure (a, b, с : real; Var d : real);
  Proc2 = Procedure (var a, b);
  РгосЗ = Procedure;
  Func = Function : string;
  Func2 =Function (var S : string) : real;

   Как видно из приведенных примеров, существует два процедурных типа: тип-процедура и тип-функция.
   Пример 16 иллюстрирует механизм передачи процедур в качеств фактических параметров вызова. Программа выводит на экран таблиц двух функций: sinl(x) - (sin(x) + 1) * ехр(-х) и cosl(x) - (cos(x) + 1) ехр(-х). Вычисление и печать значений этих функций реализуются процедуре PR1NTFUNC, которой в качестве параметров передаются на мер позиции N на экране, куда будет выводиться очередной результат (c помощью этого параметра реализуется вывод в две колонки), и имя нужной функции. 
Пример 16.
Uses CRT; 
type
  Func = Function (x : real) : real;
  FUNCTION Sin1(x : real) : real; far;
BEGIN
  sinl := (sin(x) + 1) * exp(-x) 
END;
FUNCTION Cos1(x : real) : real; far;
BEGIN
  cosl := (cos(x) + 1) * exp(-x) 
END;
PROCEDURE PrintFunc(n : byte; F : Func);
const
  np = 20; {количество вычислений функций)
var
  х : real;
  I : Integer;
BEGIN {PrintFunc}
  for I := 1 to np do 
begin
  x := I * (2 * pi / np); 
  GotoXY (n, WhereY); 
  writeln (x:5:3, F(x): 18:5) 
end 
END; {PrintFunc}
BEGIN {основная программа}
  ClrScr; {очистить экран}
  PrintFunc (1, sin1);
  GotoXY (1,1); {курсор в левый верхний угол}
  PrintFunc (40, cosl) 
END.
   Обратите внимание: для установления правильных связей функций S1N1 и COS1 с процедурой PRINTFUNC они должны компилироваться с. расчетом на дальнюю модель памяти. Вот почему в программу вставлены стандартные директивы FAR сразу за заголовками функций. В таком режиме должны компилироваться любые процедуры (функции), которые будут передаваться в качестве фактических параметров вызова.
Стандартные процедуры (функции) Турбо Паскаля не могут передаваться рассмотренным способом.
   В программе могут быть объявлены переменные процедурных типов, например, так:
var
  р1 : Proc;
  f1. f2 : Func2;
  ар : array [1..N] of Proc2;
   Переменным процедурных типов допускается присваивать в качестве значений имена соответствующих подпрограмм. После такого присваивания имя переменной становится синонимом имени подпрограммы, например:
type
  Proc = Procedure (n : word; var a : byte): var
  ProcVar : Proc; x, У : byte;
  Procedure Prod(x : word; var у : byte); far; 
begin
  if x > 255 then у := x mod 255
  else у := byte(x)
end; 
begin
  ProcVar := Proc1;
  for x := 150 to 180 do 
begin
  ProcVar (x + 100, y);
  write (у : 8) 
end 
end. 
   Разумеется, такого рода присваивания допустимы и для параметров функций, например:
Type
  FuncType = Function (i : integer) : integer; 
var 
  VarFunc : FuncType; 
  I : Integer;
Function MyFunc (count : Integer) : integer; far;
Begin
………………………….
end; {MyFunc}
begin {Основная программа}
…………………………
  I := MyFunc(1); {Обычное использование результата функции)
  VarFunc := MyFunc; {Присваивание переменной процедурного типа имени функции MyFunc}
Отметим, что присваивание
  VarFunc := MyFunc(1):
будет недопустимым, так как слева и справа от знака присваивания используются несовместимые типы: слева - процедурный тип, а справа - INTEGER; имя функции со списком фактических параметров МуFипс(1) трактуется Турбо Паскалем как обращение к значению функции, в время как имя функции без списка параметров рассматривается как имя функции.
  В отличие от стандартного Паскаля, в Турбо Паскале разрешается использовать в передаваемой процедуре (функции) как параметры-значения, так и параметры-переменные.

                      Вверх


                 8.6. НЕТИПИЗИРОВАННЫЕ                  ПАРАМЕТРЫ-ПЕРЕМЕННЫЕ
    Еще одно и очень полезное нововведение фирмы Borland - возможность использования нетипизированных параметров. Параметр считается нетипизированным, если тип формального параметра-переменной в заголовке подпрограммы не указан, при этом соответствующий ему фактический параметр может быть переменной любого типа. Заметим, нетипизированными могут быть только параметры-переменные.
     Нетипизированные параметры обычно используются в случае, когда тип данных несущественен. Такие ситуации чаще всего возникают разного рода копированиях одной области памяти в другую, например, с помощью процедур BLOCKREAD, BLOCKWRITE, MOVE и т.п.
   Нетипизированные параметры в сочетании с механизмом совмещения данных в памяти (см. п.4.4) очень удобно использовать для передаче подпрограмме одномерных массивов переменной длины. В примере 17 функция NORM А вычисляет норму вектора, длина которого меняет случайным образом. Стандартная константа MAXINT содержит максимальное значение целого типа INTEGER и равна 32767.
    Следует учесть, что при обращении к функции NORM А массив X помещается в стек и передается по ссылке, поэтому описание локальных переменной А в виде одномерного массива максимально возможной длины 65532 байта (встроенная константа MAXINT определяет максимальное возможное значение типа INTEGER и равна 32767),совпадающего с x , на самом деле не приведет к выделению дополнительного объема памяти под размещение этой переменной. Иными словами, переменная А - фиктивная переменная, размер которой никак не влияет на объем используемой памяти. С таким же успехом можно было бы объявить ее в виде массива из одного

        F :=Fac(n-1): 
        Fac := n * F 
        end; 
       END (Fac}; 
      {———————————— } 
     BEGIN {main}
   repeat 
   read ln(n); 
      wr i te ln( 'n! = ',Fac(n))
     untlI EOF
    END {main}.
  Рекурсивный вызов может быть косвенным. В этом случае подпрограмма обращается к себе опосредованно, путем вызова другой подпрограммы, в которой содержится обращение к первой, например:
                Procedure A (i : byte);
                   begin
                  ……………..
                  end;
                Procedure В (j : Byte);
                    Begin
                   ……….
                          A(j);
                          ……….
                        end;
    Если строго следовать правилу, согласно которому каждый идентификатор перед употреблением должен быть описан, то такую программную конструкцию использовать нельзя. Для того, чтобы такого рода вызовы стали возможны, вводится опережающее описание:
                   Procedure В (j : byte);
                             forward;
                   Procedure A (i : byte); 
                          begin
                               B(i);
                             End;
                     Procedure B;
                             Begin
                           …………….
                               A(j);
                                    End;

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

8.7. РАСШИРЕННЫЙ СИНТАКСИС ВЫЗОВА ФУНКЦИЙ


    В Турбо Паскале есть возможность вызывать функцию и не использовать то значение, которое она возвращает. Иными словами, вызов функции может внешне выглядеть как вызов процедуры, например:
                        {$Х+} {Включить расширенный синтаксис}
                   Function MyFunc(var x : integer) : Integer;
                           begin
                                tf x<0 then x:=0 else MyFunc := x+10 
                                 end; {MyFunc} 
                                    var
                                     I : Integer;
                           BEGIN {main}
                                 I := 1;
                             I := 2*MyFunc(l)-100: {Стандартный вызов функции}
                            MyFunc(l) {Расширенный синтаксис вызова}
                                   END. {main}

   Расширенный синтаксис делает использование функций таким же свободным, как, например, их использование в языке Си, и придает Турбо Паскалю дополнительную гибкость. Однако хороший стиль программирования не рекомендует применять глобальные переменные и параметры переменные при вызове функций, поэтому использование расширенное синтаксиса оправдано лишь в объектно-ориентированном программировании при инициации динамических объектов (см. гл. 10). Во всех остальных случаях следует придерживаться простого правила: если программисту не нужен результат, возвращаемый функцией, логичнее объявить 6 процедурой.
С помощью расширенного синтаксиса нельзя вызывать стандартные функции. Компиляция с учетом расширенного синтаксиса включаете! активным состоянием опции EXTENDED SYNTAX диалогового окна OPTIONS/COMPILER (см. прил.1) или глобальной директивой компилятора {$Х+}.

                 Вверх                                оглавление

Хостинг от uCoz