Глава 13. Процедуры и функции с параметрами
Процедуры с параметрами
Поставим и решим задачу, данную вам в качестве задания в 8.2: Составьте программу с процедурами, которая исполнит мелодию “Чижик-пыжик” (ми-до-ми-до-фа-ми-ре-соль-соль-ля-си-до-до-до).
Воспользуемся нотами третьей октавы:
USES CRT;
PROCEDURE doo; BEGIN Sound(523); Delay(2000); NoSound END;
PROCEDURE re; BEGIN Sound(587); Delay(2000); NoSound END;
PROCEDURE mi; BEGIN Sound(659); Delay(2000); NoSound END;
PROCEDURE fa; BEGIN Sound(698); Delay(2000); NoSound END;
PROCEDURE sol; BEGIN Sound(784); Delay(2000); NoSound END;
PROCEDURE la; BEGIN Sound(880); Delay(2000); NoSound END;
PROCEDURE si; BEGIN Sound(988); Delay(2000); NoSound END;
BEGIN
mi; doo; mi; doo; fa; mi; re; sol; sol; la; si; doo; doo; doo
END.
Все процедуры в нашей программе всегда делают одно и то же. Например, процедура doo всегда издает звук частоты 523 герца и продолжительности 200 мс. Происходит это потому, что в описании процедур мы использовали не переменные величины, а константы.
В реальной музыке ноты принадлежат разным октавам и имеют разную длительность. Чтобы получить ноту четвертой октавы, достаточно умножить частоту одноименной ноты третьей октавы на 2. Например, нота ре четвертой октавы имеет частоту 587*2=1174 гц. Естественно, все ноты четвертой октавы звучат выше нот третьей октавы. Пятая октава получается из четвертой так же, как четвертая из третьей и звучит еще выше. Ноты второй октавы, наоборот, получаются из нот третьей октавы делением частоты на 2.
Поставим задачу - создать более универсальную процедуру. Чтобы заставить ноту звучать по-разному, используем переменные величины. Здесь мы используем ту же самую идею, которую мы использовали в процедуре House из Глава 10, она рисовала дом в разных местах экрана в зависимости от значений переменных величин, задающих его координаты. Для простоты ограничимся пока одной нотой ре и двумя октавами - третьей и четвертой. Длительность пусть будет любая. Пусть программа должна воспроизвести три подряд ноты ре: сначала третья октава одна секунда, затем четвертая октава одна секунда и затем третья октава три секунды.
USES CRT;
VAR octava : Byte;
dlit : Word;
PROCEDURE re; BEGIN
if octava = 3 then Sound(587) else Sound(1174);
Delay(dlit);
NoSound END;
BEGIN
octava:=3; dlit:=1000; re; octava:=4; dlit:=1000; re; octava:=3; dlit:=2000; re
END.
Недостаток программы в том, что раздел операторов выглядит довольно мутно. Гораздо прозрачнее была бы такая запись:
BEGIN
re(3,1000); re(4,1000); re(3,2000)
END.
Для обеспечения такой прозрачности подходят процедуры с параметрами. Вот программа, использующая процедуру с параметрами:
USES CRT;
PROCEDURE re (octava : Byte; dlit: Word); BEGIN
if octava = 3 then Sound(587) else Sound(1174);
Delay(dlit);
NoSound END;
BEGIN
re(3,1000); re(4,1000); re(3,2000)
END.
Пояснения: Эта программа похожа на предыдущую, но имеется несколько отличий. Строка
Procedure re (octava : Byte; dlit: Word)
называется заголовком процедуры. Здесь после имени процедуры - re - ставятся скобки и внутри них описываются так называемые формальные параметрыпроцедуры. Здесь их два: octava и dlit. Поскольку они описаны в заголовке, пропадает необходимость в разделе VAR.
В записи re(3,1000) числа 3 и 1000 - так называемые фактические параметрыпроцедуры, их порядок и тип должен соответствовать формальным параметрам.
Когда во время выполнения программы Паскаль натыкается на re(4,1000), он присваивает переменной octava значение 4, переменной dlit - значение 1000 (то есть, присваивает формальным параметрам значения фактических параметров) и затем переходит к выполнению тела процедуры re.
Усложним задачу. Создадим универсальную процедуру nota, параметрами которой будут название ноты, октава и длительность. Учтем, что длительность ноты в музыке задается в так называемых долях. Доли бывают: 1 - целая нота, 1/2 - половинка длительности, 1/4 - четвертушка и т.д. Пусть целая нота звучит 1 секунду. Вызывать процедуру nota можно было бы так: nota(re,5,8) - это означает, что мы хотим, чтобы прозвучала нота re пятой октавы длительности 1/8.
Вот запись программы:
USES CRT;
TYPE Nota_type = (doo, doo_diez, re, re_diez, mi, fa, fa_diez, sol, sol_diez, la, la_diez, si);
PROCEDURE Nota(Nazvanie:Nota_type; Oktava,Dolya:Byte); {Здесь параметр Dolya - знаменатель доли}
VAR Hz:Word; {Внутри процедуры можно описывать свои переменные (в данном примере это Hz).
Они называются локальными. Подробнее о них - в 13.3}
BEGIN
{Объясним Паскалю частоту нужных нам нот третьей октавы}
case Nazvanie of
doo : Hz:=523;
re : Hz:=587;
sol : Hz:=784;
la : Hz:=880;
la_diez : Hz:=932;
end;
{Теперь меняем частоту в зависимости от октавы}
case Oktava of
1 : Hz:=Hz div 4; {Используем целочисленное деление,так как стандартная}
2 : Hz:=Hz div 2; {процедура Sound требует задавать частоту целым}
3 : Hz:=Hz; {числом герц}
4 : Hz:=Hz*2;
5 : Hz:=Hz*4;
6 : Hz:=Hz*8;
else WriteLn('Такой октавы не знаю'); ReadLn; Halt
end;
Sound (Hz); {Включаем звук}
Delay(10000 div Dolya); {Задаем пpодолжительность звука}
NoSound;
Delay (50); {Небольшой промежуток тишины после каждой ноты}
END;
BEGIN
{Вот первые ноты из песни “Широка страна моя родная”:}
Nota(re,3,8); Nota(re,3,16); Nota(re,4,4); Nota(re,4,8); Nota(re,4,8); Nota(doo,4,8);
Nota(la_diez,3,8); Nota(la,3,8); Nota(sol,3,4); Nota(re,3,4)
END.
Фактические параметры могут быть любыми выражениями подходящего типа. Например, вместо Nota(re,3,8) можно было бы написать a:=3; Nota(re, a, 11-a).
Задание 119: В модуле Graph не хватает процедуры, которая рисовала бы треугольник. Создайте такую процедуру. Она должна рисовать примерно равносторонний треугольник вершиной вверх и иметь три параметра: положение треугольника на экране и размер.
Функции
В 4.9 мы с вами уже сталкивались со стандартными функциями. Например, выражение 10+Sqr(3) имеет значение 19, так как функция Sqr(3) обозначает 32.
Вы можете создавать собственные функции. Предположим, вам часто приходится вычислять периметры прямоугольников. Тогда вам было бы удобно иметь функцию perimetr(10,4), которая имела бы значение периметра прямоугольника со сторонами 10 и 4. Рассмотрим, как это делается, на примере программы вычисления суммарной длины забора вокруг трех несоприкасающихся прямоугольных дворов:
FUNCTION perimetr(dlina,shirina:Word) : Integer;
BEGIN perimetr:=2*(dlina+shirina) END;
BEGIN
WriteLn(perimetr(10,4)+ perimetr(20,30)+ perimetr(3,8));
END.
Функцииочень похожи на процедуры. Но функция в отличие от процедуры обладает некоторыми свойствами переменной величины и поэтому описание функции отличается от описания процедуры следующими двумя вещами:
· В заголовке функции после скобок с формальными параметрами должен быть указан тип функции (у нас это Integer).
· Внутри описания функции между BEGIN и END ей хотя бы раз должно быть присвоено какое-нибудь значение (у нас это perimetr:=2*(dlina+shirina)).
Рассмотрим более сложный пример. Вспомним задачу из 12.3 о среднегодовой температуре, где исходными данными является массив из 365 значений температуры. Попробуем узнать, в январе (дни 1-31) или в декабре (дни 335-365) самый теплый день месяца был теплее. Мы можем предвидеть, что для вычисления понадобятся два похожих фрагмента программы, каждый длиной строчки по три-четыре. Чтобы не писать два раза похожие фрагменты, создадим функцию нахождения максимального элемента из заданного диапазона массива температур. Назовем ее max. Используя ее, мы можем в программе записать, например, так: u:=max(20,30), подразумевая, что переменной u должно быть присвоено значение максимального элемента массива температур, выбирая из элементов с 20-го по 30-й. Вот программа:
VAR t : array [1..365] of Integer; { t - массив температур за год}
FUNCTION max (perv,posledn:Word) :Integer;
VAR i,m :Integer;
BEGIN
m:=t[perv];
for i:=perv+1 to posledn do if t[i]>m then m:=t[i];
max:=m
END;
BEGIN
........ {Здесь присваиваем значения элементам массива температур}
if max(1,31)>max(335,365) then WriteLn(‘В январе’) else WriteLn(‘В декабре’);
END.
Задание 120: В Паскале не хватает функции для вычисления произвольной целой степени числа. Создайте функцию Power такого смысла: Power(2,3) должна иметь значение 23, то есть 8.
Задание 121: Если вы никак не можете смириться с системой координат графического режима, то напишите пару простеньких функций (например, с именами x и y), которые позволят вам считать, что отныне ось yнаправлена вверх, а центр координат расположен в центре экрана. Если вы правильно напишете эти функции, то, например, оператор Circle (x(310), y(230), 10) нарисует вам кружочек в правом верхнем углу экрана.