Программирование на языке Pascal

         

Аргументы


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

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

function min(a,b: integer): integer; far; begin if a>b then min:= b else min:= a end;

и

function max(a,b: integer): integer; far; begin if a<b then max:= b else max:= a end;



Явное преобразование типа


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

procedure proc5(var x); type arr = array[1..10] of byte; var x: integer; z: real; m: arr; begin ... y:= integer(x); z:= real(x); m:= arr(x); ... end;



Механизм передачи значения


В области памяти, выделяемой для работы вызываемой подпрограммы, создается переменная с именем <имя_подпрограммы>.<имя_параметра>, и в эту переменную записывается значение переданного в соответствующий параметр аргумента. Дальнейшие действия, производимые подпрограммой, выполняются именно над этой новой переменной. Значение же входного аргумента не затрагивается. Следовательно, после окончания работы подпрограммы, когда весь ее временный контекст будет уничтожен, значение аргумента останется точно таким же, каким оно было на момент вызова подпрограммы.

В качестве примера рассмотрим последовательность действий, выполняемых при передаче аргументов 1+а/2, а и true в описанную выше функцию func3. Пусть а - переменная, имеющая тип byte, тогда значение выражения 1+a/2 будет иметь тип real, а true и вовсе является константой (неименованной).

Итак, при вызове func3(1+a/2,a,true) будут выполнены следующие действия:

создать временные переменные func3.x, func3.k, func3.flag;вычислить значение выражения 1+а/2 и записать его в переменную func3.x;записать в переменную func3.k значение переменной а;записать в переменную func3.flag значение константы true;произвести действия, описанные в теле функции;уничтожить все временные переменные, в том числе func3.x, func3.k, func3.flag.

Уже видно, что значения аргументов не изменятся.

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


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

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

Замечание: Для экономии памяти в параметр-переменную можно передавать и такую переменную, изменять значение которой не требуется. Скажем, если нужно передать в качестве аргумента массив, то лучше не создавать его копию, как это будет сделано при использовании параметра-значения, а использовать параметр-переменную.



Нетипизированные параметры


В объявлении подпрограммы можно не указывать тип параметра-переменной:



procedure proc5(var x);

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

Для того чтобы внутри самой подпрограммы корректно обрабатывать значения, поступившие через нетипизированный параметр, существует два различных способа.



Объявление функции


Функции объявляются следующим образом:

function <имя_функции> [(<список_параметров>)]:<тип_результата>;

В отличие от констант и переменных, объявление подпрограммы может быть оторвано от ее описания. В этом случае после объявления нужно указать ключевое слово forward:

function <имя_функции> [(<параметры>)]:<тип_результата>; forward;



Объявление и описание


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

Различия между процедурами и функциями начинаются уже с момента их объявления.



Объявление процедуры


Процедуры следует объявлять так:

procedure <имя_процедуры> [(<список_параметров>)];

Если объявление процедуры оторвано от ее описания, нужно поставить после него ключевое слово forward:

procedure <имя_процедуры> [(<список_параметров>)]; forward;



В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-значением служебное слово отсутствует1). Например, функция func3 имеет три параметра-значения:
function func3(x:real; k:integer; flag:boolean):real;
При вызове подпрограммы параметру-значению может соответствовать аргумент, являющийся выражением, переменной или константой, например:
dlina:= func3(shirina/2, min(a shl 1,ord('y')), true)+0.5;
Для типов данных здесь не обязательно строгое совпадение (эквивалентность), достаточно и совместимости по присваиванию (см. лекцию 2).

В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-переменной ставится служебное слово var. Например, процедура proc3 имеет три параметра-переменные и один параметр-значение:
procedure proc3(var x,y:real; var k:integer; flag:boolean);
При вызове подпрограммы параметру-переменной может соответствовать только аргумент-переменная; константы и выражения запрещены. Кроме того, тип аргумента и тип параметра-переменной должны быть эквивалентными (см. лекцию 2).

