Алгоритмические языки высокого уровня. Курс лекций
.pdfBegin
For i:=4 to 25 do For j:=1 to 80 do Begin
Gotoxy(j,i); write(“”); End;
Gotoxy(1,4); While p1<>nil do Begin
Write(“ Val - ”, p1^.val,”;”); P1:=p1^.Next;
End;
End;
Procedure Add_Element(var p_uk:uk); Begin
New(p_pr); P_pr^.next:=nil; K:=k+1; P_pr^.val:=k;
If p_uk=nil then Begin P_beg:=p_pr; P_end:=p_pr; End
Else Begin
P_uk^.next:=p_pr; P_uk:=p_pr;
End;
End;
Procedure Del_Element(var p_uk:uk); Begin
If p_uk<>nil then Begin P_pr:=p_uk;
P_uk:=p_uk^.next; Dispose(p_pr); End
else begin
gotoxy(1,4); write(“spisok - pust” ); p_end:=nil;
end;
end;
begin {основная программа} clrscr;
p_beg:=nil; p_end:=nil; new(p_pr);
p_pr^.Next:=nil;
121
p_pr^.val:=1; p_beg:=p_pr; p_end:=p_pr;
k:=3; fin:=false; while not Fin do
begin gotoxy(1,1);
write(“1-Vivod spisok,”,“2–add element,”,“3- delete”,“4-exit”);
ch:=readkey; write(ch); case ch of
“1”: Vivod_Spisok(p_beg); “2”: Add_Element(p_end); “3”: Del_Element(p_beg); “4”: fin:=true;
end;
end;
end.
122
5.ПРОЦЕДУРЫ И ФУНКЦИИ
ВPascal предусмотрены средства, позволяющие оформлять вспо- могательный фрагмент как подпрограмму. Это бывает необходимо, когда какие-либо действия неоднократно повторяются в программе или имеется возможность использовать некоторые фрагменты уже разработанных ранее алгоритмов. Кроме того, подпрограммы приме- няются для разбиения крупных программ на отдельные смысловые части в соответствии с модульным принципом в программировании (в языке имеется два вида подпрограмм – процедуры и функции).
Для использования фрагмента в качестве подпрограммы ему не- обходимо присвоить имя и описать алгоритм по правилам языка Pascal. В дальнейшем, при необходимости вызвать его в программе, делают вызов подпрограммы упоминанием в нужном месте имени соответствующего подалгоритма со списком входных и выходных данных. Такое упоминание приводит к выполнению входящих в подпрограмму операторов, работающих с указанными данными. По- сле выполнения подпрограммы работа продолжается с той команды, которая непосредственно следует за вызовом подпрограммы. Проце- дуры и функции помещаются в раздел описаний программы. Для об- мена информацией между процедурами и функциями и другими бло- ками программы существует механизм входных и выходных пара- метров. Входными параметрами называют величины, передающиеся из вызывающего блока в подпрограмму (исходные данные для под- программы), а выходными – передающиеся из подрограммы в вызы- вающий блок (результаты работы подпрограммы). Подпрограмма может вызываться неоднократно, выполняя одни и те же действия с разными наборами входных данных. Параметры, использующиеся при записи текста подпрограммы в разделе описаний, называют фор- мальными, а те, что используются при ее вызове, – фактическими.
5.1.Описание и вызов процедур и функций
Структура описания процедур и функций до некоторой степени похожа на структуру Pascal-программы: у них также имеются заго- ловок, раздел описаний и исполняемая часть. Раздел описаний со- держит те же подразделы, что и раздел описаний программы: описа- ния констант, типов, меток, процедур, функций, переменных. Испол- няемая часть содержит собственно операторы процедур.
123
Формат описания процедуры:
procedure имя процедуры (формальные параметры); {раздел описаний процедуры}
begin
{исполняемая часть процедуры} end;
Формат описания функции:
function имя функции (формальные параметры):тип результата;
{раздел описаний функции} begin
{исполняемая часть функции} end;
Формальные параметры в заголовке процедур и функций записы- ваются в виде:
var имя праметра: имя типа
и отделяются друг от друга точкой с запятой. Ключевое слово var может отсутствовать (об этом далее). Если параметры однотипны, то их имена можно перечислять через запятую, указывая общее для них имя типа. При описании параметров можно использовать только стандартные имена типов, либо имена типов, определенные с помо- щью команды type. Список формальных параметров может отсут- ствовать.
Вызов процедуры производится оператором, имеющим следую- щий формат:
имя процедуры(список фактических параметров);
При вызове фактические параметры как бы подставляются вместо формальных, стоящих на тех же местах в заголовке. Таким образом происходит передача входных параметров, затем выполняются опе- раторы исполняемой части процедуры, после чего происходит воз- врат в вызывающий блок. Передача выходных параметров происхо- дит непосредственно во время работы исполняемой части. Вызов функции в Pascal может производиться аналогичным способом, кро- ме того имеется возможность осуществить вызов внутри какого-либо выражения. В частности имя функции может стоять в правой части оператора присваивания, в разделе условий оператора if и т.д.
Для передачи в вызывающий блок выходного значения функции в исполняемой части функции перед возвратом в вызывающий блок необходимо поместить следующую команду:
имя функции := результат;
124
При вызове процедур и функций необходимо соблюдать следую- щие правила:
•количество фактических параметров должно совпадать с коли- чеством формальных;
•соответствующие фактические и формальные параметры долж- ны совпадать по порядку следования и по типу.
Замечание: имена формальных и фактических параметров могут совпадать. Это не приводит к проблемам, так как соответствую- щие им переменные все равно будут различны из-за того, что хра- нятся в разных областях памяти. Кроме того, все формальные па- раметры являются временными переменными – они создаются в момент вызова подпрограммы и уничтожаются в момент выхода из нее.
Пример использования процедуры
(программа поиска максимума из двух целых чисел)
var x,y,m,n: integer;
procedure MaxNumber(a,b: integer; var max: integer); begin
if a>b then max:=a else max:=b; end;
begin
write('Введите x,y '); readln(x,y); MaxNumber(x,y,m); MaxNumber(2,x+y,n); writeln('m=',m,'n=',n);
end.
Пример использования функции
(программа поиска максимума из двух целых чисел)
var x,y,m,n: integer;
function MaxNumber(a,b: integer): integer; var max: integer;
begin
if a>b then max:=a else max:=b; MaxNumber := max;
end; begin
write('Введите x,y '); readln(x,y);
m := MaxNumber(x,y);
n := MaxNumber(2,x+y); writeln('m=',m,'n=',n);
end.
Program test1;
125
Const i1=1; I2=2;
Procedure D (var i1:byte, I2:byte); Begin
I1:=i1*2;
I2:=i2*2; Write (i1, i2);
End;
Begin D(i1, i2);
Write (i1, 2); End.
Результатом работы программы будет:
12
22
Рекурсией называется процедура или функция, которая содержит обращение к самой себе. Такая рекурсия называется прямой. При косвенной рекурсии две или более подпрограммы (процедуры, функ- ции) вызывают друг друга. Рекурсивная подпрограмма должна со- держать хотя бы одну ветвь, заканчивающуюся возвратом в вызы- вающую программу.
Пример рекурсии: вычисление факториала.
Чтобы получить значение факториала числа n, требуется ум- ножить на n факториал числа (n-1). Известно, что 0!=1 и 1!=1.
Function fact(n:byte):longint; Begin
If (n=0) or (n=1) then fact:=1
else fact:=n*fact(n-1);
End;
Достоинством рекурсии является компактная запись, недостатка- ми – опасность переполнения стека, а также расход времени и памя- ти на повторные вызовы функции.
Передача параметров. В стандарте языка Pascal передача парамет- ров может производиться двумя способами – по значению и по ссылке. Параметры, передаваемые по значению, называют параметрами– значениями, передаваемые по ссылке – параметрами–переменными.
Последние отличаются тем, что в заголовке процедуры (функции) перед ними ставится служебное слово var.
126
При первом способе (передача по значению) значения фактиче- ских параметров копируются в соответствующие формальные пара- метры. При изменении этих значений в ходе выполнения процедуры (функции) исходные данные (фактические параметры) измениться не могут. Поэтому таким способом передают данные только из вызы- вающего блока в подпрограмму (т.е. входные параметры). При этом в качестве фактических параметров можно использовать и констан- ты, и переменные, и выражения.
При втором способе (передача по ссылке) все изменения, проис- ходящие в теле процедуры (функции) с формальными параметрами, приводят к немедленным аналогичным изменениям соответствую- щих им фактических параметров. Изменения происходят с перемен- ными вызывающего блока, поэтому по ссылке передаются выходные параметры. При вызове соответствующие им фактические параметры могут быть только переменными.
Выбор способа передачи параметров при создании процедуры (функции) происходит в соответствии со сказанным выше: входные параметры нужно передавать по значению, а выходные – по ссылке. Практически это сводится к расстановке в заголовке процедуры (функции) описателя var при всех параметрах, которые обозначают результат работы подпрограммы. Однако в связи с тем, что функция возвращает только один результат, в ее заголовке использовать па- раметры–переменные не рекомендуется.
Локальные и глобальные идентификаторы. Использование процедур и функций в Pascal тесно связано с некоторыми особенно- стями работы с идентификаторами (именами) в программе. В частно- сти, не все имена всегда доступны для использования. Доступ к идентификатору в конкретный момент времени определяется тем, в каком блоке он описан.
Имена, описанные в заголовке или разделе описаний процедуры или функции, называют локальными для этого блока. Имена, опи- санные в блоке, соответствующем всей программе, называют гло- бальными. Следует помнить, что формальные параметры процедур и функций всегда являются локальными переменными для соответ- ствующих блоков.
Основные правила работы с глобальными и локальными именами можно сформулировать так:
• локальные имена доступны (считаются известными, «видимы- ми») только внутри того блока, где они описаны. Сам этот блок и все
127
другие, вложенные в него, называют областью видимости для этих локальных имен;
•имена, описанные в одном блоке, могут совпадать с именами из других, как содержащих данный блок, так и вложенных в него. Это объясняется тем, что переменные, описанные в разных блоках (даже если они имеют одинаковые имена), хранятся в разных областях опе- ративной памяти;
•глобальные имена хранятся в области памяти, называемой сег- ментом данных (статическим сегментом) программы. Они создают- ся на этапе компиляции и действительны на все время работы про- граммы. В отличие от них, локальные переменные хранятся в специ- альной области памяти, которая называется стек. Они являются вре- менными, так как создаются в момент входа в подпрограмму и унич- тожаются при выходе из нее.
Имя, описанное в блоке, «закрывает» совпадающие с ним имена из блоков, содержащих данные. Это означает, что если в двух бло- ках, один из которых содержится внутри другого, есть переменные с одинаковыми именами, то после входа во вложенный блок работа будет идти с локальной для данного блока переменной. Переменная с тем же именем, описанная в объемлющем блоке, становится времен- но недоступной, и это продолжается до момента выхода из вложен- ного блока.
Рекомендуется все имена, которые имеют в подпрограммах чисто внутреннее, вспомогательное назначение, делать локальными. Это предохраняет от изменений глобальные объекты с такими же имена- ми.
5.2. Параметры–массивы и параметры–строки
Может сложиться впечатление, что объявление переменных в списке формальных параметров подпрограммы ничем не отличается от объявления их в разделе описания переменных. Действительно, в обоих случаях много общего, но есть одно существенное различие: типом любого параметра в списке формальных параметров может быть только стандартный или ранее объявленный тип. Поэтому нель- зя, например, объявить следующую процедуру:
Procedure S (a: array [1..10] of Real);
так как в списке формальных параметров фактически объявляется тип-диапазон, указывающий границы индексов массива. Если мы хотим передать какой-то элемент массива, то проблем, как правило,
128
не возникает, но если в подпрограмму передается весь массив, то следует первоначально описать его тип.
type
atype = array [1..10]of Real; Procedure S (a: atype);
Поскольку строка является фактически своеобразным массивом, ее передача в подпрограмму осуществляется аналогичным образом:
type
intype = String [15] ; outype = String [30] ;
Function St (s : intype): outype;
Требование описать любой тип-массив или тип-строку перед объ- явлением подпрограммы на первый взгляд кажется несущественным. Действительно, в рамках простейших вычислительных задач обычно заранее известна структура всех используемых в программе данных, поэтому статическое описание массивов не вызывает проблем. Одна- ко разработка программных средств универсального назначения свя- зана со значительными трудностями. По существу, речь идет о том, что в Pascal невозможно использовать в подпрограммах массивы с «плавающими» границами изменения индексов. Например, если раз- работана программа, обрабатывающая матрицу 10×10 элементов, то для обработки матрицы 9×11 элементов необходимо переопределить тип, т.е. перекомпилировать всю программу (речь идет не о динами- ческом размещении массивов в куче, а о статическом описании мас- сивов и передаче их как параметров в подпрограммы). Этот недоста- ток разработчики Turbo Pascal не рискнули кардинально изменить, но, тем не менее, включили некоторые средства, позволяющие в из- вестной степени его смягчить. Прежде всего, в среде Turbo Pascal можно установить режим компиляции, при котором отключается контроль за совпадением длины фактического и формального пара- метра-строки. Это позволяет легко решить вопрос о передаче под- программе строки произвольной длины. При передаче строки мень- шего размера формальный параметр будет иметь ту же длину, что и параметр обращения; передача строки большего размера приведет к ее усечению до максимального размера формального параметра. Следует сказать, что контроль включается только при передаче стро- ки, объявленной как формальный параметр-переменная. Если соот- ветствующий параметр объявлен параметром-значением, эта опция игнорируется и длина не контролируется. Значительно сложнее об- стоит дело с передачей массивов произвольной длины. Наиболее
129
универсальным приемом в этом случае будет работа с указателями и использование индексной арифметики. В версии Turbo Pascal 7.0 язык поддерживает так называемые открытые массивы, легко ре- шающие проблему передачи подпрограмме одномерных массивов переменной длины. Открытый массив представляет собой фор- мальный параметр подпрограммы, описывающий базовый тип эле- ментов массива, но не определяющий его размерности и границы:
Procedure MyProc(OpenArray: array of Integer);
Внутри подпрограммы такой параметр трактуется как одномер- ный массив с нулевой нижней границей. Верхняя граница открытого массива возвращается функцией HIGH. Используя 0 как минималь- ный индекс и значение, возвращаемое функцией HIGH как макси- мальный индекс, подпрограмма может обрабатывать одномерные массивы произвольной длины.
Пример программы
Описание: использование открытых массивов. Программа выво- дит на экран содержимое двух одномерных массивов разной длины с помощью одной процедуры ArrayPrint.
Procedure ArrayPrint(aArray: array of Integer); var k: Integer;
begin
for k := 0 to High(aArray) do Write(aArray[k]:8); WriteLn
end; const
A:array [-1..2] of Integer = (0,1,2,3); B: array [5..7] of Integer = (4,5,6);
begin
ArrayPrint(A);
ArrayPrint(B)
end.
Как видно из этого примера, фактические границы массивов А и В, передаваемых в качестве параметров вызова процедуре ArrayPrint, не имеют значения. Однако размерность открытых массивов (количество индексов) всегда равна 1 – за этим следит ком- пилятор. Если бы, например, мы добавили в программу двухмерный массив С:
var С: array [1..3,1..5] of Integer; то обращение
ArrayPrint(С) вызывало бы сообщение об ошибке Error26: Type mismatch (Ошибка 26: Несоответствие типов).
130
