1. Процедуры и функции. Синтаксис, примеры использования.
Очень часто при разработке программ встречается ситуация, когда одни и те же действия выполняются несколько раз над различными данными. Встречается и повторение какого-либо участка кода. В данном случае было бы полезно выделить его в отдельную единицу и использовать по мере необходимости. В реальной жизни приходится разрабатывать сложные программы, решать большие задачи. Мы можем разбить исходную задачу на подзадачи, программируя и отлаживая каждую по-отдельности. Такой подход позволяет также изменять, модифицировать каждую часть независимо от других. В языке Pascal данный механизм реализуется при помощи подпрограмм.
Подпрограмма - отдельная, функционально независимая часть программы. Программа, готовая к самостоятельному исполнению, состоит из главной (с нее начинается работа) программы и некоторого количества подчиненных ей подпрограмм. Вызов подпрограммы происходит следующим образом (рис. 1):
- Приостановка работы основной подпрограммы.
- Копирование параметров (создание локальных для подпрограммы переменных).
- Передача управления на первый операнд вызванной подпрограммы.
- Выполнение подпрограммы.
- Удаление локальных для подпрограммы переменных.
- Возвращение управления основной подпрограмме.
Основная программа может вызвать сколько угодно подпрограмм. Подпрограммы можно разделить на стандартные (системные) и нестандартные (собственные, пользовательские). К стандартным подпрограммам относятся подпрограммы расчета sin, cos, ln, min и т.д. К нестандартным подпрограммам относятся подпрограммы, реализованные пользователем. Существует два вида подпрограмм - процедуры и функции. Различаются они синтаксическим оформлением, способом вызова и передачей возвращаемого значения.
Рис. 1. Вызов подпрограммы
|
Подпрограмма
--- --- --- --- --- --- --- --- --- --- --- ---
|
Головная программа
begin --- --- --- Вызов подпрограммы --- --- --- --- --- --- end. |
Функции используются в тех случаях, когда в основную подпрограмму следует вернуть некоторый результат. Часто их используют как операнды выражений. Процедуры используются как отдельные операторы и не возвращают значений.
Синтаксическое оформление процедур и функций выглядит следующим образом:
function <имя функции>(<список формальных параметров>): <тип возвращаемогорезультата>;
procedure <имя процедуры>(<список формальных параметров>);
Здесь function и procedure – зарезервированные слова. Имена функций и процедур являются их идентификаторами. Список параметров – набор переменных, задаваемых при описании подпрограммы и используемых в ее теле в качестве средства получения исходных данных (входные параметры) и передачи результатов работы (выходные параметры). При написании подпрограммы параметрам задаются некоторые имена. Эти параметры называются формальными, т.к. они лишь обозначают переменные. При вызове подпрограммы в списке параметров указываются имена переменных, в которых должны находиться входные данные или в которые надо поместить выходные результаты, или которые надо изменить в ходе выполнения подпрограммы. Эти параметры носят фактические.
Синтаксически каждый параметр оформляется следующим образом
<идентификатор>:<тип>
через точку с запятой[1]. В том случае, елси необходимо объявить несколько параметров одного типа, их можно перечислить через запятую слудеющим образом:
<идентификатор1>,<идентификатор2>,<идентификатор3>:<тип>
Фрагмент программы от заголовка процедуры до завершающей операторной скобки end принято называть блоком. Подпрограммы в языке Pascal описываются в разделе объявлений блока. В языке Pascal существует возможность использовать вложенные блоки, с каждым из которых связывают уровень вложения. К примеру, в блок A могут быть вложены два блока первого уровня – B и C.
procedure A();
…
procedure B();
…
begin
…
end;
procedure C();
…
begin
…
end;
begin
…
end;
Если же блок С вложен в блок В, то он имеет уровень вложения два по отношению к блоку А.
procedure A();
…
procedure B();
…
procedure C();
…
begin
…
end;
begin
…
end;
begin
…
end;
При этом нельзя напрямую обратиться к блоку, имеющий уровень вложения выше первого. В нашем случае, из А нельзя обратиться к С.
Структура подпрограммы повторяет структуру главной программы. В ней присутствуют следующие разделы:
- Label – описание меток.
- Const – описание констант.
- Type – описание типов данных.
- Var – описание переменных.
- Описание процедур и функций.
Тело подпрограммы начинается с операторной скобки begin, а завершается с операторной скобки end с последующей точкой с запятой. При необходимости досрочного выходы из тела подпрограммы используется оператор exit. Напомним, что функция отличается от процедуры тем, что возвращает некоторое значение определенного типа. Вернуть значение можно следующим образом:
<имя функции>:=<возвращаемое значение>;
Или можно использовать зарезервированное слово result
result:=<возвращаемое значение>;
2. Глобальные и локальные переменные
Представьте, что Вы разрабатываете программу, которая использует несколько подпрограмм. Как Вы будете решать вопрос с использованием одного идентификатора в разных подпрограммах? А что Вы будете делать, если переменная из одной подпрограммы понадобится в другой?
Существует система правил, определяющая области видимости (действия, доступности) имен:
- Переменная, объявленная в некотором блоке, является локальной для этого блока. Она “не видна” во всех других блоках, за исключением тех, которые описаны как внутренние подпрограммы данного блока, в которых она является глобальной, то есть доступной для использования в том смысле, как она описана.
- Если имя глобальной переменной используется для описания другой переменной во внутренней подпрограмме, то имя вновь локализуется и бывшая глобальная переменная становится недоступна по “переобъявленному” имени внутри этой внутренней подпрограммы.
Переменная существует (то есть размещена в оперативной памяти) только в то время, когда является активным блок, где она объявлена. Блок считается активным после начала его выполнения и до завершения его работы. Другими словами, память под переменные отводится только после входа в подпрограмму, где они описаны, и отбирается у них после завершения работы подпрограммы. Переменные, объявленные в главной программе, существуют все время работы программы([1],[2]).
Как видите, не стоит надеяться, что после выхода из подпрограммы при последующем ее вызове какая-либо переменная сохранит свое прежнее значение. Для того, чтобы его сохранить, можно использовать глобальную переменную. Однако при таком подходе существует вероятность ошибки, скажем, при переносе подпрограммы в другую программу.
Ниже приведен пример использования переменных. Функция ReferenceCount использует глобальную переменную, а фукнция AddOne - локальную.
program AddCount;
var
i,j, res : integer;
procedure ReferenceCount();
begin
i := i + 1;
end;
function AddOne(i:integer):integer;
begin
i := i + 1;
end;
begin
i := 0;
for j:=1 to 10 do
begin
ReferenceCount();
res := AddOne(i);
end;
end.
В общем случае можно сформулировать рекомендацию, состоящую в следующем: используйте обмен данными через глобальные переменные только тогда, когда без них нельзя обойтись.
Существует еще один механизм обмена данными – через параметры, - который более подробно будет рассмотрен в следующей главе.
3. Передача параметров по значению и по ссылке
Вообще говоря, локальные переменные хранятся в специальной области памяти, называемой стек. Передача параметров функциям также осуществляется через стек. Однако передать можно как само значение, так и адрес в оперативной памяти. Отсюда и идет разделение на передачу параметров по значению и по ссылке(адресу). При этом следует помнить, что типы формального и фактического параметров обязательно должны совпадать.
3.1. Передача параметра по значению
При передаче параметров по значению соответствующие значения фактических параметров копируются в область памяти формальных параметров, при этом фактические параметры изменить внутри подпрограммы нельзя, т.к. фактически подпрограмма работает с копией переданных данных. В качестве фактических параметров в данном случае мы можем использовать и переменные, и константы, и выражения. Такие параметры называются параметры-значения.
Передача параметра по значению выглядит следующим образом.
program Max;
var
a, b, max : integer;
function GetMax(x,y : integer) : integer;
begin
if x>y then result := x;
else result := y;
end;
begin
write('Input first value ');
readln(a);
write('Input second value ');
readln(b);
max = GetMax(a,b);
writeln(‘More value ',max);
end.
3.2. Передача параметра по ссылке
При передаче параметра по ссылке любое изменение параметра внутри подпрограммы ведет за собой соответствующее изменение фактического параметра. Их еще называют параметрами-переменными. В качестве фактических параметров нельзя использовать константы или выражения. Т.о. по ссылке передаются параметры, значения которых должна модифицировать подпрограмма. Кроме этого, по ссылке передаются массивы, т.к. в этом случае нет необходимости создавать копии в стеке каждого элемента массива по-отдельности, что ускоряет работу программы.
Синтаксически передача параметра по ссылке отличается от передачи параметра по значению лишь наличием ключевого слова var перед описанием формального параметра. Передача параметра по ссылке выглядит следующим образом:
program Max;
var
a, b, max : integer;
procedure GetMax(x,y : integer; var max: integer);
begin
if x>y then max := x;
else max := y;
end;
begin
write('Input first value ');
readln(a);
write('Input second value ');
readln(b);
GetMax(a,b, max);
writeln(‘More value ',max);
end.
В некоторых случаях бывает необходимо передать параметр по ссылке, но при этом запретить изменение его фактического параметра. В этом случае перед описанием формального параметра ставится ключевое слово const. Чаще всего это происходит тогда, когда копирование данных в локальные будет затратно по времени, но при этом сами данные нельзя портить.
program Array;
type
myArray = array[1..100000] of integer;
var
max, min, sum : integer;
nums:myArray;
procedure GetMaxMin(const arr: myArray; var max, min: integer);
var
i : integer;
begin
max := arr[1];
min := arr[1];
for i:=2 to 100000 do
begin
if max>arr[i] then max := arr[i];
if min<arr[i] then min := arr[i];
end;
end;
function GetSum(const arr: myArray): integer;
var
sum : integer;
begin
sum := 0;
for i:=1 to 100000 do
sum:=sum + arr[i];
result:=sum;
end;
procedure Sort(сonst arr: myArray);
var
i, j: integer;
temp: integer;
begin
for i := 1 to 99999 do
for j := 99999 downto i do
if arr[j] < arr[j - 1] then
begin
temp := a[j];
arr[j] := arr[j - 1];
arr[j - 1] := temp;
end;
end;
begin
…
sum:=GetSum(nums);
GetMaxMin(nums, max, min);
Sort(nums);
…
end.
3.3. Параметры по умолчанию
Программист имеет возможность указать, какое значение будет принимать параметр в том случае, если фактический параметр не был указан при вызове подпрограммы. Для этих целей используются параметры по умолчанию. Синтаксически это выглядит следующим образом
procedure GetMax(var max:integer;x:integer=0;y:integer=0);
Т.е. после описания формального параметра необходимо поставить «=» и указать необходимое значение. При этом мы можем указать несколько параметров по умолчанию. При этом следует помнить об одном простом правиле: все параметры по умолчанию указываются в конце описания формальных параметров.
3.4. Открытые массивы
Выше упоминалось, что тип формального и фактического параметров должны совпадать. А потому нельзя создать процедуру (или функцию) такого вида
procedure GetMaxMin(const arr: array[1..100000]of integer; var max, min: integer);
procedure Sort(const arr: array[1..100000]of integer);
Одним из существующих решений является решение, описанное в разделе «Передача параметров по ссылке». Нам необходимо вводит собственный тип и использовать его. Это снижает универсальность использования данной функции в дальнейшем. Для решения этой задачи вводятся так называемые открытые массивы (Object Pascal).
Синтаксически объявление открытого массива выглядит следующим образом:
<идентификатор>:array of <тип элементов>;
Рассматриваемую процедуру мы можем объявить следующим образом:
procedure GetMaxMin(var arr: array of integer; var max, min: integer);
Что касается нумерации, то она начинается с 0 (только для открытых массивов, не путать с обычными), а номер последнего элемента можно получить при помощи функции:
high(<идентификатор открытого массива>);
Какие данные мы можем передавть в описанную функцию? Во-первых, массив произвольной длины, элементы которого имеют тип integer. Во-вторых, мы можеме передать просто переменную типа integer, она будет интерпретироваться как массив длины 1. Ниже приведен пример использования открытых массивов.
procedure Sort(var arr: array of integer);
var
i, j: integer;
temp: integer;
begin
for i := 1 to high(arr) do
for j := high(arr) downto i do
if arr[j] < arr[j - 1] then
begin
temp := a[j];
arr[j] := arr[j - 1];
arr[j - 1] := temp;
end
end;
4. Рекурсия.
Рекурсивной подпрограммой называется подпрограмма, которая вызывает саму себя. Однако в этом случае важно определить условие выхода из подпрограммы, т.к. в противном случае рекурсивный вызов может продолжаться бесконечно. Классическим примером является вычисление факториала.
n!=n*(n-1)*(n-2)*…*2*1 = n*(n - 1)!
При такой записи n! зависит от (n-1)!, который в свою очередь зависит от (n-2)! и т.д. При этом 1! = 1, что можно использовать как условие выхода.
function Factorial(i: integer): integer;
begin
if i = 0 then
begin
result := 1;
exit;
end
else
result := i * Factorial (i - 1);
end;
Выше мы описали пример прямой рекурсии. Существует более сложный вид рекурсии, называемой косвенной. В этом случае подпрограммы не пытаются вызвать непосредственно сами себя. Пусть у нас есть процедура A, которая вызывает процедуру B, а процедура B, в свою очередь, вызывает процедуру A. Т.к. в языке Pascal существует правило, согласно которому сначала необходимо объявить объект, а уже потом его использовать, непонятно, какую из процедур – А или В – описывать первой. Предположим, что процедура В описана первой. Но она вызывает процедуру А. Тогда перед В необходимо вставить опережающее объявление, которое выглядит следующим образом:
procedure A();forward;
procedure B();
begin
…
A();
…
end;
procedure A();
begin
…
B();
…
end;
В общем случае опережающее объявление выглядит следующим образом:
procedure <имя процедуры>(<cписок параметров>);forward;
function <имя функции>(<cписок параметров>):<возвращаемое значение>; forward;
В чем же преимущества рекурсии? Программы легко читаются, используемые алгоритмы становятся понятными, изящно реализованные. Однако каждый вызов рекурсивной подпрограммы сопровождается сохранением всех локальных переменных и выделением памяти под локальные переменные для нового вызова. Все это занимает достаточно много и памяти, и времени. Т.к. любой рекурсивный алгоритм можно реализовать, не прибегая к рекурсии, существует рекомендация реализовывать ее именно таким способом. Однако во многих случаях это сопряжено с большими усилиями.
Вычисление факториала можно записать в следующем виде:
function Factorial(i: integer): integer;
var
j, fact:integer;
begin
if i = 0 then
begin
result := 1;
exit;
end
else
fact:= 1;
for j:=1 to i do
fact:= fact * j;
result:=fact;
end;
5. Литература
- А.О. Грудзинский, И.Б. Мееров, А.В. Сысоев. Методы программирования. Курс на основе языка Object Pascal. Нижний Новгород, 2005. [http://www.software.unn.ru/file.php?id=415]
- Александр Кетков, Юлий Кетков. Практика программирования: Бейсик,Си, Паскаль. Самоучитель.-СПб.:БХВ-Петербург, 2002. – 420с.
- Процедуры и функции.[http://citforum.ru/programming/bp70_lr/lr9.shtml]
- Pascaler. Обучение Turbo Pascal. [http://www.pascaler.ru/pascal/underprog/procedure/1/index.html]