Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель игры на Паскале. АВС и немного Турбо

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
Хотя ещё не всё. Видите в нашей песне точки после некоторых нот? Их, нот, длительность увеличивается в полтора раза. То есть без точки она ¼, а с точкой уже ¼ +
1
/8 = 3/8.
Снова переписываем песню словами, но теперь уже с учётом длительностей. Длительности в скобках, они у нас будут параметрами. Только мы будем писать не исполнении ноты мы устанавливаем задержку в длительность целой ноты, делённой на 4. Нам так удобнее. Следуя этой логике, превращаются в
8
/3. Не пугайтесь.
1
/4, а просто 4. Означать это будет, что при
3
/8 закономерно
H1(4) H1(8) H1(8) /A1(8/3) A1(8) /G1(8) G1(8) G1(8) G1(8) /F1#(8/3) F1#(8)
E1(4) E1(4) /D1(4) E1(8) F1(8) /G1(4) G1(4) /G1(2)/
H1(4) H1(8) H1(8) /A1(4) A1(8) A1(8) /G1(8) A1(8) G1(8) A1(8)/
F1#(8/3) F1#(8) /E1(4) E1(4) /D1(4) E1(8) F1#(8)/ G1(4) G1(4) /G1(2)/
А теперь наконец попрограммируем! У нас есть запись песни в виде последовательности нот – напишем для каждой ноты по процедуре, что может быть проще? Или написать одну процедуру, а ноту передавать как параметр? А какая, собственно, разница. Поразмышляйте, и придёте к тому же выводу. Одна из прич
ин такого моего решения – а как передав
ать в
универсальную процедуру играемую ноту? Как параметр какого типа?
221
Строка? Именованная константа целого типа? Пользовательский тип? Рассмотрите эти варианты и подумайте. Другая причина – наглядность. Наглядность – дело во многом субъективное, но всё же. С точки зрения моей персональной субъективности запись исполнения до-ре-ми в виде
c(8);
d(8); e(8)
предпочтительнее чем
pl(c,8);
pl(d,8); pl(e,8);
Сколько раз повторялось, что использование глобальных переменных – преступ меня правильно, я от своих принципов не отказываюсь. Но иногда отступаю. А куда деваться, в этом случае? Как уже сказано, длительность – понятие относительное. Недаром в нотах обычно задаётся темп, словесно или численно. Хотелось бы иметь возможность написать вот так:
one:=1000;
То есть мы указ миллисекунд. А как передать значение one в процедуру для ноты? Как параметр? Была бы процедура одна, а их ну очень много. Какой-то очень мусорный текст получается. Так что придётся поступиться принципами. В результате, для многострадальной ноты ля второй октавы (она же первая струна на пятом) пишем:
procedu var
ti : real;
begin if t > 0 then begin
ление? Вот-вот. А сейчас мы их того… использу
али, что целой ноте соответствует длительность в тысячу
re a( t : real);
емПоймите
222
ti:=one / t; Sound(440); OurDelay(t); NoSound; end; end;
На что обратить внимание. В процедуру передаётся длительность не в миллисекундах, а в музыкальных понятиях – половинная, четвёртая, восьмая. Пересчитываем в миллисекунды уже внутри, с использованием злополучной переменной one. Условный оператор – защита от идиота, передавшего ноль. Не сомневайтесь, идиот найдётся.
Дальше, по идее, надо программировать ля диез, но чем он отличается? Только высотой. А не вынести ли нам всю внутренн процедуры в процедуру отдельную, а уже ей передавать заказанную высоту и длительность? Очень похож универсальную процедуру, только теперь она зарыта глубоко внутри нашей иерархии процедур. Конечному пользователю (который будет записывать му
procedure MS( hz : integer; t : real);
var
ti : real;
begin if t > 0 then begin ti:=one / t;
Sound(hz); OurDelay(t); NoSound; end;
end;
procedure a( t : real); begin MS( 440, t);
зыку) процедура уж
е не видна. Получается вот так:
е на предлагавшуюся раньше
ость нашей
223
end;
MS это не Microsoft, а MakeSound.
Самостоятельно программируем пару-тройку октав (про запас). Для нетрудолюбивых текст модуля в приложении. Что ещё осталось? Когда музыканту надоедает пиликать по скрипочке, он отдыхает. Не играет, в смысле. Это называется пауза. В зависимости от тунеядистости пиликальщика паузы классифицируются в точности так же, как и ноты – поло сейчас нам и не понадобятся.
Теперь опробуем.
h1(4); h1(8); h1(8); a1(8/3); a1(8); g1(8); g1(8); g1(8); g1(8); f1d(8/3); f1d(8);
Исполним. Звучит. Но звучит как-то того – легато, что ли. В смысле всё слиплось и одна нота плавно переходит в другую. Если это то, о чём Вы мечтали, то ладно, а иначе – решаем проблему. Сдел если Вы конечно понима сколько надо, а чуть меньше. От длительности ноты откусывается одна восьмая. Семь восьмых нота звучит как надо, последнюю восьмую не звучит. Константу можно поменять для большей стаккатистости. Бывают случаи, когда в нотах явно обозначено связное исполнение. Этот вариант в модуле тоже предусмотрен. Всякие бо Вот и наша песенка в финальном варианте:
h1(4); h1(8); h1(8); a1(8/3); a1(8); g1(8); g1(8); g1(8); g1(8); f1d(8/3); f1d(8); e1(4); e1(4); d1(4); e1(4); f1d(4); g1(4); g1(4); g1(2);
h1(4); h1(8); h1(8); a1(4); a1(8); a1(8);
льные фантазии вроде триолей мы игнорируем.
one:=3000;
винная, четвёртая. В модуле они предусмотрены, хотя
Напишем кусок мелодии:
аем чуток стаккато,
ете, о чём я говорю. Пусть нота звучит не
224
g1(8); a1(8); g1(8); a1(8); f1d(8/3); f1d(8); e1(4); e1(4); d1(4); e1(8); f1d(8); g1(4); g1(4); g1(2);
Оно надо? Рекурсия.
Настоящее произведение искусства, впрочем, совершенно бесполезное” – записал в своём дневнике начальник немецкого Генерального штаба
сухопутных войск генерал-полковник Франц Гальдер. Речь шла о гигантской восьмисотмиллиметровой пушке Дора, изваянной сумрачным германским гением. С рекурсией та же фигня. Красиво, но бесполезно. Но красиво. Но бесполезно. Хотите на кончили хреново Что такое рекурсия? Это очень просто. Это когда процедура вызывает сама себя. Или, понятное дело, функция вызывает сама себя. Как обычно, демонстрируем рекурсию на совершенно бессмысленном примере.
procedure Recurs; begin
Recurs; end;
Написали. Вызвали. Да, не очень хорошо. Модернизируем.
procedure Recurs; begin
Writeln(‘Muche trabajo!’); Recurs; end;
щем-то, немногим лучше. Но хоть видно, что работает. Вся проблема
В об с рекурсией в смысле, что всё когда-нибудь кончается. Сама по себе рекурсия не кончается. Это мы должны обеспечить её конец.
.
том, что когда-нибудь она должна кончиться. Нет, не в том
писатьнапишите. Но помните, что немцы
225
Всегда и везде, если речь заходит о рекурсии, её иллюстрируют на примере вычисления факториала. А мы что, не люди, что ли? И мы начнём с факториала. Напоминаю (какой-то уже раз): Факториал от N – это произведение всех целых чисел от единицы до N. Обозначается N!. Факториал мы уже считали. Вот таким образом:
function Fact( N : integer) : integer; var
result i : integer;
begin
result:=result * I; Fact:=result; end;
Применяем рекурсию. Сначала, как водится, думаем. Что такое факториал от 5? 5! = 1*2*3*4*5. А что такое 1*2*3*4? Правильно, это факториал от
4. То есть 5! = 4!*5. Очевидно, что эта забавная особенность присуща не только пятерке. N!=(N-1)!*N. И вот тут вползает рекурсия.
function Fact( N : integer) : integer; var
result : integer;
begin if N = 1
end;
Имя нашей функции появилос Вот оно, зримое проявление рекурсии! А главное здесь, что рекурсия не вечна. При N, равном единице рекурсия завершается. И это главное. Обратите внимание, раньше был цикл. А теперь цикла нет, но есть рекурсия. Рекурсия, по сути, замаскированный цикл. И обычно её можно
: integer;
result:=1;
for i:=1 to
then result:=1 else result:=N*Fact(N-1);
Fact:=result;
N do
ь в правой части оператора присва
ивания.
226
циклом заменить. Но иногда сделать это не то, что трудно, но коряво и неудобно.
Чуть-чуть технических подробностей. Подробность первая – теоретическая. Есть процедура A, она вызывает процедуру A, саму себя, в смысле. Именно это мы сейчас и наблюдали. Это называется прямая рекурсия. Но если есть процедура A, она вызывает процедуру B, а процедура B вызывает, в свою очередь, процедуру A, то это называется косвенная рекурсия. Теперь подробность практическая. У нас в п байта. Повезло. Переменных могло быть больше и размер у них мог быть потолще. Чем это чревато? Паскаль (и не только он) делит всю доступную память на две части – кучу (Heap) и стек (Stack). Кучу оставим пока в стороне. На что используется стек? Переменные, объявленные внутри п ис
ходной версии нашей процедуры расчёта факториала было объявлено две переменных, общим объёмом шесть байт. При входе в процедуру из стека забираются эти шесть байт, при выходе из процедуры шесть байт освобождаются и возвращаются в стек. Из чего попутно следует, что значения локальных переменных заб п
роцедуры. В рекурсивной версии нашей процедуры только одна переменная размером четыре байта. Но теперь процедура не завершается а, скорее всего, вызывает сама себя. И эти четыре байта выделяются снова. Если мы захотим посчитать факториал от тысячи, то потратим из стека тысячу, умноженную на четыре, байтов. Четыре байта, конечно не де Но даже мегабайта надолго не хватит, если объявить в локальных переменных какую-нибудь хреновину всего-навсего килобайт на двадцать. На самом деле, всё немного хуже. Передача параметров в процедуру и возвращение значения функции тоже используют ст Что делать? Экономить стек. Или увеличить его размер – если это возможно. Или не применять рекурсию.
ек. Так что пам
ньги. Размер стека в ABC не афишируется, но в Del
ять в стеке может закончиться даже быстрее, чем планировалось.
роцедуре объявлена всего одн
роцедуры (локальные переменные), размещаются в стеке. В
а переменная, занимающая жалких четыре
ываются при выходе из
phi он по умолчанию равен мегабайту.
А теперь ещё одна классическая задача на рекурсию. Реализуете её самостоятельно. Называется – рекурсивный обход дерева. Чтобы не мучиться, дальше всё самым мелким шрифтом.
Дерево, которое мы собираемся обходить – дерево каталогов. Мы знаем, как вывести оглавление каталога. Но только одного. А если в этом каталоге есть подкаталоги? Например, в каталоге c:\Program Files\PABC (где конкретно у меня стоит Pascal ABC) имеются ещё минимум четыре подкаталога. И минимум три из них содержат свои собственные подкаталоги. И мы хотим получить оглавление не только о содержатся в подкаталога тоже могут и так далее. Новых знаний нам для этого потребуется только два. Первое - команда перехода в другой каталог. Называется она ChDir. Параметр у неё ровно один, строкового типа. Значение параметра – ка диск. В этом случае сменится не только каталог, но и диск. Но для нашей задачи это лишнее. Второе – при сканировании после поиска файлов командой FindFirstFile запустить аналогичный поиск командой FindFirstDirectory. Она, как несложно догадаться ищет все подкаталоги в текущем каталоге.
прочем, я немн
В каков алгоритм решения нашей задачи, и где здесь возникает рекурсия? С помощью FindFirstFile и FindNext получаем и выводим список файлов, содержащихся в текущем каталоге. В списке этом каталогов не будет, там будут только файлы. Выво
rectory.
FindFirstDi
Пишем процедуру с одним параметром. Параметр – имя каталога, оглавление которого, включая все его подкаталоги, нам надо вывести. В процедуре – сначала переходим в заданный каталог, затем в цикле выводим (куда-то) список файлов. Если файл вдруг неожиданно оказывается каталогом, вызываем для него нашу процедуру. Во Собственно,
и всё. Приступайте к реализации.
х. Разумеется, подкаталоги эти могут содержать свои собственные подкаталоги, которые
талог, в которы
ого погорячился, только этими новыми знаниями нам не обойтись, но это уже попутно. А сначала -
й мы хотим перейти. Имя каталога может содержать и символ, обозначающий
сновного каталога PABC, но ещё и узнать, какие файлы
дим список. Затем запускаем второй поиск, с
т и рекурсия!
227
Меряем время
Предмет, рассматриваемый в данном разделе, прост, как не знаю что. Узнать, сколько времени. И всё. Но для запутывания интриги, попутно узнаем, который день. Зачем? Вы и так знаете? У Вас и справка есть? Ну мало ли что…
var DT : DateTime;
DT:=CurrentDateTime;
Загадочный тип DateTime объявлен не нами следующим образом:
type
eTime = record
Dat Day : integer;
onth : integer;
M Year : integer; Hour : integer; Minute : integer; Second : integer; Milliseconds : integer; end;
Для изучавших французский, перевожу. Поля записи – день, месяц, год, часы, минуты, секунды, миллисекунды. И не забываем, что всё это счастье содержится в модуле Utils, который мы и должны добавить в uses. Вроде бы всё кристально ясно и прозрачно. Теперь давайте не просто узн времени. Это будет чуть сложнее. Чтобы мерить что-то осмысленное и с пользой, предлагаю взять две процедуры определения факториала – обычную с циклом и рекурсивную – и померить, кто быстрее.
аем, который час, а померяем интерва
л
228
Какие проблемы и проблемки ждут нас на этом пути? Процедуры нужно переименовать – не могут же они сосуществовать в одной программе с одинаковыми именами. Если попробовать определить факториал хотя бы двадцати, мы получим очень странный результат. Вспомните, какое максимальное число помещается в четырёхбайтовое integer. Затем объявляем четыре переменные для получения и хран времени. А может быть, восемь? Четыре для времени начала расчета, четыре для времени окон А как мы будем считать, сколько времени прошло? Сделать это, оперируя часами, минутами и секундами, задача, безусловно, полезная и развивающая навык работы с условными операторами, но мы сейчас занимаемся немножко другим. Моё мнение – оба времени надо перевести в миллисекунды и узнать, сколько врем И вот их-то, миллисекунды в смысл переменные типа integer – время до и время после. Вот что имеем для начала:
program factor; uses Crt, Utils;
var
DT : DateTime; time0, time1 : integer; N : integer; rez : integer; i : integer; {----------------------------------------------------------} function OldFact( N : integer) : integer; var result : integer; i : integer;
begin
result:=1; for i:=1 to N do result:=result*i;
OldFact:=result;
чания
ени истекло, простым вычитанием.
е, и запоминать. Объявляем две
ения
229
end; {----------------------------------------------------------} function NewFact( N : integer) : integer; var result : integer; begin if N = 1 then result:=1 else result:=N*NewFact(N-1); NewFact:=result; end; {----------------------------------------------------------}
begin
{--------------- обычный способ ------------------}
{тут меряем} {--------------- рекурсивный способ -------------} {и тут меряем}
end.
В чём заключается само измерение?
Получить текущее время Перевести в миллисекунды и запомнить Посчитать факториал
Получить текущее время Перевести в миллисекунды и запомнить Вывести разницу между временами
Повторить для способа с рекурсией
айте, попробуйте, посмотрите на рез
Сдел получилось, что для 16! время расчёта является нулевым. А факториал большего числа в integer не вмещается. Можно было бы расширить доступный диапазон, использовав какой-нибудь плавающий тип. В этом случае мы, разумеется, получили бы только приближ
ультат, подумайте. У меня
ённое значение
230
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]