В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-константой ставится служебное слово const. Например, процедура proc4 имеет один параметр-переменную и один параметр-константу:
procedure proc4(var k:integer; const flag:boolean);
При вызове подпрограммы параметру-константе может соответствовать аргумент, являющийся выражением, переменной или константой. Во время выполнения подпрограммы соответствующая переменная считается обычной константой. Ограничением является то, что при вызове другой подпрограммы из тела текущего параметра-константе не может быть подставлен в качестве аргумента в параметр-переменную.
Для типов данных здесь не обязательно строгое совпадение (эквивалентность), достаточно и совместимости по присваиванию (см. лекцию 2).

В разделе type процедурный тип


В разделе type процедурный тип данных задается одним из следующих способов:
<имя_типа> = function[(<список_параметров>)]:<тип_результата>;
или
<имя_типа> = procedure[(<список_параметров>)];
Например:
type func = function(a,b:integer):integer;

В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-значением служебное слово отсутствует3). Например, функция func3 имеет три параметра-значения:
function func3(x:real; k:integer; flag:boolean):real;
При вызове подпрограммы параметру-значению может соответствовать аргумент, являющийся выражением, переменной или константой, например:
dlina:= func3(shirina/2, min(a shl 1,ord('y')), true)+0.5;
Для типов данных здесь не обязательно строгое совпадение (эквивалентность), достаточно и совместимости по присваиванию (см. лекцию 2).

В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-переменной ставится служебное слово var. Например, процедура proc3 имеет три параметра-переменные и один параметр-значение:
procedure proc3(var x,y:real; var k:integer; flag:boolean);
При вызове подпрограммы параметру-переменной может соответствовать только аргумент-переменная; константы и выражения запрещены. Кроме того, тип аргумента и тип параметра-переменной должны быть эквивалентными (см. лекцию 2).

В списке параметров подпрограммы перед


В списке параметров подпрограммы перед параметром-константой ставится служебное слово const. Например, процедура proc4 имеет один параметр-переменную и один параметр-константу:
procedure proc4(var k:integer; const flag:boolean);
При вызове подпрограммы параметру-константе может соответствовать аргумент, являющийся выражением, переменной или константой. Во время выполнения подпрограммы соответствующая переменная считается обычной константой. Ограничением является то, что при вызове другой подпрограммы из тела текущего параметра-константе не может быть подставлен в качестве аргумента в параметр-переменную.
Для типов данных здесь не обязательно строгое совпадение (эквивалентность), достаточно и совместимости по присваиванию (см. лекцию 2).

В разделе type процедурный тип


В разделе type процедурный тип данных задается одним из следующих способов:
<имя_типа> = function[(<список_параметров>)]:<тип_результата>;
или
<имя_типа> = procedure[(<список_параметров>)];
Например:
type func = function(a,b:integer):integer;

Описание подпрограммы


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

[ uses <имена_подключаемых_модулей>;] [ label <список_меток>;] [ const <имя_константы> = <значение_константы>;] [ type <имя_типа> = <определение_типа>;] [ var <имя_переменной> : <тип_переменной>;]

[ procedure <имя_процедуры> <описание_процедуры>] [ function <имя_функции> <описание_функции>;]

begin {начало тела подпрограммы} <операторы> end; (* конец тела подпрограммы *)

Если объявление подпрограммы было оторвано от ее описания, то описание начинается дополнительной строкой с указанием только имени подпрограммы:

function <имя_подпрограммы>;

или

procedure <имя_подпрограммы>;

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

Пример подпрограммы-процедуры:

procedure err(c:byte; s:string); var zz: byte; begin if c = 0 then writeln(s) else writeln('Ошибка!') end;



Открытые массивы


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

В заголовке подпрограммы открытый параметр-массив описывается по следующему шаблону:

var <имя_параметра>: array of <тип_компонентов_массива>

Например, если описано

procedure proc6 (var a: array of byte);

то аргументом могут стать такие массивы:

