ГЛАВА II. Введение в язык программирования TURBO Pascal ───────────────────────────────────────────── Написание программы в конечном счете сводится к записи последовательности операторов на имеющем- ся в распоряжении программиста языке. То, таким образом, каждый из этих операторов представлен, определяет в значительной степени разумность всего в целом. Ни обьем комментариев, ни форма- тизация или документирование не могут в полной мере заменить хорошо представленные операторы. В конце концов именно они определяют то,что дей- ствительно д е л а е т программа. Керниган Б., Плоджер Ф. О п е р а т о р ы ───────────────── Операторы языка Pascal можно разделить на простые и сложные. П р о с т ы е не содержат внутри себя других операторов. С л о ж н ы е (структурные) операторы представляют собой конструкции, содержащие простые операторы. К простым операторам в языке Pascal относятся операторы: п р и с в а и в а н и я , п е р е х о д а , п у с т о й о п е р а т о р, о п е р а т о р ы в в о д а и в ы в о д а ; к сложным операторам относятся: с о с т а в н о й о п е р а т о р , о п е р а т о р у с л о в н о г о п е р е х о д а , о п е р а т о р ы ц и к л а , о п е р а т о р в ы - б о р а , о п е р а т о р п р и с о е д и н е н и я в з а п и с я х. Мы попытались сделать это пособие поучи- тельным, понятным и полезным.Чтобы полу- чить максимальную пользу, Вы должны ра- ботать с пособием самым активным обра- зом. Не занимайтесь просто чтением при- меров. Вводите их в Ваш компьютер и пы- тайтесь выполнить.Не бойтесь эксперимен- тировать - измените часть программы,что- бы увидеть, к чему это приведет. Модифи- цируйте Ваши программы, чтобы они слег- ка отличались от исходных.Попробуйте не обращать внимания на наши иногда встре- чающиеся предупреждения и посмотрите, что при этом произойдет. Чем больше Вы сделаете самостоятельно,тем большему на- учитесь. М.Уэйт, С.Прата, Д.Мартин 2.1. П р о с т ы е о п е р а т о р ы ──────────────────────────────── 2.1.1 О п е р а т о р п р и с в а и в а н и я ──────────────────────────────────────── Оператор присваивания - один из основных операторов любого процедурно- го языка программирования. Общая форма записи оператора: V:=A Здесь: 1) V - имя переменной; 2) := - знак присваивания; 3) A - выражение. Данный оператор вычисляет значение выражения A, стоящего справа от знака операции присваивания ":=", и присваивает полученное значение пере- менной V, стоящей слева. Следует обратить внимание на разницу между символом операции присваива- ния ":=" и символом равенства "=". Это различие заключается не только в форме, но и в содержании. Например, в обычной математической записи выражение X=X+2 является не- верным. Однако, запись оператора присваивания X:=X+2 правильна и означает следующее: к текущему значению переменной Х (пусть до выполнения оператора оно было равно 5) прибавляется число 2, и после выполнения данного операто- ра значение переменной Х будет равно числу 7. Переменная и выражение должны иметь о д и н а к о в ы й тип. ┌──────────────────────────────────────────────────────────────┐ │ Исключение составляет случай, когда тип переменной - Real. │ │ Тогда тип выражения может быть Integer. │ └──────────────────────────────────────────────────────────────┘ Отметим, что выражение может состоять из констант, переменных, функций и знаков операций. Если выражение не содержит скобок, то операции выполня- ются в следующем порядке: 1) NOT 2) *,/,DIV, MOD, AND 3) +,-,OR 4) =,<>,<=,<,>=,IN Операции одинакового старшинства выполняются слева направо. С помощью скобок можно задать любой желаемый порядок вычислений. П р и м е р. А теперь составим маленькую программку: ──────────── { Преобразование из галлонов в литры } A:=7; B:=A*4.54; Write(B); В н и м а н и е! Будьте осторожны, ибо ■■■ ■■■ var x: String[12]; begin x:='кошмар!'; x:=''; Writeln (x[1]); end. Running к > 2.1.2. П у с т о й о п е р а т о р ──────────────────────────── Пустой оператор не обозначается и не вызывает никаких действий. Его синтаксис: ┌──────────┐ │ ; │ └──────────┘ Пустой оператор - это просто символ ";" (точка с запятой) в программе. Чаще всего пустой оператор встречается с м е т к о й и ставится в конце составного оператора, процедуры или функции. Кстати, рассмотрим вопрос о пунктуации в программе. Все операторы от- деляются друг от друга символом "точка с запятой" (;). Поэтому точку с за- пятой часто называют р а з д е л и т е л е м. Если между двумя операторами отсутствует точка с запятой, то это при- ведет к возникновению ошибки, поскольку компилятор часто не может "понять", что же хотел сказать автор программы. Пусть, например, записано X:=1 Y:=2; Несмотря на то, что операторы присваивания записаны на разных строчках, компилятор будет воспринимать эту запись как X:=1Y:=2 В итоге получается "оператор", в котором используются два знака присваи- вания и неправильный идентификатор 1Y (идентификатор не может начинаться с цифры). Если же записано X:=1;; Y:=2; (т.е. поставлены лишняя точка с запятой), то это уже поддается осмысленной интерпретации: сначала идет оператор присваивания X:=1; затем пустой опера- тор (еще одна точка с запятой, зачем она поставлена - это уже другой во- прос), а вслед за ним - еще один оператор присваивания Y:=2. Таким образом, можно (но не нужно!) ставить подряд несколько точек с запятой везде, где можно поставить одну точку с запятой. Вместе с тем есть ряд мест в программе, где точка с запятой не ставится. Рассмотрим, например, составной оператор begin S1; S2;...;SN end; Зарезервированные слова begin и end представляют собой операторные скобки. Поэтому приведенный оператор можно мысленно представить в таком виде: (S1; S2;...;SN); Вряд ли Вы поставите точку с запятой после открывающей и перед закрываю- щей скобками. Этого же правила следует придерживаться при записи составно- го оператора. Точка с запятой никогда не ставится после зарезервированного слова begin. В противном случае будет зарегистрирована ошибка). Перед зарезервированным словом end точка с запятой может присутствовать, хотя она там вовсе не нужна. В этом случае между последней точкой с запя- той и зарезервированным словом end будет мыслиться пустой оператор. Часто в программах используются вложенныедруг в друга циклы, тела кото- рых записываются как составные операторы. В этих случаях в конце такого фрагмента программы стоят несколько зарезервированных слов end: ... end end ... end end; В этом случае точка с запятой ставится только после последнего слова end. Если же эта конструкция стоит в самом конце программы, т.е. мы имеем дело с ситуацией ... end end ... end end. то точка с запятой вообще не ставится. Особо отметим следующую ситуацию. Если помеченный пустой оператор стоит непосредственно перед зарезервированным словом end составного опе- ратора или программы, то между ним и предшествующим оператором обяза- тельно ставится точка с запятой. Например, X:=-Y; 99: ° end. └─ ! Спагетти - вид длинной вермишели (обычно в виде мотков), которую отваривают в под- соленной воде или бульоне. Кулинарная книга 2.1.3. О п е р а т о р п е р е х о д а ──────────────────────────────── В языке Pascal принят естественный порядок выполнения программы: все операторы выполняются последовательно один за другим в том порядке, как они записаны. Однако в практике программирования задач возникает необходи- мость нарушения последовательности выполнения операторов. Например, необ- ходимо обойти участок программы, а вернуться к нему позже. Для этого пред- назначен оператор п е р е х о д а, который имеет следующую форму записи: ┌─────────────────────────┐ │ goto м е т к а ; │ └─────────────────────────┘ где: 1) goto ("идти к") - служебное слово; 2) м е т к а - целое число без знака в диапазоне 1·9999. Более того, в языке TURBO Pascal разрешено использовать в качестве меток идентифика- торы! Метка записывается перед помечаемым оператором и отделяется от него двоеточием: goto 32; 10: A:=2; ■■■ 32: Y:=X/Z Здесь, после оператора GOTO 32 выполняется оператор с меткой 32. Следу- ет отметить, что оператор, следующий за оператором перехода, также должен быть помечен. Иначе все операторы в программе между оператором goto и опе- ратором с меткой 32 будут лишними, так как доступа к ним нет и они никог- да не будут выполняться. Метка должна быть объявлена в разделе описания label. Объявление меток имеет вид: ┌───────────────────────────────────┐ │ LABEL Метка1,Метка2,...,МеткаN; │ └───────────────────────────────────┘ Для рассмотренного примера объявление меток выглядит следующим образом: label 10,32; Метки действуют только внутри блока, не вкладываясь во внутренние блоки (н е л ь з я перейти на метку, определенную в н е текущего модуля). Данный оператор служит для передачи управления из одной точки программы в другую. В некоторых случаях он весьма полезен, но в то же время этот опе- ратор затрудняет восприятие программы (имеется в виду восприятие программы человеком, так как для компьютера обработка операторов goto труда не сос- тавляет). Отслеживание этого оператора напоминает ситуацию, в какой Вы ока- зались бы, если бы при чтении романа Вам приходилось все время перелисты- вать книгу на несколько страниц то назад, то вперед. прерывая логическую последовательность алгоритма, операторы goto практически исключают возмож- ность хорошо разобраться в структуре программы для всех, кроме ее автора. А это в свою очередь не позволяет делать в программе какие-либо изменения без риска внести ошибки в самые неожиданные ее части. Оператор GOTO - довольно архаичное языковое средство (причем сохранился он больше по традиции, чем по необходимости). Оператора GOTO избегают сей- час потому, что необдуманное применение этого языкового средства может лег- ко повлечь за собой образование так называемых п р о г р а м м - с п а г е т т и: если потащить одну спагетти из большой миски, то где-ни- будь в другом месте миски что-то тоже потянется, а где - угадать невозмо- жно. Конечно, написать программу-спагетти на языке Pascal можно и без GOTO, если, например, необдуманно применить глобальные переменные, неосто- рожно использовать указатели, упустить из виду побочное действие функций. Все эти опасные моменты гораздо хуже, чем GOTO, так как глобальные перемен- ные, указатели и процедуры нужны нам для полезных целей и обойтись без них невозможно. Несколько слов о постановке символа точка с запятой... Если необходимо пропустить несколько операторов в составном операторе, но не выходить из него, то перед зарезервированным словом end ставится метка и двоеточие, например ■■■ lab: end; Заметим, что смысл программы может существенно меняться в зависимости от того, где стоит метка: п е р е д или п о с л е слова end. Аналогично, если необходимо завершить выполнение всей программы, про- пустив последовательность операторов, помеченный пустой оператор ставит- ся перед заключительным end программы. ■■■ lab: end. 2.1.4. "О п е р а т о р ы" в в о д а и в ы в о д а д а н н ы х ─────────────────────────────────────────────────────────── Для ввода и вывода данных в языке Pascal предусмотрены процедуры ввода- вывода: Read, Readln, Write и Writeln Часто эти процедуры называют о п е р а т о р а м и по аналогии с дру- гими операторами языка. Можно услышать, например, "оператор Write" или "оператор Read". Строго говоря, называть процедуры операторами неправильно. Однако вряд ли стоит осуждать такую терминологическую неточность. Тем более, что в других язы- ках программирования говорят именно об операторах ввода-вывода. В конце концов, важна суть, а не форма. Процедура в в о д а имеет следующий формат: ┌─────────────────────────────┐ │ Read (a1,a2,...,aN) │ └─────────────────────────────┘ где: 1) Read ("читать") - служебное слово; 2) a1,a2,...aN - имена переменных, которым последовательно присваива- ются вводимые значения. Числовые значения указываются через пробел, признаком окончания ввода является нажатие клавиши "RETURN". Обратите внимание: числовые значения вводятся после набора на экране дисплея всей программы и запуска ее на вы- полнение. Например, пусть переменным A,B,C необходимо присвоить в процессе выпол- нения программы следующие значения: A=5, B=17, C=6.2 Процедура ввода примет вид: Read (A,B,C); а числовые значения можно ввести следующим образом: 5 17 6.2 (нажмите клавишу "RETURN") Если вновь повторить запуск программы, то можно ввести другие значения, например: 16 -4 -0.5 (нажмите клавишу "RETURN") Переменные получат новые значения A=16, B=-4, C=-0.5, при которых будет выполняться программа. Ни один оператор программы в этом случае не изменяется. Допускается использование оператора ввода без параметров Readln , осуществляющего переход на н о в у ю с т р о к у при вводе данных. Дополнительно к этому имеется оператор ввода ┌───────────────────────────────┐ │ Readln (a1,a2,...,aN) │ └───────────────────────────────┘ который сначала вводит значения a1, a2, ..., aN, а затем осуществляет пе- реход на новую строку, т.о. один оператор равносилен использованию двух предыдущих операторов. При вызове процедур Read или Readln строка с клавиатуры (консоли) вво- дится в буфер и хранится там, а операции чтения переменных используют этот буфер как источник ввода. Это позволяет проводить редактирование во время ввода. Перечислим возможности редактирования: 1) клавиши "BS" и "DEL": возвращает на предыдущую позицию и уничтожат там символ. Клавишу "BS" можно имитировать нажатием клавиши ў── или ком- бинацией клавиш "CTRL"+"H"; 2) "ESC" и "CTRL"+"X": возвращает в начало строки и уничтожает все на- бранные символы строки; 3) "CTRL"+"D": вызывает один символ из предыдущей введенной строки; 4) "CTRL"+"R": вызывает предыдущую введенную строку; 5) "RETURN" и "CTRL"+"M": завершает вводимую строку и помещает символ CR/LF в буфер строки (без "эха" на экран); 6) "CTRL"+"Z": завершает вводимую строку и помещает маркер конца файла "CTRL"+"Z" в буфер строки. Входная строка, хранится в буфере с присоединенным в конце ее символом "CTRL"+"Z" Поэтому, если в строке задано меньше значений, чем указано переменных в списке параметров процедуры read , то любые переменные типа Char получат значение CTRL+Z, а строковые переменные будут пустыми строками, а число- вые переменные останутся без изменений. Максимальное количество символов входной строки, вводимой с консоли, равно 127. Однако это число можно уменьшить присвоением предопределенной переменной с именем BUFLEN значения в интервале от 0 до 127. П р и м е р. Write ('Имя файла (максимум 14 символов):'); ─────────── BUFLEN := 14; Read (FileName); Заметим, что значение, присвоенное переменной BUFLEN сохраняется толь- ко до ближайшего после него обращения к процедуре Read, а после нее вос- станавливается значение 127. Для в ы в о д а данных из памяти компьютера на экран дисплея предназ- начена процедура в ы в о д а ┌─────────────────────────────┐ │ Write (a1,a2,...,aN); │ └─────────────────────────────┘ где: 1) Write ("писать") - служебное слово; 2) a1,a2,...,aN являются либо переменными, либо строкой символов, заключенной в апострофы. Например, операторы B:=5; Write ('Значение B=',B); позволяют вывести на экран дисплея строку Значение B= 5 В следующих описаниях различных форматов и их действий символы: I,m,n обозначают выражение типа Integer R обозначает выражение типа Real Ch обозначает выражение типа Char S обозначает выражение типа String B обозначает выражение типа Boolean Изучите следующую таблицу: ┌──────────┬───────────────────────────────────────────────────────────┐ │ Параметр │ Выполняемое действия │ ├──────────┼───────────────────────────────────────────────────────────┤ │ Ch │ Выводится символ Ch │ │ Ch:n │ Символ Ch выводится в правую позицию поля ширины n , │ │ │ т.е. символу Ch предшествует n-1 пробел. │ │ S │ Выводится строка S. Символьный массив так не может быть │ │ │ выведен, если он совместим со строкой. │ │ S:n │ Строка S выводится в правую позицию поля ширины n , │ │ │ т.е. строке S предшествует n-LENGTH(S) пробелов. │ │ B │ Соответствующее значение величины B (слово TRUE │ │ │ или слово FALSE выводится) │ │ B:n │ Соответствующее значение величины B (слово TRUE │ │ │ или слово FALSE выводятся) помещается в правую позицию │ │ │ поля ширины n │ │ I │ Десятичное представление величины I выводится │ │ I:n │ Десятичное представление величины I выводится в правую │ │ │ позицию поля ширины n │ │ R │ Десятичное представление величины R выводится в поле │ │ │ шириной 18 символов в формате с плавающей точкой. │ │ │ Для R>0.0 формат: │ │ │ __#.#*########E*## , │ │ │ для R<0.0 формат: │ │ │ _-#.##########E*## , │ │ │ где: символ "_" обозначает пробел, │ │ │ символ "#" обозначает цифру, │ │ │ символ "*" обозначает символы "+" или "-" │ │ R:n │ Десятичное представление величины R выводится в правую │ │ │ позицию поля ширины n , в формате с плавающей точкой. │ │ │ Для R>0.0 формат: │ │ │ blanks#.digitsE*## , │ │ │ для R<0.0 формат: │ │ │ blanks-#.digitsE*## , │ │ │ где: blanks обозначает 0 или более пробелов, │ │ │ символ '#' обозначает цифру │ │ │ digits обозначает от 1 до 10 цифр. │ │ │ символ '*' обозначает символы '+' или '-' │ │ │ Так как по крайней мере одна цифра после точки всегда │ │ │ выводится, то ширина поля как минимум 7 символов │ │ │ (8 символов для случая R<0.0). │ │ R:n:m │ Десятичное представление величины R выводится в правую │ │ │ позицию поля ширины n , в формате с фиксированной точ- │ │ │ кой и m знаками после нее. Не выводится дробная часть │ │ │ и точка, если m=0, и должно быть заключено в пределах │ │ │ от 0 до 24,иначе используется формат с плавающей точкой.│ │ │ Числу предшествует такое количество пробелов, чтобы │ │ │ ширина поля составила n . │ └──────────┴───────────────────────────────────────────────────────────┘ Процедура в ы в о д а без параметров ┌────────────────────┐ │ Writeln; │ └────────────────────┘ осуществляет переход на новую строку экрана дисплея. Последующий оператор вывода с параметрами будет выводить данные на новую строку экрана. Опера- тор вывода без параметров часто используется для пропуска пустых строк. Процедура в ы в о д а ┌────────────────────────────┐ │ Writeln (a1,a2,...,aN); │ └────────────────────────────┘ осуществляет сначала вывод на экран дисплея значений a1,a2,...,aN,а затем- переход на новую строку. Таким образом, выполнение данной процедуры эквива- лентен последовательному выполнению двух процедур Write (a1,a2,...,aN); Writeln; "Программа должна быть надежной, насколько это возможно, так, ее поведе- ние должно быть разумным даже при неправильном использовании,а ее правиль- ное применение должно быть легким. Задайте себе вопрос: "Сможет ли она за- щитить себя от тупости и необразованности пользователей (включая меня)? Хотел бы я сам пользоваться ею?" Ввод-вывод служит интерфейсом между программой и окружающей ее средой. Два правила управляют всем программированием ввода-вывода: Н и к о г д а н е н а д е й т е с ь н а п р а в и л ь н о с т ь д а н н ы х и п о- м н и т е о п о л ь з о в а т е л е . 1. Проверяйте вводимые данные на обоснованность и правдоподобие. 2. Убедитесь в том, что данные не нарушают ограничений программы. ("Му- сор на входе - мусор в результатах" не есть закон природы,но может являть- ся комментарием к тому, насколько хорошо на практике следовать принципам (1) и (2).) 3. Выполняйте ввод до тех пор,пока не встретится конец файла или некий признак, а не пересчитывйте данные. 4. Находите ошибки ввода и по возможности продолжайте его. Не останав- ливайтесь на первой ошибке. Не игнорируйте ошибки. 5. Используйте мнемонический ввод и вывод. Организуйте ввод таким обра- зом, чтобы подготовить данные было просто (и просто подготовить).Пусть вво- димые данные и данные, задаваемые по умолчанию, находят отражение в выводи- мой информации. 6. Выделяйте ввод-вывод вместо того,чтобы разбрасывать его по всей про- грамме. 7. Убедитесь в том, что в структуре программы находят отражение обраба- тываемые данные." Керниган Б., Плоджер Ф. 2.2. С л о ж н ы е о п е р а т о р ы ───────────────────────────────── 2.2.1 С о с т а в н о й о п е р а т о р ────────────────────────────────── Языковое средство, обеспечивающее последовательное выполнение в языке Pascal, называется с о с т а в н ы м оператором. Составной оператор имеет вид begin S1; S2;...SN end , где S1,S2,...,SN - операторы. Если при некотором условии надо выполнить определенную последователь- ность операторов, то их объединяют в один составной оператор. С о с т а в н о й оператор начинается ключевым словом begin и заканчи- вается словом end. Между этими словами помещаются составляющие операторы, которые выполняются в порядке их следования. Например: begin I:=2; K:=I/5 end Слова begin и end представляют собой так называемые о п е р а т о р - н ы е с к о б к и . Отметим, что тело самой программы также имеет вид составного оператора. Любой из операторов составного оператора, в свою очередь, также может быть составным. Нельзя извне составного оператора передавать управление внутрь его. Отметим, что Pascal - язык последовательного программирования. Он может использоваться только для программирования последовательных процессов. Процесс, который может быть представлен как последовательность подпро- цессов, называется п о с л е д о в а т е л ь н ы м процессом. Существу- ют процессы, где необходимо или желательно, чтобы несколько подпроцессов протекали одновременно. Такие процессы относятся к числу п а р а л л е - л ь н ы х. Составной оператор часто применяется в операторе условного перехода. 2.2.2 О п е р а т о р у с л о в н о г о п е р е х о д а ─────────────────────────────────────────────────── Этот оператор имеет две разновидности: if и case. Операторы условного перехода позволяют выбрать для исполнения один из нескольких операторов-компонент. 2.2.2.1 О п е р а т о р IF ─────────────────── Напомним, что булевские (логические) выражения могут принимать одно из двух значений: TRUE (истина) либо FALSE (ложь). Общий вид оператора IF: ┌─────────────────────────────────┐ │ if A then ST1 else ST2; │ └─────────────────────────────────┘ где: 1) A - булевское выражение, 2) ST1,ST2 - операторы. Если значение выражения A - TRUE, то выполняется оператор ST1; если зна- чение выражения A - FALSE, то выполняется оператор ST2. Затем, в обоих слу- чаях управление передается следующему оператору. З а м е ч а н и е 1. Перед else и после then н е л ь з я ставить точ- ку с запятой. З а м е ч а н и е 2. Согласно синтаксису условного оператора верна, на- пример, следующая конструкция: if E1 then if E2 then S1 else S2; где Е1 и Е2 - логические выражения; S1,S2 - произвольные операторы. Синтаксическая неоднозначность этой конструкции разрешается, если учесть, что слово ELSE всегда сопоставляется длижайшему предшествующему и еще не сопоставленному слову THEN, т.е. if E1 then begin if E2 then S1 else S2 end; В языке Pascal допускается и краткая форма условного оператора: ┌───────────────────────┐ │ if A then ST; │ └───────────────────────┘ где: 1) A - булевское выражение; 2) ST - оператор. Если значение A - TRUE, то выполняется оператор ST. Если значение A - FALSE, то управление передается оператору,следующему за оператором IF. Краткой формой условного оператора нужно пользоваться осторожно, т.к. может нарушиться вся структура при вложенных условных операторах. Вместо краткой формы рекомендуется использовать всегда полную форму, но после слова else ничего не ставить (говорят, указывается пустой оператор). Кро- ме того, допускается использование пустого оператора и после слова then. Например: if A>15 then Y:=X-7; IF A<=15 then else; else Y:=X-7; Z:=SUM+1 Z:=SUM+1 П р и м е р 1. Программа позволяет определить большее из двух вещест- ───────────── венных чисел A и B. PROGRAM bol(input,output); var A,B: Real; { Аргументы } M: Real; { Результат } BEGIN Readln (A,B); if A>B then M:=A else M:=B; Write(m) END. П р и м е р 2. ─────────────── В отрывке из учебника "Аль-Джабр Ва-Аль-Мукабала" ("Наука исключения и сокращения") арабского математика аль-Хорезми для иллюстрации метода, называемого сейчас "выделением полного квадрата", решается уравнение x2+10■x=39. Эта книга, написанная в Багдаде в 820 г., сыграла огромную роль в развитии математики в эпоху средневековья; от нее же пошли слова "алгебра" (от "Аль-Джабр" в заголовке) и "алгоритм" (от "аль-Хорезми") Программа на TURBO Pascal воспроизводит шаги решения уравнения, исполь- зованные аль-Хорезми. PROGRAM Horezmi (input,output); var koff,prav: real; polovina,novprav,X: real; BEGIN writeln('Введите коэффициент при X'); readln (koff); writeln('Введите правую часть') ; readln (prav); { Вначале разделим коэффициент при X на 2. В нашем случае коэффициент равен 10, а его половина - 5 } polovina := koff/2; { Вновь полученное число 5 возводим в квадрат и прибавляем к обоим частям уравнения. Левая часть превращается в X- квадрат +10■X+25, что равно (X+5) в квадрате, а правая часть становится равной 39+25, что равно 64 или 8 в квадрате.} novprav := prav + polovina*polovina; { Извлечем теперь из обоих частей квадратный корень и возьмем положи- тельные значения, что дает X+5=8. Находим X, вычитая из обеих частей 5. } if novprav<0 then begin writeln('Нет действительных решений для величины X'); halt end else begin X:=sqrt(novprav) - polovina; writeln('Число ',X:5:2,' решает уравнение') end END. П р и м е р 3. Определение високосного года ────────────── PROGRAM visokos_year (input,output); var Year: 1001..9999; flag: Boolean; BEGIN Writeln ('Какой год Вы хотите проверить?'); Readln (Year); flag:=FALSE; if (Year Mod 4=0) and (Year Mod 100<>0) then flag:=TRUE; if Year mod 400=0 then flag:=TRUE; if flag then Writeln (Year:4,' - високосный год') else Writeln (Year:4,' - не високосный год') END. Синтаксическая двусмысленность, возникающая в конструкции: if выражение 1 then if выражение 2 then оператор 1 else оператор 2 разрешается следующей перестройкой конструкции: if выражение 1 then begin if выражение 2 then оператор 1 else оператор 2 end оператор 2 относится ко второму из операторов IF, первый оператор IF не имеет ELSE-части. П р и м е р ы. if Interest > 25 ────────────── then Usury:=True else TakeLoan:=OK; if (Entry < 0) or (Entry > 100) then begin Write('******'); Read(Entry) end; Мой генерал, артиллерия не стреляла по двадцати причинам. Во-первых, не было снарядов... Старая армейская шутка 2.2.2.2 О п е р а т о р CASE ───────────────────── Обычно при написании программы не рекомендуется использовать многократ- но вложенные друг в друга условные операторы - программа становится гро- моздкой и ее трудно понимать. Считается, что число уровней вложения не до- лжно превышать двух-трех. Но как быть, если необходимо проверять достаточ- но много условий и в зависимости от них выполнять те или иные действия? Для этих целей в языке Pascal существует специальный оператор выбора. Общий вид оператора: ┌────────────────────────────┐ │ case N of │ ? Подумать, │ M1,...,MN: ST1; │ ? как это │ K1,...,KN: ST2; │ ? грамотно │ ■■■ │ ? описать!!! │ D1,...,DS: ST │ ? │ end; │ ? └────────────────────────────┘ где: 1) case ("случай") - служебное слово; 2) N - с е л е к т о р; 3) MI,KI,... - метки (I=1,2,...), которые отличаются по смыслу от меток, описываемых в разделе LABEL. Переключатель и метки должны быть одного и того же с к а л я р н о г о типа. Оператор CASE передает управление тому оператору STI, с одной из меток которого совпало значение селектора N, а затем - на следующий за END опе- ратор. Если значение селектора N не совпало ни с одной из меток, то воз- никает ошибка, и выполнение всей программы аварийно завершается. Оператор состоит из выражения (селектора) и списка операторов, каждый из которых помечен меткой того же типа, что и селектор. Селектор должен быть скалярного типа, но не типа Real. Оператор выбирает для исполнения оператор, метка которого равна (или включает в себя, если это подмножест- во) текущему значению селектра. Если такая метка в списке не встречается, то исполняется оператор, следующий за зарезервированным словом ELSE. (В стандартном Паскале нет ELSE-части) Метка может состоять из нескольких констант или подмножеств, разделен- ных запятыми. Подмножество записывается как две константы между которыми ставится '..'. Тип констант должен быть тот же, что и у селектора. Например: case I of 2: x:=0; 3: x:=x*x; 100: x:=Sin(x); end; A:=B; Если значение I есть 3, то выполняется оператор X:=X*X; а затем управ- ление передается на оператор A:=B. П р и м е р ы. ───────────── case Operator of '+' : Result:= Answer + Result; '-' : Result:= Answer - Result; '*' : Result:= Answer * Result; '/' : Result:= Answer / Result; end; case Year of M1n..1939 : begin Time:=PreWorldWar2; Writeln('The world at peace...'); end; 1946..Max : begin Time:=PostWorldWar2; Writeln('Building a new world'); end; else begin Time:=WorldWar2; Writeln('We are at war'); end; end; З а м е ч а н и е 1. Метки оператора case не описываются в разделе label и на них нельзя переходить оператором goto. З а м е ч а н и е 2. В операторе выбора точка с запятой не ставится после последнего элемента списка выбора. П р и м е р 1. ─────────────── PROGRAM upr(input,output); var n,a1,a2,c,islo,mesqc,god: Integer; p: Boolean; BEGIN writeln(' Эта программа определяет по заданным числам,'); writeln(' месяцу и году день недели (М.Ленуа).'); writeln(' Введите дату в виде: число месяц год ,'); writeln(' например, 12 7 1950 (12 июля 1950 года)'); Readln (islo,mesqc,god); (* Представим год в виде: год = a1■100 + a2 *) a1:=god div 100; a2:=god mod 100; (* Определим, является ли год в и с о к о с н ы м *) p:=(a2=0) and (a1 mod 4=0) or (a2<>0) and (a2 mod 4=0);n:=0; if mesqc<3 then if p (* Если год високосный, то *) then n:=1 else n:=2; c:=trunc(365.25*a2)+trunc(30.56*mesqc)+islo+n; c:=(c+2) mod 7+1; writeln('День недели ',islo:2,'.',mesqc:2,'.',god:4,' есть '); case c of 1: write(' понедельник'); 2: write(' вторник'); 3: write(' среда'); 4: write(' четверг'); 5: write(' пятница'); 6: write(' суббота'); 7: write(' воскресенье'); end END. П р и м е р 2. Решение квадратного уравнения A■x2+B■x+C=0 ────────────── PROGRAM a(input,output); label 111; var a,b,c,d,x1,x2:real;m:integer;z:char; BEGIN writeln('Введите через пробел три вещественных числа, которые представляют собой значения коэффициентов уравнения А,В,С'); 111: Read (a,b,c); Writeln; d:=b*b-4*a*c; m:=3; { Квадратное уравнение } if (a=0) and (b<>0) and (c<>0) then m:=1; { Линейное уравнение } if (a=0) and (b=0) and (c<>0) then m:=4; { Решений нет } if (a=0) and (b=0) and (c=0) then m:=2; { x - любое число } if (m=3) and (d<0) then m:=5; { Уравнение имеет компл. корни} case m of 1: begin x1:=-c/b; Write('Решение -- ',x1) end; 2: write('Решение -- все действительные числа'); 3: begin x1:=(-b-sqrt(d))/(2*a);x2:=(-b+sqrt(d))/(2*a); write('Решения -- ',x1,' и ',x2) end; 4: write(c,'=0 ?!'); 5: write('Действительных корней н е т'); end; Writeln; Write('Еще раз ?'); Read(z); Writeln; if (z='d') or (z='y') then goto 111 END. П р и м е р 3. Все о сигналах светофора... ─────────────── PROGRAM pravila (input, output); type Svetofor=(red,yellow,green); var Signal: Svetofor; BEGIN for Signal:= red to green do case Signal of red: begin writeln('Красный сигнал, в том числе мигающий, или'); writeln('два попеременно мигающих красных сигнала запрещаю т движение.'); writeln('--------------------') end; yellow: begin writeln('Желтый сигнал запрещает движение и преду преждает о'); writeln('предстоящей смене сигналов.'); writeln('Желтый мигающий сигнал разрешает движение и ин формирует о'); writeln('наличии нерегулируемого перекрестка или пешехо дного перехода.'); writeln('--------------------') end; green: begin writeln('Зеленый круглый сигнал разрешает движени е.'); writeln('Зеленый мигающий сигнал разрешает движение'); writeln('и информирует, что время его действия истекает '); writeln('и вскоре будет включен запрещающий сигнал.') end end END. Оператором case удобно пользоваться при выводе значений перечислимых типов. Привести пример!!! Едва ли существует хоть одна программа, написанная с практической, а не учебной целью, которая бы не использовала циклов и массивов (или аналогичных им структур данных). Н.Вирт 2.2.3 О п е р а т о р ц и к л а ────────────────────────── Операторы цикла заставляют выполняться входящие в их состав операторы (так называемые о п е р а т о р ы т е л а ц и к л а) несколько раз,в частности один раз или ни разу. В языке Pascal имеется три вида операторов цикла: while, repeat и for. Прежде чем начать какое-нибудь дело, прикинь сможешь ли завершить его. тогда не уподобишься тому, кто взялся проехаться верхом на тигре. Из древнего китайского сборника афоризмов "Хун Цзычен. Вкус корней" (начало XVII в.) 2.2.3.1 О п е р а т о р ц и к л а WHILE ───────────────────────────────── Общий вид оператора: ┌───────────────────────┐ │ While A do ST; │ , └───────────────────────┘ где: 1) A - булевское (логическое) выражение; 2) ST - оператор (простой или составной). Значение выражения A вычисляется перед каждым выполнением оператора ST. Если значение A - TRUE, то оператор ST выполняется и управление передается на вычисление значения выражения A; если значение выражения A - FALSE, то оператор ST не выполняется и происходит выход из цикла. Заметим, что если первоначальное значение выражения A - FALSE, то опера- тор ST не будет выполнен ни разу. В операторе ц и к л а while точка с запятой никогда не ставится после зарезервированного слова do. Чтобы избежать бесконечного повторения, необходимо хотя бы одну перемен- ную, входящую в у с л о в и е, изменять в теле оператора цикла. Более то- го, эти изменения должны быть таковы, чтобы булевское выражение рано или поздно обратилось в "ложь". Если же булевское выражение первоначально ис- тинно и ни при каких обстоятельствах не становится ложным, то выполнение оператора цикла никогда не завершится! Если даны две неравные величины и из большей вычитается часть, большая по- ловины, а из остатка - снова часть, большая половины и это повторяется по- стоянно, то когда-нибудь остается ве- личина, которая меньше, чем меньшая из данных величин. Евклид. Начала. Книга X, предл.1 П р и м е р 1. Пусть A и B - положительные вещественные числа и A>B. ────────────── Найти такое натуральное m, что m■B>A. PROGRAM Euclid (input,output); var A,B: Real; m: Integer; BEGIN Write('Введите числа A и B... '); Readln(A,B); m:=1; while m*B<=A do m:=m+1; Writeln('Результат... ',m:3,'*',B:3:2,'>',A:6:2) END. П р и м е р 2. Вычислить сумму S=1+1/2+1/3+...+1/50 ────────────── PROGRAM N1 (output); var S: Real; N: Integer; BEGIN S:=0; N:=1; while N<=50 do begin S:=S+1/N; N:=N+1 end; Writeln ('S=',S) END. Running S= 4.4992053382E+00 > П р и м е р 3. Для данного компилятора определение наименьшего числа ─────────────── EPS такого, что 1+EPS>1. PROGRAM def_m_eps (input,output); const c1=1.0; c2=2.0; var EPS,eps1: Real; BEGIN EPS:=c1; eps1:=c2; while eps1 > 1.0 do begin EPS := EPS/2; eps1 := eps+1 end; EPS := EPS*2; writeln('Приближенное значение EPS равно ',EPS) END. П р и м е р 4. Г р е ч е с к и й алгоритм. Сейчас мы приведем ─────────────── один из самых древних алгоритмов, используемых в про- граммировании и поныне (Евклид, "Начала", III в. до н.э.) - алгоритм на- хождения наибольшего делителя двух чисел. PROGRAM Euclid (input,output); var sh1,sh2: integer; bol,men,ost:integer; BEGIN writeln('Введите первое число'); readln (sh1); writeln('Введите второе число'); readln (sh2); { Проверить, какое из чисел больше, а какое меньше. } if sh1>=sh2 then begin bol:=sh1; men:=sh2 end else begin bol:=sh2; men:=sh1 end; { Отнимать из большего числа меньшее, пока остаток не станет меньше меньшего из чисел. Заменить большее число на меньшее, а меньшее на остаток и повторять это, пока остаток не станет нулем.} while (bol mod men) <> 0 do begin ost:= bol mod men; bol:=men; men:=ost end; writeln('Наибольшее число, делящее ',sh1,' и ',sh2,' это - ', men) END. П р и м е р 5.[Каймин В.А.] Программа, позволяющая угадывать целое ──────────────────────────── число из отрезка [1,99]. PROGRAM ugadka (input,output); var x,z: Integer; ugadal: Boolean; BEGIN Writeln ('Угадай число'); Writeln ('от 1 до 100'); z:=Random (100); ugadal:=FALSE; while not ugadal do begin Write ('число='); Read(x); if x=z then begin ugadal:=TRUE; Writeln('Правильно') end else if xz then writeln('Много...') end; Writeln ('Молодец, умница!') END. П р и м е р 6. Bычисление целой части корня квадратного из поло- ────────────── жительного числа. Идея алгоритма заключается в том, что сумма К первых нечетных чисел равна К2, например 1 + 3 = 22, 1 + 3 + 5 = 32, ... PROGRAM inroot (input,output); var i,j: Integer; x: Real; { Входной параметр } BEGIN Readln(x); i:=-1; j:=0; while j<=x do begin i:=i+2; j:=j+i end; Write ((i-1) div 2) END. П р и м е р 7. ────────────── Написать программу приближенного вычисления суммы ряда 1 + x/1! + x2/2! + ■■■ По условию задачи считается, что нужное приближение получено, если вычис- лена сумма нескольких первых слагаемых, и очередное слагаемое оказалось по модулю меньше, чем данное малое положительное число E - это и все по- следующие слагаемые уже не надо учитывать. PROGRAM Summa (input,output); var X : real; { Аргумент } Eps : real; { "Точность" } i,y : real; S : real; { Искомый результат } BEGIN write('Введите значение аргумента: '); read(x); writeln; write('Введите значение точности : '); read(Eps); writeln; S:=0; y:=1; i:=0; while abs(y)>=Eps do begin S:=S+y; i:=i+1; y:=y*x/i end; writeln('Получите результат: '); writeln(S); writeln('А вот "точное" решение: '); writeln(exp(x)) END. Обычно процесс суммирования заканчивается в зависимости от величины членов ряда относительно общей суммы, а не от абсолютной величины послед- него члена ряда. Это требует, однако, дальнейшего исследования сходимос- ти ряда, особенно если ряд знакопеременный [Вирт Н.]. К сожалению, найти ... разложение числа N на простые множители или определить, является ли N простым числом, совсем не просто. Д.Кнут П р и м е р 8. Разложение целого числа на простые множители. ─────────────── PROGRAM razlovenie(input,output); var x,m: Integer; BEGIN read(x); m:=2; writeln('Разложение числа',x,'на простые множители'); while m<=x do if (x mod m)=0 then begin write(' ',m:1); x:=x div m end else m:=m+1; writeln END. Чем раньше начнешь писать программу, тем длиннее она получится. Афоризм П р и м е р 9. Вычисление квадратного корня из положительного числа ────────────── итерационным методом Ньютона. PROGRAM Koren (input,output); var tek: (* tek - текущее приближение к корню; *) pred: Real; (* pred - предыдущее приближение *) koren,A: Real; BEGIN write('Введите значение, из которого извлекается корень... '); readln(A); tek := A+1; Repeat pred:=tek; tek:=0.5 * (pred + A/pred); Until tek>=pred; Koren := pred; writeln('Значение корня...',Koren) end. П р и м е р 10. ──────────────── Рассмотрим задачу умножения двух многочленов: n n-1 P(x)=a ■x + a ■ x +■■■+ a ■x + a n n-1 1 0 m m-1 Q(x)=b ■x + b ■x +...+ b ■x + b m m-1 1 0 Поскольку многочлен определяется набором своих коэффициентов, то нашу задачу можно сформулировать так: даны два набора коэффициентов (а ,a ,...,a ) и (b ,b ,...,b) многочленов P(x) и Q(x) соот- 0 1 n 0 1 m ветственно. Требуется найти (вычислить) коэффициенты (c ,c ,...,c ) мно- 0 1 n+m гочлена R(x)=P(x)■Q(x). Нам понадобиться формула, по которой вычисляются коэффициенты произ- ведения двух многочленов: C = Е a ■b k { i+j=k } i j { 00, и одновременно i=k-j>k-m. Отсюда max(0,k-m) < i < min(k,n). Учитывая эти оценки, получаем формулу для вычисления C : min(k,n) k C = Е a ■ b k i=max(0,k-m) i k-i Коэффициенты многочлена P и Q целесообразно вывести для того, чтобы убе- диться в отсутствии ошибок ввода. Следует подчеркнуть, что в хороших алго- ритмах всегда предусмотрен вывод исходных данных с целью контроля правиль- ности ввода . PROGRAM proizwedenie (input,output); const MAX = 10; MAXP = 20; var M,N,K,K1,K2,j: Integer; A,B: Array [0..MAX] of Real; C: Array [0..MAXP] of Real; S: Real; BEGIN Writeln('Программа умножения двух многочленов'); Writeln('Введите степени многочленов'); readln(N,M); Writeln('Введите коэффициенты первого многочлена'); for j:=0 to N do Readln (A[j]); Writeln; Writeln('Введите коэффициенты второго многочлена'); for j:=0 to M do Readln (B[j]); Writeln; K:=0; while K<=N+M do begin K1:=K-M; if K1<0 then K1:=0; K2:=K; if K2>N then K2:=N; S:=0; j:=K1; while j<=K2 do begin S:=S+A[j]*B[K-j]; j:=j+1 end; C[K]:=S; K:=K+1; end; writeln('Коэффициенты произведения двух многочленов, имеющих коэффициенты...'); for j:=0 to N do Write (A[j]:6:2,' '); Writeln; for j:=0 to m do Write (B[j]:6:2,' '); Writeln; Writeln(' равны... '); for j:=0 to N+M do Write (C[j]:6:2,' '); Writeln END. П р и м е р 11 [Перминов, Справочник]. ─────────────────────────────────────── Прочитать последовательность цифр и преобразовать ее к виду целого числа. PROGRAM RdrInt (input,output); var ch: Char; i,j: Integer; BEGIN Read (ch); i:=0; while (ch>='0') and (ch<='9') do begin j:=Ord(ch) - Ord('0'); i:=10*i+j; Read(ch) end; Writeln (' Введено число... ',i) END. В данной программе мы воспользовались тем, что, Ord('0') не есть нуль. Но порядковый номер '0' на единицу меньше порядкового номера '1'; порядко- вый номер '1' на единицу меньше порядкового номера '2' и т.д. Следователь- но, Ord(ch)-Ord('0') дает целое значение, в точности соответствующее вве- денной цифре. Наоборот, пусть задано целое число I, лежащее между 0 и 9 включительно. тогда Chr(Ord('0')+I) - это цифра, соответствующая заданному целому числу I. П р и м е р 12 [ ]. Программа, находящая приближенное значение числа ─────────────── п с помощью представления числа 2/п в виде бесконечно- го произведения корней v1/2 ■ v(1/2+1/2■v1/2) ■ v(1/2+1/2■v(1/2+1/2■v1/2))X■■■ Итерация обрывается, когда два следующих друг за другом приближения для п отличаются менее, чем на 1.e-10. Заметим, что данное произведение корней впервые встречается у Франсуа Виета (1540-1603) в "Variorum de rebus mathematicis responsorum", liber VIII, 1593. PROGRAM PI(input,output); var Resultat,Koren,StartPi,NowPi: Real; i: Integer; BEGIN Resultat:=1; Koren:=0; NowPi:=0; StartPi:=5; i:=0; while Abs(StartPi-NowPi)>1.E-10 do begin i:=i+1; Koren:= Sqrt((1+Koren)/2); Resultat:=Resultat*Koren; StartPi:=NowPi; NowPi:=2/Resultat; Writeln (NowPi) end; Writeln ('Проверка... ',Pi) { Pi содержит "машинное" значение п } END. Running 2.8284271247E+00 3.0614674589E+00 3.1214451523E+00 ■■■ ў──── 13 итераций 3.1415926536E+00 3.1415926536E+00 Проверка... 3.1415926536E+00 > Наконец, отметим, что цикл while (как говорят,цикл с п р е д у с л о - в и е м) используется, как правило, в тех случаях, когда заранее неизвест- но число повторений цикла. В связи с этим следует помнить простое, но очень важное правило - "самая внутренняя" циклическая инструкция должна формулироваться с особой тщательностью с тем, чтобы по возможности минимизировать затраты на вы- числения и повысить эффективность программы [Вирт Н]. Одно из о с н о в н ы х п р а в и л программирования: Если выражение f(x) вычисляется внутри повторяемой конструкции S и если аргумент x не изменяется в цикле, то следует ввести вспомогательную переменную h, которой один раз перед выполнением S присваивается значе- ние f(x) и которая подставляется вместо f(x) внутри S, т.е. конструкция while P do begin ... f(x) ... end заменяется на h:=f(x); while P do begin ... h ... end [Вирт Н]. Когда вы убедитесь, что теорема верна, вы начинаете ее доказывать. Традиционный профессор математики (персонаж анекдотов о математиках) О б и н в а р и а н т а х ц и к л о в [ ] Инвариант - то, что не меняется. Инвариант цикла - это соотношение меж- ду значениями переменных, которое остается справедливым при любом прохож- дении цикла. В последующем изложении нам хотелось бы подчеркнуть ту мысль, что инварианты циклов помогают не только доказывать правильность программ, но и разрабатывать программы. Если думать над доказательством правильности уже при разработке программ, они получатся яснее, изящнее и зачастую эф- фективнее, чем программы, написанные бессистемно. Рассмотрим в качестве примера процесс разработки программы возведения целого числа в натуральную степень. Через M обозначим основание степени, через N - показатель, через P - переменную,в которой будем накапливать ре- зультат. В конце программы требуется обеспечить истинность соотношения N P=M (1) Мы должны стремиться к этой цели посредством некоторого цикла. Для вы- бора соотношения, остающегося истинным в цикле,требуется придумать обобще- ние соотношения (1). Такое "придумывание" - процесс творческий,тут трудно дать готовый рецепт. Полезный прием - введение в соотношение типа (1) но- вых переменных. помогает выбору обобщения тот факт, что справедливость об- общенного соотношения должна обеспечиваться и до входа в цикл.Простота на- чальных установок, обеспечивающих истинность инварианта,- веское соображе- ние в пользу того, что инвариант выбран правильно. В рассматриваемой зада- че будем поддерживать истинность соотношения N1 N P*M1 =M (2) Начальные установки, делающие соотношение (2) истинным, записываются оче- видным образом: P:=1; M1:=M; N1:=N Чтобы из (2) следовало (1), второй сомножитель в (2) должен равняться еди- нице. Этого можно добиться, сделав N1 равным нулю. Таким образом, из сооб- ражений правильности получаем условие выхода из цикла и следующий набро- сок программы: P:=1; M1:=M; N1:=N; WHILE N1<>0 DO BEGIN ТЕЛО ЦИКЛА (* P*(M1 В СТЕПЕНИN1)=(M В СТЕПЕНИ N) *) END (* P= (M В СТЕПЕНИ N) *) Чтобы условие N1 стало ложным, в теле цикла N1 надо уменьшать. При этом множитель (М1 в степени N1) будет уменьшаться. Чтобы соотношение (2) оста- валось истинным, Р надо во столько же раз увеличивать. В результате полу- чаем следующую программу: PROGRAM СТЕПЕНЬ (input,output); var M,N,M1,N1,P: Integer; BEGIN Read (M,N); Write (' ',M,' в степени ',N,' = '); P:=1; M1:=M; N1:=N; while N1<>0 do begin N1:=N1-1; P:=P*M1 (* P■(M1 в степени N1)=(M в степени N) *) end; (* P = (M в степени N) *) Writeln (P) END. Чтобы доказать, что соотношение (2) является инвариантом цикла, проще всего воспользоваться методом математической индукции.Основанием индукции служит состояние перед входом в цикл, индуктивный шаг очевиден. Для доказательства того, что работа программы завершиться после конеч- ного числа повторений цикла, воспользуемся фактом, который примем на веру: убывающая последовательность натуральных чисел не может быть бесконечной. В нашей программе значение N1 уменьшается на единицу при каждом повторении цикла, поэтому условие N1<>0 в конце концов станет ложным. Подчеркнем,что мы доказали два утверждения (в предположении корректнос- ти исходных данных и отсутствия переполнения). 1. Если программа завершится, значением переменной Р станет М в степе- ни N. 2. Работа программы завершится после конечного числа повторений цикла. С математической точки зрения наша программа правильна. Полезно, одна- ко, посмотреть на нее с другой, инженерной точки зрения, проанализировать характеристики созданного изделия. Оценим число умножений, которое требу- ется программе. В данном случае легко видеть, что умножений будет N.Число умножений можно уменьшить, если выбрать более эффективный, чем вычитание единицы, способ уменьшения N1. Попытаемся изменить форму представления ве- N1 личины M1 , воспользовавшись соотношением 2B B A =(A*A) (3) Применение соотношения (3) сразу уменьшит показатель степени в два раза. Ясно, что это эффективнее, чем вычитание единицы. Соотношение (3) можно применять, пока значение N1 четно.Вставим в нашу программу перед инструкцией N1:=N1-1 цикл уменьшения N1 в два раза с одно- временным возведением М1 в квадрат. Программа примет следующий вид: PROGRAM СТЕПЕНЬ(INPUT,OUTPUT); VAR M,N,M1,N1,P :INTEGER; BEGIN Read (M,N); Write (' ',M,' в степени ',N,' = '); P:=1; M1:=M; N1:=N; while N1<>0 do begin while N1 MOD 2=0 do begin N1:=N1 DIV 2; M1:=M1*M1 end; N1:=N1-1; P:=P*M1 (* P■(M1 в степени N1)=(M в степени N) *) end; (* P = (M в степени N) *) Writeln (P) END. По отношению к новой программе достаточно доказать конечность числа по- вторений вставленного цикла. Единственное целое число, которое можно до бесконечности делить пополам, получая четный результат, есть нуль.Но усло- вие внешнего цикла N1<>0 гарантирует завершимость цикла внутреннего. Проследив поведение нового варианта программы при N=15 и N=16, можно подсчитать, что в первом случае требуется семь умножений, а вот втором - пять. Таким образом, вставив цикл деления N1 пополам, мы получили сущест- венный выигрыш в быстродействии. 2.2.3.2 О п е р а т о р ц и к л а REPEAT ────────────────────────────────── Цикл repeat (цикл с постусловием), как правило, используется в тех случаях, когда заранее неизвестно число повторений цикла. Общий вид цикла: ┌─────────────────────────────┐ │ repeat ST until A; │ └─────────────────────────────┘ где: 1) ST - группа выполняемых операторов; 2) А - булевское выражение. В операторах ц и к л а точка с запятой никогда не ставится после заре- зервированного слова repeat. "Работает" оператор так: выполняются операторы ST, вычисляется значение выражения А; если его значение FALSE, то вновь выполняются операторы ST, если значение выражения А - TRUE, то цикл заканчивается. Если же значение выражения А - "истина" с самого начала, то операторы ST выполняются один раз. Заметим, что если выражение А никогда не принимает значение TRUE, то группа операторов ST выполняется бесконечное число раз,происходит "зацик- ливание". Следует подчеркнуть, что нижняя граница операторов циклической части четко обозначена словом until, поэтому нет необходимости заключать опе- раторы циклической части в операторные скобки begin и end. В то же вре- мя и наличие операторных скобок не будет являться ошибкой! П р и м е р 1. Вычислить сумму S=1+1/2+1/3+...+1/50, используя ─────────────── оператор repeat. PROGRAM N2 (output); var S: real; N: integer; BEGIN S:=0; N:=1; repeat S:=S+1/N; N:=N+1 until N>50; Writeln (' S=',S) END. П р и м е р 2. Калькулятор. ─────────────── PROGRAM ex4b(input,output); var operator: Char; answer,n: Real; BEGIN answer:=0; operator:='+'; repeat Readln (n); case operator of '+': answer:=answer+n; '-': answer:=answer-n; '*': answer:=answer*n; '/': answer:=answer/n end; Readln (operator) until operator='='; Writeln ('answer is ',answer) END. Если в циклической части встречается оператор перехода, указывающий на метку за пределами цикла, то цикл может завершится до его естественного окончания. Установим теперь взаимосвязь операторов while и repeat. Оператор: while B do S; где В - логическое выражение, а S - оператор, эквивалентен оператору: if B then repeat S until Not B 2.2.3.3. О п е р а т о р ц и к л а FOR ─────────────────────────────── Оператор цикла с параметром используется в тех случаях, когда заранее известно, сколькВ раз должна повторяться циклическая часть программы. Общий вид оператора: ┌──────────────────────────────────┐ │ FOR I:=N1 TO N2 DO ST; │ , └──────────────────────────────────┘ где: 1) 2) I - переменная (параметр) цикла; 3) N1 - начальное значение переменной цикла; 4) N2 - конечное значение; 5) ST - оператор (простой либо составной). I, N1, N2 должны быть одного и того же скалярного типа, но не типа REAL! Переменная I принимает п о с л е д о в а т е л ь н ы е значения данного типа от N1 до N2. В частном случае, когда N1 и N2 - целые числа, а I - целая переменная, то шаг всегда равен е д и н и ц е . Например: FOR I:=1 TO 20 DO A:=A+1; Для I= 1,2,3,...,20 будет выполняться оператор А:=А+1; П р и м е р. Вычислить сумму S=1+1/2+1/3+...+1/50 ─────────── PROGRAM N3(output); var i:integer; s:real; BEGIN S:=0; for i:=1 to 50 do s:=s+1/i; writeln(' S=',s) END. П р и м е р. ─────────── PROGRAM simple (input,output); var m,n,i,j,kl:integer; BEGIN readln(m,n); writeln('простые числа в диапазоне от ',m,' do ',n); for i:=m to n do begin kl:=0; for j:=2 to round(sqrt(i)) do if (i mod j)=0 then kl:=kl+1; if kl=0 then writeln(i); end END. П р и м е р. N рыбаков легли спать, не разделив добычи. Проснувшийся ─────────── первым решил взять свою долю и уйти. Но число рыб не дели- лось на N, и для этого он выбросил одну,а от остатка забрал 1/N-ую часть. Остальные сделали то же самое. Найти число рыб в заданном интервале, до- пускающее такую операцию. program rybak(input,output); label 1,2; var a,a1,i,n,b,j:integer; r:array[1..20]of integer; begin write('Количество рыбаков n= ');readln(n); write('Макс. колич. рыб (не меньше n) = ');readln(a); for a1:=n+1 to a do begin b:=a1; for i:=1 to n do begin j:=(b-1) mod n; if j=0 then goto 1 else goto 2; 1: r[i]:=(b-1) div n; b:=b-r[i]-1; end; writeln(a1); 2: end; write('Это все !'); end. { Т е с т о в ы е п р и м е р ы : n=3; a=... 79, 52, 25,, -2, -29, -56, -83, -110, -137, ...; n=4; a=... 765, 509, 253, -3, -259, -515, ... ; n=5; a=... 9371, 6246, 3121, -4, -3129, -6254, -9379 ...;} Если же N1 и N2 символьного типа - например,соответственно, имеют зна чения A и Z, то переменная I принимает последовательные значения в поряд- ке алфавита: А, В, С, ... , Z. Если N1 и N2 типа COLOR = (RED, YELLOW, GREEN, BLUE), например RED и GREEN соответственно, то переменная I принимает значения RED, YELLOW, GREEN. Цикл по убывающим значениям параметра I от N2 до N1 имеет вид: ┌─────────────────────────────────────┐ │ for I:=N2 downto N1 do ST; │ └─────────────────────────────────────┘ В этом случае параметр I принимает последовательные убывающие значения данного типа от N2 до N1. Например: for I:=20 downto 1 do A:=A+1; З а м е ч а н и я. 1. Внутри цикла нельзя менять ни начальное, ни конечное значения пере- менной цикла, ни само значение переменной - параметра цикла. П р и м е р. Посмотрите, к чему приводит несоблюдение этого правила... ─────────── PROGRAM a(input,output); var i: integer; S: integer; k: integer; BEGIN S:=0; Write('Задайте количество повторений тела цикла... '); Readln(k); for i:=1 to k do begin S:=S+i; write('S = ',S,'...'); i:=i+1 ; write(i,'...') { Внимание! Перед Вами запрещенная } end; { операция: изменение значения па- } writeln; { раметра цикла в теле цикла. } writeln('Ваша сумма... ',S); writeln('Обратите внимание на то, что ',S,' = ',k,'*',k) END. 2. Если в цикле по возрастающим значениям переменной начальное значе- ние больше конечного, то цикл не выполняется ни разу (аналогично - для цикла со служебным словом downto, если начальное значение меньше конечного). 3. После выполнения оператора FOR значение управляющей переменной рав- но конечному значению, за исключением случая, когда цикл вообще не выпол- нялся. В этом случае присвоение значения управляющей переменной не выпол- няется (ее значение будет п р о и з в о л ь н ы м !). Наконец, отметим,что циклы могут быть вложены один в другой (вложенные циклы).При использовании вложенных циклов необходимо составлять программу таким образом, чтобы в н у т р е н н и й цикл полностью укладывался в циклическую часть в н е ш н е г о цикла. Внутренний цикл может также содержать в себе другой внутренний цикл. П р и м е р. Подсчитайте,сколько палиндромических чисел лежат в проме- ─────────── жутке от 100 до 1000 (число M называется п а л и н - д р о м и ч е с к и м , если оно равно своему обращенному). PROGRAM PALINDROM (input,output); var d,x,y,z,n,m:integer; BEGIN d:=0; for x:=1 to 9 do for y:=0 to 9 do for z:=0 to 9 do begin n:=100*x+10*y+z;m:=100*z+10*y+x; if n=m then begin d:=d+1; Write(n,' ') end; end; Writeln; Writeln('d=',d) END. П р и м е р. Точное вычисление факториалов "огромных" чисел. ─────────── Перед Вами факториалы нескольких "не очень больших" чисел: 10!=3628800 13!=6227020800 17!=3.55687428096Е+14. Уже факториал 17 компьютер выводит в экспоненциальной форме, т.е.точ- ность нарушается, если же составить программу, которая бы вычисляла факториал поразрядно, а ответы "склеивала" и выдавала на экран, то факториал 17 выглядел бы в таком виде: 355687428096000 П р и м е р. Нужно посчитать факториал 5 (мы знаем, что факториал 4 равен 24), мы сначала умножаем 5 на младшиЙ разряд равныЙ 4,а потом на старшиЙ равныЙ 2 * 10: 4 (R2) 2 (R1) * 5 * 5 ─────── ────── ┌─────┐ 2 0 + 1 0 * 1 0 =│ 120 │ └─────┘ В программе старшиЙ и младшиЙ разряды - R1 и R2 , поочередно умно- жаются на число (в данном случае 5), а результата заносится в массив А. В конце программы выдаются результаты этих умножениЙ, принимаемые нами как одно число. Итак, приведем программу вычисления факториала "огромного" целого числа. PROGRAM faktorial (input,output); label metka,wozwrat; var a:array[1..100] of real; i,k,l,j,n:integer; r,r1,r2:real; BEGIN n:=0; Writeln('Введите натуральное число (можно "огромное")');readln(n); for i:=2 to 100 do a[i]:=0; a[1]:=1;l:=1; for k:=1 to n do begin r2:=0; r1:=0; i:=1; wozwrat: if r2=0 then if i>l then goto metka; r := a[i]*k+r2; r2 := Int(r/10000); r1 := r-r2*10000; a[i]:=r1; i:=i+1; goto wozwrat; metka: l:=i-1; end; for j:=1 to i-1 do begin write (Trunc(a[i-j]),' ') end END. Привести результаты счета! Отметим,что для в с е х операторов цикла характерна следующая особен- ность. Повторяющиеся вычисления записываются всего лишь один раз. Вход в цикл возможен только через его начало. Переменные оператора цикла должны быть определены до входа в циклическую часть. Необходимо предусмотреть вы- ход из цикла: или по естественному его окончанию, или по оператору перехо- да. Если этого не предусмотреть, то циклические вычисления будут повторять- ся бесконечно. В этом случае говорят, что произошло "з а ц и к л и в а - н и е" выполнения программы. Большинство опытных программистов, пишущих на Паскале, считают, что продуманное распо- ложение текстов программ является важным ас- пектом квалифицированного программирования. Г.Джонстон Р а с п о л о ж е н и е о п е р а т о р о в в п р о г р а м м е ────────────────────────────────────────────────────────────────── С точки зрения компьютера совершенно безразлично, как написана програм- ма. Важно лишь, чтобы она была правильной. Однако, часто возникает ситуа- ция, когда есть программа для решения крайне необходимой задачи, но понять, как она работает, в какой последовательности задавать исходные данные и ка- кие, совершенно невозможно. Во избежание подобных ситуаций следует писать программы так, чтобы сделать их максимально понятными. При написании про- грамм желательно соблюдать следующие правила форматирования: 1. С о с т а в н о й о п е р а т о р BEGIN S1;S2;...;Sn END; где S1,S2,...,Sn-операторы 2. У с л о в н ы й о п е р а т о р IF B THEN S1 ELSE S2; где В - логическое выражение, S1 и S2-операторы. Если S1 и S2 составные операторы, то необходимо использовать такое рас- положение: IF B THEN S1 ELSE S2; 3. О п е р а т о р ы ц и к л а WHILE B DO S1; Если S1 - составной оператор, то используется такое расположение: WHILE B DO BEGIN S1;S2;...;Sn END;(*while*); При этом дается примечание к зарезервированному слову END. Оператор REPEAT располагается так: REPEAT S1;S2;...;Sn UNTIL B; Оператор FOR распологается следующим образом: FOR I:=ВЫР1 ТО ВЫР2 DO S1; где ВЫР1 и ВЫР2-выражения. 4. О п е р а т о р в ы б о р а CASE C OF A1: S1; A2: S2; ■■■ An: Sn END (*case*); Здесь С - переключатель, а А1 и А2,..., Аn - метки выбора. 5. Зарезервированные слова PROGRAM, LABEL, CONST, TYPE, VAR, PROCEDURE, FUNCTION записываются в самой левой позиции. При использовании вложенных процедур и функций соответствующие им слова PROCEDURE и FUNCTION записыва- ются правее. Соответственно сдвигаются вправо и тела вложенных процедур и функций. Итак, при написании программ вложенную конструкцию рекомендуется распологать правее объемлющей ее конструкции. В заключение отметим,что все сказанное о расположении операторов в про- грамме носит рекомендательный характер. Безусловно, этим рекомендациям же- лательно следовать, поскольку они обобщают уже довольно большой опыт не одного поколения программистов. Вместе с тем к ним не следует обращаться как к догмам! И наконец, интересные мысли Д.Гриса, приведенные им в книге "Наука про- граммирования", М.:Мир,1984. "Приведем некоторые простые правила для отступов и выравнивания,ко- торые могут использоваться в самых распространенных языках программи- рования. В этих правилах могут быть небольшие отличия для разных язы- ков, но в главных чертах они остаются одними и теми же. 1) Последовательные команды могут располагаться на одной строке,ес- ли они логически составляют единое целое. 2) Команды последовательности, начинающиеся на разных строках,долж- ны начинаться в одном и том же столбце. 3) Начинайте продолжение команды на три-четыре колонки правее той, где начинается сама команда (если это разумно, то можно отступать и на большее число колонок). 4) Существуют три соглашения о выравнивании ограничителя, завершаю- щего команду (например, END;). a) ограничитель помещают на отдельной строке с той же колонки, что и начало команды; b) ограничитель помещается там же, где и подкоманды данной коман- ды; c) ограничитель полностью помещается на последней строке команды. Пользуйтесь последовательно тем соглашением о выравнивании заверша- ющих ограничителей, которое Вами принято! 5) Команда-комментарий помещается там же, где помещалась бы на ее месте любая другая команда. Следующее за ней ее уточнение сдвигается на три-четыре столбца вправо. 6) Заголовок процедуры, включающий список параметров, их специфика- ции и описание того, что делает процедура, должен размещаться там же, где размещалась бы любая другая команда в данном контексте. Тело про- цедуры сдвигается на три-четыре столбца вправо по отношению к заголов- ку. Может иметь смысл пропустить по строчке до и после описания проце- дуры, чтобы отделить его от окружающего текста."