a1: array[1..100] of byte; a2: array[-10..10] of byte; a3: array[1..2,1..3] of byte;

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

Если в качестве аргумента поступил многомерный массив, его компоненты "вытягиваются" в одну строку: сначала все компоненты первой строки массива, затем - второй строки и т.д. Например, если массив а3 имеет значения

1 2 3 4 5 6

то внутри процедуры proc6 параметр а будет иметь уже следующий вид:

1 2 3 4 5 6

причем компоненты двух массивов будут соотноситься так:

a[0]a[1]a[2]a[3]a[4]a[5]
a3[1,1]a3[1,2]a3[1,3]a3[2,1]a3[2,2]a3[2,3]



Открытые параметры


Открытые параметры - это массивы и строки неопределенной длины. Открытым параметром может стать только параметр-переменная. Возможность работать с открытыми параметрами в подпрограммах появилась в версии Turbo Pascal 7.0.



Открытые строки


Поскольку строки - это массивы символов, то они тоже могут стать открытыми параметрами. Описывается это следующим образом:

var <имя_параметра>: string

Например:

function func6 (var s: string): byte;

Длина такого параметра будет автоматически скорректирована в соответствии с длиной строки-аргумента.



Побочный эффект


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

Чтобы избежать побочного эффекта, необходимо строго следить за тем, чтобы подпрограммы изменяли только свои локальные переменные (в том числе и параметры-переменные).



Подпрограммы


Весьма поэтичное объяснение понятия подпрограмма дал В.Ф. Очков: "Подпрограмма - это припев песни, который поют несколько раз, а в текстах песен печатают только один раз"1).

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

С математической же точки зрения любая подструктура - это замкнутая часть целого, которую можно рассматривать как самостоятельную структуру: подмножество является множеством, подгруппа - группой, подалгебра - алгеброй, подпространство - пространством и т.д.

Таким образом, подпрограмма - это в первую очередь программа. Со всеми полагающимися полноценной программе атрибутами: именем, разделами описания меток (label), констант (const), типов (type), переменных (var) и даже со своими (вложенными) функциями и процедурами.

В языке Pascal имеется два вида подпрограмм: процедуры и функции. Описывая их общие черты, мы будем употреблять обобщенный термин "подпрограмма". Если же в тексте встретятся слова "процедура" или "функция", то это будет означать, что излагаемая информация свойственна только одному конкретному виду подпрограмм: либо только процедурам, либо только функциям.



Процедурный тип данных


Имена подпрограмм могут выступать в роли аргументов для других подпрограмм.



Разграничение контекстов


Глобальные объекты - это типы данных, константы и переменные, объявленные в начале программы до объявления любых подпрограмм. Эти объекты будут видны во всей программе, в том числе и во всех ее подпрограммах. Глобальные объекты существуют на протяжении всего времени работы программы.

Локальные объекты объявляются внутри какой-нибудь подпрограммы и "видны" только этой подпрограмме и тем подпрограммам, которые были объявлены как внутренние для нее. Локальные объекты не существуют, пока не вызвана подпрограмма, в которой они объявлены, а также после завершения ее работы.

Таблица 8.1. Пример разграничения контекстов

program prog; var a:byte;
procedure pr1 (p:byte); var b:byte; (первый уровень вложенности)
function f (pp:byte); var c:byte; (второй уровень вложенности) begin (здесь "видны" переменные a, b, c, p, pp) end;
begin (здесь "видны" переменные a, b, p) end;
var g:byte
procedure pr2; var d:byte; (первый уровень вложенности) begin (здесь видны переменные a, d, g) end;
begin (тело программы; здесь "видны" переменные a, g) end;



Совмещение в памяти


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

<локальная_переменная>: <тип> absolute <нетипизир_параметр>;

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

function func5(var x):real; var xxx: longint absolute x; begin {здесь с началом любой переменной, поступившей в параметр х, ... можно обращаться как с longint-числом: при помощи локальной переменной ххх} end;



Совпадение имен


Вообще говоря, совпадения глобальных и локальных имен допустимы, поскольку к каждому локальному имени неявно приписано имя той подпрограммы, в которой оно объявлено. Таким образом, в приведенном выше примере (см. пример 8.1) фигурируют переменные a, g, pr1.p, pr1.b, pr1.f.pp, pr1.f.c, pr2.d.

Если имеются глобальная и локальная переменные с одинаковым именем, то изнутри подпрограммы к глобальной переменной можно обратиться, приписав к ней спереди имя программы:

<имя_программы>.<имя_глобальной переменной>

Например (локальной переменной здесь присваивается значение глобальной):

a:= prog.a;

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



Список параметров


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

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

Список параметров может и вовсе отсутствовать:

procedure proc1; function func1: boolean;

В этом случае подпрограмма не получает никаких переменных "извне". Упомянутый в начале лекции песенный припев как раз и является примером подпрограммы, в которую не передается никаких данных при вызове.

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

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

[<способ_подстановки>]<имя_параметра>:<тип>;

О возможных способах подстановки значений в параметры (<пустой>, var, const) мы расскажем в разделе "Способы подстановки аргументов".

Если способ подстановки и тип нескольких параметров совпадают, описание этих параметров можно объединить:

[<способ_подстановки>]<имя1>,...,<имяN>: <тип>;

Пример описания всех трех способов подстановки:

function func2(a,b:byte; var x,y,z:real; const c:char);

В заголовке подпрограммы можно указывать только простые (не составные) типы данных. Следовательно, попытка записать

procedure proc2(a: array[1..100]of char);

вызовет ошибку уже на этапе компиляции. Для того чтобы обойти это ограничение, составной тип данных нужно описать в разделе type, а при объявлении подпрограммы воспользоваться именем этого типа:

type arr = array[1..100] of char; procedure proc2(a: arr); function func2(var x: string): arr;



Способы подстановки аргументов


Как уже упоминалось выше, при вызове подпрограммы подстановка значений аргументов в параметры производится в соответствии с правилами, указанными в атрибуте <способ_подстановки>. Мы рассмотрим три различных значения этого атрибута:

<пустой>; var; const1).



Возвращаемые значения


Основное различие между функциями и процедурами состоит в количестве возвращаемых ими значений.

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

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

Например:

function min(a,b: integer): integer; begin if a>b then min:= b else min:= a end;

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



Вызов


Приведем пример подпрограммы, имеющей параметр процедурного типа:

procedure count(i,j:integer; f:func); var c: integer; begin ... c:= f(i,j); ... end;

Теперь, если будет осуществлен вызов count(x,y,min), то в локальную переменную с запишется минимум из x и y. Если же вызвана будет count(x,y,max), то в локальную переменную с запишется максимум из x и y.



Вызов подпрограмм


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

При вызове в подпрограмму передаются фактические параметры или аргументы (в круглых скобках после имени подпрограммы, разделенные запятыми):

<имя_подпрограммы>(<список_аргументов>)

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

Количество и типы передаваемых в подпрограмму аргументов должны соответствовать количеству и типам ее параметров. Кроме того, тип каждого аргумента должен обязательно учитывать способ подстановки, указанный для соответствующего параметра (подробнее об этом будет рассказано в разделе "Способы подстановки аргументов"). Если у подпрограммы вообще нет объявленных параметров, то при вызове список передаваемых аргументов будет отсутствовать вместе с обрамляющими его скобками.

Вызов функции не может быть самостоятельным оператором, потому что возвращаемое значение нужно куда-то записывать. Зато оно может стать равноправным участником арифметического выражения. Например:

c:= min(a,a*2); if min(z, min(x,y))= 0 then...;

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

err(res,'Привет!');

Замечание: После того как вызванная подпрограмма завершит свою работу, управление передается оператору, следующему за оператором, вызвавшим эту подпрограмму.