Функциональные клавиши
Функциональные клавиши используются для управления средой Турбо Паскаля. Они обозначаются F1, F2, ..., F12 и располагаются в самом верхнем ряду клавиатуры. С каждой из этих клавиш связывается некоторая команда меню. Действие почти всех функциональных клавиш можно модифицировать тремя особыми клавишами: Alt (от ALTernative - дополнительный), Ctrl (ConTRoL -управляющий) и Shift (SHIFT- сдвиговый). Эти клавиши используются подобно клавиши временной смены регистра на пишущей машинке: нужно нажать на одну из них и затем, не отпуская ее, нажать функциональную клавишу. В дальнейшем такое совместное нажатие двух клавиш будем обозначать чертой. Например, Alt-F3 означает, что вместе с клавишей Alt необходимо нажать клавишу F3, Ctrl-F9 - вместе с Ctrl нажимается F9 и т.д.
Ниже приводятся команды, которые передаются среде Турбо Паскаля функциональными клавишами и некоторыми их комбинациями с клавишами Ctrl и Alt:
Fl - обратиться за справкой к встроенной справочной службе (Help-помощь);
F2 - записать редактируемый текст в дисковый файл;
F3 - прочитать текст из дискового файла в окно редактора;
F4 - используется в отладочном режиме: начать или продолжить исполнение программы и остановиться перед исполнением той ее строки, на которой стоит курсор;
F5 - распахнуть активное окно на весь экран;
F6 - сделать активным следующее окно;
F7 - используется в отладочном режиме: выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), войти в эту процедуру и остановиться перед исполнением первого ее оператора;
F8- используется в отладочном режиме: выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), исполнить ее и не прослеживать ее работу;
F9 - компилировать программу, но не выполнять ее;
F10 - перейти к диалоговому выбору режима работы с помощью главного меню;
Ctrl-F9 - выполнить прогон программы: компилировать программу, находящуюся в редакторе, загрузить ее в оперативную память и выполнить, после чего вернуться в среду Турбо Паскаля.
Alt-F5 - сменить окно редактора на окно вывода результатов работы (прогона) программы.
Полное описание функциональны клавиш содержится в прил.1, а сейчас - самый краткий комментарий.
Во-первых, Вам понадобятся команды Ctrl-F9 для проверки работы Вашей программы и Alt-X - для выхода из Турбо Паскаля. Клавиши F2 и F3 помогут Вам в работе с Вашим каталогом. Командой Alt-F5 Вы в любой момент сможете просмотреть данные, выданные на экран в результате прогона программы.
Как начать работу с Турбо Паскалем
Система Турбо Паскаль довольна значительна по объему. Она поставляется на нескольких дистрибутивных дискетах и устанавливается на жесткий диск. При развертывании системы на жестком диске обычно создается каталог с именем ТР (или PAS, TURBOPAS, PASCAL и т.п.), в который помещаются все файлы с дистрибутивных дискет. Для вызова Турбо Паскаля необходимо отыскать в древовидной структуре каталогов ПК этот каталог и в нем файл TURBO.EXE. Этот файл содержит готовую к работе диалоговую систему программирования Турбо Паскаль. В него входят минимально необходимые части Турбо Паскаля (текстовый редактор, компилятор, компоновщик, загрузчик). Для нормальной работы в диалоговой среде понадобятся также основная библиотека, располагающаяся в файле TURBO. TPL, и справочная служба (файл TURBO.HLP). В принципе, этих файлов достаточно для написания, компиляции и исполнения большинства примеров, содержащихся в этой книге.
Пусть перечисленные файлы располагаются в каталоге ТР на диске D. Тогда для вызова Турбо Паскаля следует дать команду:
D:\TP\TURBO
По этой команде операционная система MS-DOS поставит на исполнение программу из файла TURBO.EXE: загрузит программу в оперативную память и передаст ей управление.
Не рекомендуется работать с системой, назначив в качестве каталога по умолчанию (текущего каталога) тот, в котором хранятся перечисленные выше файлы (этот каталог будем называть системным). Во-первых, в таком случае можно ошибочно стереть какой-либо из файлов системы программирования и тем самым нарушить ее работоспособность, а во-вторых, этот каталог очень скоро заполнится другими файлами, прямо не относящимися к Турбо Паскалю. Существует и еще одна причина, по которой нежелательно работать в системном каталоге. Дело в том, что Турбо Паскаль имеет свойство запоминать свою настройку в двух файлах с именами TURBO. TP и TURBO.PCK. При вызове система начинает поиск этих файлов в текущем каталоге. Если этот каталог - Ваш индивидуальный, система всякий раз будет настраиваться так, как Вы этого хотите. Если эти файлы не обнаружены в Вашем каталог (а при первом обращении к Турбо Паскалю так оно и будет), система продолжит поиск в системном каталоге, а не найдя их там, настроится стандартным образом. Впоследствии можно сохранить настроечные файлы в своем каталоге и тем самым избавить себя от необходимости перенастройки системы всякий раз при обращении к ней.
После успешного вызова системы экран ПК приобретает вид, показанный на рис. 1.1.
Рис. 1.1. Вид экрана после вызова Турбо Паскаля
Сразу же скажем, что для выхода из Турбо Паскаля следует нажать клавишу Alt и, не отпуская ее, - клавишу с латинской буквой X, после чего можно отпустить обе клавиши.
Верхняя строка содержит «меню» возможных режимов работы Турбо Паскаля, нижняя - краткую справку о назначении основных функциональных клавиш. Вся остальная часть экрана принадлежит окну редактора, очерченному двойной рамкой и предназначенному для ввода и коррекции текстов программ. В его верхней строке приводятся имя того дискового файла, откуда был прочитан текст программы (новому файлу присваивается имя NONAME00.PAS), два специальных поля, используемых при работе с устройством ввода «мышь» (эти поля выделены квадратными скобками), и цифра 1 - номер окна. В Турбо Паскале можно работать одновременно с несколькими программами (или частями одной крупной программы), каждая из которых может располагаться в отдельном окне редактора. Среда позволяет использовать до 9-ти окон редактора одновременно.
Кроме окна (окон) редактора в Турбо Паскале используются также окна отладочного режима, вывода результатов работы программы, справочной службы, стека, регистров. По желанию они могут вызываться на экран поочередно или присутствовать на нем одновременно.
Знакомство со средой
Глава 1. Знакомство со средой Турбо Паскаля
1.1 Как начать работу с Турбо Паскалем
1.2 Функциональные клавиши
1.3 Текстовый редактор
1.4 Основные приемы работы в среде Турбо Паскаля
1.4.1 Работа с файлами
1.4.2 Прогон и отладка программы
1.4.3 Справочная служба Турбо Паскаля
Основные приемы работы в среде Турбо Паскаля Работа с файлами
Как уже говорилось, сразу после запуска Турбо Паскаля среда переходит в режим редактирования текста, в котором можно подготовить новую программу или исправить существующую.
Основной формой хранения текстов программ вне среды являются файлы. После завершения работы с Турбо Паскалем можно сохранить текст новой программы в дисковом файле с тем, чтобы использовать его в следующий раз. Для обмена данными между дисковыми файлами и редактором среды предназначены клавиши F2 (запись в файл) и F3 (чтение из файла). Если Вы создаете новую программу, то среда еще не знает имя того файла, в который Вы захотите поместить текст этой программы, и поэтому она присваивает ей стандартное имя NONAME00.PAS
(NO NAME - нет имени). Для сохранения текста программы в файле нужно нажать F2. В этот момент среда проверит имя программы и, если это стандартное имя NONAME, спросит, нужно ли его изменять: на экране появится небольшое окно запроса с надписью
Save File as
(Сохранить в файле с именем...)
Ниже надписи располагается поле для ввода имени файла, в котором можно написать нужное имя и нажать Enter - текст будет сохранен в файле. Если в имени опущено расширение, среда присвоит файлу стандартное расширение PAS. Если Вы захотите завершить работу с Турбо Паскалем, а в редакторе остался не сохраненный в файле текст, на экране появится окно с запросом
NONAMEOO.PAS has been modified.Save?
(Файл NONAME00.PAS был изменен. Сохранить?)
В ответ следует нажать Y(Yes - да), если необходимо сохранить текст в файле, или N(No - нет), если сохранять текст не нужно.
Прогон и отладка программы
После подготовки текста программы можно попытаться исполнить ее, т.е. откомпилировать программу, связать ее (если это необходимо) с библиотекой стандартных процедур и функций, загрузить в оперативную память и передать ей управление. Вся эта последовательность действий называется прогоном программы и реализуется командой Ctrl-F9.
Если в программе нет синтаксических ошибок, то все действия выполняются последовательно одно за другим, при этом в небольшом окне сообщается о количестве откомпилированных строк и объеме доступной оперативной памяти. Перед передачей управления загруженной программе среда очищает экран (точнее, выводит на экран окно прогона программы), а после завершения работы программы вновь берет управление компьютером на себя и восстанавливает на экране окно редактора.
Если на каком-либо этапе среда обнаружит ошибку, она прекращает дальнейшие действия, восстанавливает окно редактора и помещает курсор на ту строку программы, при компиляции или исполнении которой обнаружена ошибка. При этом в верхней строке редактора появляется диагностическое сообщение о причине ошибки. Все это позволяет очень быстро отладить программу, т.е. устранить в ней синтаксические ошибки и убедиться в правильности ее работы. Если ошибка возникла на этапе прогона программы, простое указание того места, где она обнаружена, может не дать нужной информации, так как ошибка может быть следствием неправильной подготовки данных в предыдущих операторах программы. Например, если ошибка возникла в результате извлечения квадратного корня из отрицательного числа, будет указан оператор, в котором осуществляется извлечение корня, хотя ясно, что первопричину ошибки надо искать где-то раньше, там, где соответствующей переменной присваивается отрицательное значение. В таких ситуациях обычно прибегают к пошаговому исполнению программы с помощью команд, связанных с клавишами F4, F7 и F8. Пока еще не накоплен достаточный опыт отладки, можно воспользоваться одной клавишей F7, после нажатия на которую среда осуществит компиляцию, компоновку (связь с библиотекой стандартных процедур и функций) и загрузку программы, а затем остановит прогон перед исполнением первого оператора. Строка программы, содержащая этот оператор, будет выделена на экране указателем (цветом). Теперь каждое новое нажатие F7 будет вызывать исполнение всех операций, запрограммированных в текущей строке, и смещение указателя к следующей строке программы. В подозрительном месте программы можно просмотреть текущее значение переменной или выражения. Для этого нужно установить курсор в то место строки, где находится имя интересующей Вас переменной, и нажать Ctrl-F4. На экране появится диалоговое окно, состоящее из трех полей (в верхнем поле будет стоять имя переменной, два других поля будут пустыми). Нажмите Enter, чтобы в среднем поле получить текущее значение переменной. Если перед нажатием Ctrl-F4 курсор стоял на пустом участке строки или указывал на имя другой переменной, верхнее поле диалогового окна также будет пустым или содержать имя этой другой переменной. В этом случае следует ввести с помощью клавиатуры имя нужной переменной и нажать Enter. Кстати, таким образом можно вводить не только имена прослеживаемых переменных, но и выражения - среда вычислит и покажет значение введенного выражения.
Справочная служба Турбо Паскаля
Неотъемлемой составной частью среды Турбо Паскаля является встроенная справочная служба. Если Вы достаточно хорошо владеете английским языком, у Вас не будет проблем при работе с Турбо Паскалем: в затруднительной ситуации достаточно нажать F1 и на экране появится необходимая справка. Эта справка зависит от текущего состояния среды, поэтому справочную службу называют контекстно-чувствительной. Например, если нажать F1 в момент, когда среда обнаружила ошибку в программе, в справке будут сообщены дополнительные сведения о причинах ошибки и даны рекомендации по ее устранению.
Существуют четыре способа обращения к. справочной службе непосредственно из окна редактора:
F1 - получение контекстно-зависимой справки;
Shift-F1 - выбор справки из списка доступных справочных сообщений;
Ctrl-F1 - получение справки о нужной стандартной процедуре, функции, о стандартной константе или переменной;
Alt-F1 - получение предыдущей справку.
По команде Shift-F1 на экране появится окно, содержащее упорядоченный по алфавиту список стандартных процедур, функций, типов, констант и переменных, для которых можно получить справочную информацию.
Эту же справку можно получить и по-другому. Напечатайте на экране имя процедуры (функции, типа и т.д.) или подведите курсор к имеющемуся в тексте стандартному имени и нажмите Ctrl-F1. Среда проанализирует ближайшее окружение курсора, выделит имя и даст нужную справку.
Во многих случаях справка содержит небольшой пример, иллюстрирующий соответствующие возможности Турбо Паскаля. Не торопитесь запоминать его или записывать на листе бумаги: его можно «вырезать» из справки и перенести в окно редактора. Для этого после вызова справки нажмите Alt-E, выберите в появившемся дополнительном меню продолжение Copy examples (копировать примеры) и нажмите Enter -текст примера скопируется во внутренний буфер редактора. Для извлечения пример из буфера, нажмите Esc, чтобы выйти из справочной службы, подвести курсор к свободной строке в окне редактора, нажмите Shift-Insert (копирование содержимого буфера в текст программы) и Ctrl-K H, чтобы убрать выделение скопированного текста цветом.
Текстовый редактор
Текстовый редактор среды Турбо Паскаля предоставляет пользователю удобные средства создания и редактирования текстов программ. Признаком того, что среда находится в состоянии редактирования, является наличие в окне редактора курсора -небольшого мигающего прямоугольника. Режим редактирования автоматически устанавливается сразу после загрузки Турбо Паскаля. Из режима редактирования можно перейти к любому другому режиму работы Турбо Паскаля с помощью функциональных клавиш или выбора нужного режима из главного меню. Если среда находится в состоянии выбора из меню, курсор исчезает, а в строке меню появляется цветной указатель-прямоугольник, выделяющий одно из кодовых слов (опций меню). Для перехода от состояния выбора режима из главного меню в состояние редактирования нужно нажать клавишу Esc (ESCape - ускользать, убегать), а для перехода к выбору из главного меню - F10.
Рассмотрим основные приемы работы с текстовым редактором.
Для создания текста программы нужно ввести этот текст с помощью клавиатуры ПК подобно тому, как это делается при печатании текста на пишущей машинке. После заполнения очередной строки следует нажать на клавишу Enter, чтобы перевести курсор на следующую строку (курсор всегда показывает то место на экране, куда будет помещен очередной вводимый символ программы).
Окно редактора имитирует длинный и достаточно широкий лист бумаги, фрагмент которого виден в окне. Если курсор достиг нижнего края, осуществляется прокрутка окна редактора: его содержимое смещается вверх на одну строку и снизу появляется новая строка листа. Если курсор достиг правой границы экрана, окно начинает по мере ввода символов смещаться вправо, показывая правый край листа. Размеры листа по горизонтали и вертикали ограничиваются только общим числом символов в файле, которых не должно быть больше 64535, однако компилятор Турбо Паскаля воспринимает строки программы длиной не более 126 символов.
Окно можно смещать относительно листа с помощью следующих клавиш:
Page Up -на страницу вверх;
Page Down - на страницу вниз;
Ноте - в начало текущей строки;
End - в конец текущей строки;
Ctrl-Page Up -в начало текста;
Ctrl-Page Down - в конец текста.
Клавишами перевода курсора (эти клавши помечены стрелками и располагаются в правой части клавиатуры) его можно смещать по экрану. При достижении границ окна оно смещается на строку Или на символ.
Если Вы ошиблись при вводе очередного символа, его можно стереть с помощью клавиши со стрелкой (или надписью Backspace), расположенной над клавишей Enter. Клавиша Delete стирает символ, на который в данный момент указывает курсор, а команда Ctrl-Y- всю строку, на которой расположен курсор.
Следует помнить, что редактор Турбо Паскаля вставляет в конце каждой строки невидимый символ-разделитель. Этот символ вставляется клавишей Enter, а стирается клавишами Backspace или Delete. С помощью вставки/стирания разделителя можно «разрезать»/«склеить» строки. Чтобы разрезать строку, следует подвести курсор к нужному месту и нажать Enter, чтобы склеить соседние строки, нужно установить
курсор в конец первой строки (для этого удобно использовать клавишу End) и нажать Delete или установит курсор в начало второй строки (клавишей Ноте) и нажать Backspace.
Нормальный режим работы редактора - режим вставки, в котором каждый вновь вводимый символ как бы «раздвигает» текст на экране, смещая вправо остаток строки. Следует учитывать, что разрезание текста и последующая вставка пропущенных строк возможны только в этом режиме. Редактор может также работать в режиме наложения новых символов на существующий старый текст: в этом режиме новый символ заменяет собой тот символ, на который указывает курсор, а остаток строки не смещается вправо. Для перехода к режиму наложения нужно нажать клавишу Insert, а если нажать эту клавишу еще раз, вновь устанавливается режим вставки. Признаком того, в каком режиме работает редактор, является форма курсора: в режиме вставки он похож на мигающий символ подчеркивания, а в режиме наложения он представляет собой крупный мигающий прямоугольник, заслоняющий символ целиком.
И еще об одной возможности редактора. Обычно редактор работает в режиме автоотступа. В этом режиме каждая новая строка начинается в той же позиции на экране, что и предыдущая. Режим автоотступа поддерживает хороший стиль оформления текста программы: отступы от левого края выделяют тело условного или составного оператора и делают программу более наглядной. Отказаться от автоотступа можно командой Ctrl-O I (при нажатой Ctrl нажимается сначала клавиша с латинской буквой О, а затем О отпускается и нажимается I ), повторная команда Ctrl-O I восстановит режим автоотступа.
Ниже перечислены наиболее часто используемые команды редактор Турбо Паскаля.
Смешение курсора
Page Up -на страницу вверх;
Page Down - на страницу вниз;
Ноте - в начало текущей строки;
End - в конец текущей строки;
Ctrl-Page Up - в начало текста;
Ctrl-Page Down - в конец текста.
Команды редактирования
Backspace - стирает символ слева от курсора;
Delete - стирает символ, на который показывает курсор;
Ctrl-Y- стирает строку с курсором;
Enter - вставляет новую строку, разрезает старую;
Ctrl-Q L - восстанавливает измененную строку (действует, если курсор не
покидал строку после ее изменения).
Работа с блоком
Ctrl-K В - начинает выделение блока;
Ctrl-K K- заканчивает выделение блока;
Ctrl-K Y - уничтожает выделенный блок;
Ctrl-K С - копирует блок;
Ctrl-K V - перемещает блок на новое место;
Ctrl-K W - записывает блок в файл;
Ctrl-K R - читает блок из файла;
Ctrl-K P - печатает блок.
Биоритмы
Давно известно, что творческая и физическая активность человека не остается постоянной, циклически меняется, причем периодичность ее изменения приблизительно согласуется с периодом вращения Луны вокруг Земли. Существует теория, согласно которой физическая, эмоциональная и интеллектуальная активность человека подчиняется соответствующим биоритмам. Каждый биоритм представляет собой синусоиду со строго постоянным периодом, причем для каждого биоритма существует свой период. В отдельные дни все три биоритма человека могут достигнуть своего максимума и тогда человек испытывает подъем творческих и физических сил, в такие дни у него все спорится, он легко решает проблемы, которые в другое время ему решить гораздо сложнее. Точно также существуют и «черные» дни, соответствующие спаду всех трех биоритмов.
Используя уже опробованную методику нисходящего программирования, создадим программу, в которой запрашивается дата рождения человека и дата, для которой требуется оценить его состояние. Программа должна рассчитать и выдать на экран ближайшие к этой дате дни пика и спада биоритмов.
Алгоритм программы можно укрупнено записать следующим образом:
ввести дату рождения и текущую дату, проконтролировать их правильность и непротиворечивость;
вычислить количество дней между двумя датами, чтобы определить фазу синусоид для текущей даты;
вычислить количество дней от текущей даты до даты ближайшего пика биоритмов и даты ближайшего спада;
определить и напечатать обе даты.
Будем считать, что каждое из перечисленных действий реализуется в отдельной процедуре, тогда начальный вариант программы будет таким:
Procedure InputDates(var dO,mO,yO,d,m,y: Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения) }
begin {InputDates}
end; {InputDates}
{..........................}
Procedure Get_count_pf_days (dO,mO,yO,d,m,y: Integer;
var days: Integer);
{Определяет полное количество дней, прошедших от одной даты до другой}
begin {Get_count_of_days}
end; {Get_count_of_days}
{--------------------------}
Procedure FindMaxMin (var dmin,dmax: Integer; days: Integer);
{Ищет критические дни}
begin {FindMaxMin}
end; {FindMaxMin}
{--------------------------}
Procedure WriteDates ( dmin , dmax , days : Integer);
{ Определяет критические даты по количеству дней, прошедших от
момента рождения, и выдает эти даты на экран}
begin {WriteDates}
end; {WriteDates}
{--------------------------}
var
d0,d , {Дни рождения и текущий}
m0,m, {Месяцы рождения и текущий}
у0,у, {Годы рождения и текущий}
dmin, {Наименее благоприятный день}
dmax, {Наиболее благоприятный день}
days: Integer; {Количество дней от рождения}
begin {Главная программа}
Input-Dates (d0,m0,y0,d,m,y) ;
Get_numbers_of_days (d0,m0,y0,d,m,y,days) ;
FindMaxMin (dmin, dmax, days) ;
WriteDates (dmin, dmax, days)
end .
Начинаем детализацию программы. Прежде всего подумаем, как по двум датам вычислить разделяющее их количество дней? Если вспомнить, что следует учитывать неодинаковое количество дней по месяцам года, а также 29 февраля для високосных лет, то ответ на этот вопрос окажется не таким уж простым. Предлагаемый алгоритм подсчета количества дней заключается в вычислении количества дней от даты рождения до конца месяца, а затем и года рождения, количества дней, от начала текущего года до текущего месяца и текущей даты, а также - в подсчете количества полных лет, разделяющих обе даты. Количество лет затем легко пересчитывается в количество дней с учетом длины года (365 дней для обычных и 366 дней для високосных лет). Это очень прямолинейный алгоритм, но, откровенно говоря, мне не пришло в голову ничего другого. Возможно, существует более изящный способ подсчета и Вы его знаете, тогда программная реализация будет другой.
Упростить алгоритм можно за счет создания и использования массива из 12 целых чисел, содержащего количества дней по месяцам невисокосного года, т.е. 31, 28, 31, 30 и т.д. Этот массив (назовем его SIZE_OF_MONTH - длина _месяца) можно использовать и для обратной задачи, т.е. для определения даты критических дней, а также для проверки правильности вводимых дат. Таким образом, массив SIZE__OF_MONTH будет использоваться сразу в трех процедурах. Сделаем его глобальным, для чего его описание поместим перед описанием процедур:
const
Size_of_Month: array - [1. .12] of Byte =
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
{--------------------------}
Procedure InputDates (var d0,m0,y0,d,m,y: Integer);
.........
Поскольку описание массива размещается до описания процедур, он становится доступным внутри каждой из процедур и служит для них глобальным. В отличие от этого все константы и переменные, объявляемые внутри некоторой процедуры, являются локальными и могут использоваться только в этой процедуре.
С учетом сказанного напишем следующий начальный вариант программной реализации процедуры INPUTDATES:
Procedure InputDates(var d0,m0,y0,d,m,y: Integer);
{Вводит дату рождения и текущую дату. Контролирует правиль-
ность дат и их непротиворечивость (текущая дата должна быть
позже даты рождения)}
var
correctly: Boolean; {Признак правильного ввода}
begin {InputDates}
repeat
{Вводим и контролируем дату рождения d0,m0,y0.}
{Вводим и контролируем текущую дату d,m,y.}
{Проверяем непротиворечивость дат:}
correctly := у > у0; if not correctly and (у = y0) then
begin
correctly := m > m0;
if not correctly and (m = m0) then
correctly := d>=d0
end
until correctly
end; {InputDates}
В этой процедуре дважды выполняется одно и то же алгоритмическое действие (ввод и контроль даты). Это действие можно вынести в отдельную внутреннюю процедуру с именем INPDATE, тогда получим следующий окончательный вариант:
Procedure InputDates(var d0,m0,y0,d,m,y : Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения)}
var
correctly: Boolean; {Признак правильного ввода}
{--------------------------}
Procedure InpDate (text: String; var d,m,y: Integer);
{Выводит приглашение TEXT, вводит дату в формате ДД ММ ГГГГ и
проверяет ее правильность}
const
YMIN = 1800; {Минимальный правильный год}
YМАХ = 2000; {Максимальный правильный год}
begin {InpDate}
repeat
Write (text);
ReadLn (d,m,y) ;
correctly := (y >= YMIN) and (Y <= YMAX) and (m >= 1)
and (m <= 12) and (d > 0) ; if correctly then
if (m = 2) and (d = 29) and (y mod 4=0) then
{ Ничего не делать: это 29 февраля високосного года!}
else
correctly := d <= Size_of_Month[m] ;
if not correctly then
WriteLn (' Ошибка в дате!')
until correctly
end; {InpDate}
{--------------------------}
begin {InputDates}
repeat
InpDate (' .Введите дату рождения в формате ДД ММ ГГГГ:',d0,m0,y0) ;
InpDate (' Введите текущую дату: ',d,m,y);
{Проверяем непротиворечивость дат:}
correctly := у > у0; if not correctly and (y = y0) then
begin
correctly := m > m0;
if not correctly and (m = m0) then
correctly := d >= d0
end
until correctly
end; {InputDates}
В самом общем виде алгоритм подсчета количества дней, разделяющих две даты, описан выше. При его реализации следует учесть три возможных варианта:
месячный младенец (год и месяц обеих дат одинаков): количество дней находится простым вычитанием двух чисел;
годовалый младенец (год обеих дат совпадает): количество дней = (остаток дней в месяце рождения) + (количество дней в текущем месяце) + (количество дней в месяцах, разделяющих обе даты);
общий вариант (отличаются года): количество дней = (количество дней от даты рождения до конца года) + (количество дней в разделяющих даты годах) + (количество дней от начала текущего года до текущей даты).
С учетом этого составим начальный вариант программной реализации процедуры
GET_NUMBERS_OF_DAYS :
Procedure Get_numbers_of_days (d,m,y,d,m,y: Integer;
var days: Integer);
{Определение полного количества дней, прошедших от одной даты до другой }
{--------------------------}
Procedure Variant2;
{Подсчет количества дней в месяцах,разделяющих обе даты}
begin {Variant2}
end; {Variant2}
{--------------------------}
Procedure Variant3 ;
{Подсчет количества дней в месяцах и годах, разделяющих обе
даты}
begin {Variant3}
end; {Variant3}
{--------------------------}
begin {Get_numbers_of_days}
if (y = y0) and (m = m0) then {Даты отличаются только днями: }
days := d - d0
else {Даты отличаются не только днями:}
begin
days := d + Size_of_Month [m0] - d0;
{Учитываем количество дней в текущем месяце и количество дней до конца месяца рождения}
if (y0 mod 4=0) and (m0 = 2) then
inc(days); {Учитываем високосный год}
if у = y0 then
Variant2 {Разница в месяцах одного и того же года}
else
Variant3 {Даты отличаются годами}
end
end; {Get_numbers_of_days}
В этом фрагменте используется способ связи вспомогательных процедур VARIANT2 и VARIANT3 с основной процедурой через глобальные переменные, которыми являются параметры обращения к основной процедуре. Вспомогательные процедуры удобнее всего реализовать на основе циклов WHILE:
Procedure Variant2 ;
{Подсчет количества дней в месяцах, разделяющих обе даты }
var
mm : Integer;
begin {Variant2}
mm : = m0 ;
while mm < m do
begin
days := days + Size_of_Month [mm] ;
if (mm = 2) and (y0 mod 4=0) then
inc (days) ;
inc (mm)
end
end; {Variant2}
{--------------------------}
Procedure Variant3;
{Подсчет количества дней в месяцах и годах, разделяющих обе
даты }
var
mm/ УУ : Integer;
begin {Variant3}
mm : = m0 + 1 ;
while mm <= 12 do {Учитываем остаток года рождения:}
begin
days := days+Size_of_Month [mm] ;
if (mm = 2) and (y0 mod 4=0) then
inc (days) ;
inc (mm)
end ;
yy := y0 + 1;
while yy < у do {Прибавляем разницу лет:}
begin
days : = days + 365;
if yy mod 4=0 then
inc (days) ;
inc (yy)
end;
mm : = 1 ;
while mm < m do {Прибавляем начало текущего года:}
begin
days := days + Size_of_Month [mm] ;
if (y mod 4=0) and (mm = 2) then
inc (days) ;
inc (mm)
end
end; {Variant3}
В процедуре FINDMAXMIN осуществляется поиск критических дней, т.е. ближайших к текущей дате дней, для которых все три биоритма достигают своего максимума и минимума. Предполагается, что биоритмы изменяются по законам синуса от количества прожитых дней с периодами ТF, ТE и TI соответственно для физической, эмоциональной и интеллектуальной активности человека. В программе приняты следующие периоды (в днях):
Знакомство с языком Турбо Паскаля
TF= 23.6884
ТЕ= 28.4261
TI= 33.1638
Самый простой алгоритм поиска заключается в том, чтобы вычислить значения сумм всех трех синусоид для текущего дня и для каждого из последующих дней на некотором заранее обусловленном интервале, например, в пределах месяца. Сопоставив результаты расчетов для каждого дня, нетрудно определить критические дни:
Procedure FindMaxMin(var dmin,dmax: Integer; days: Integer);
{Поиск критических дней}
const
TF = 2*3.1416/23.6884;{Период физической активности}
ТЕ = 2*3.1416/28.4261;{Период эмоциональной активности}
TI = 2*3.1416/33.1638;{Период интеллектуальной активности}
INTERVAL =30; {Интервал прогноза}
var
min, {Накапливает минимум биоритмов}
max, {Накапливает максимум биоритмов}
x : Real; {Текущее значение биоритмов}
i : Integer;
begin {FindMaxMin}
max := sin(days*TF)+sin(days*TE)+sin(days*TI);
min := max; {Начальное значение минимума и максимума равно
значению биоритмов для текущего дня}
dmin := days;
dmax := days;
for i := 0 to INTERVAL do
begin
x := sin((days+i)*TF) + sin((days+i)*TE) +
sin((days+i)*TI);
if x > max then
begin
max : = x;
dmax : = days + i
end
else if x < min then
begin
min := x;
dmin := days + i
end
end;
end; {FindMaxMin}
При разработке алгоритма процедуры WRITEDATES, с помощью которой на экран выводится результат работы программы, учтем, что основные сложности будут связаны с определением новой даты по начальной дате и количеству прошедших дней. Этот насчет будет повторяться дважды - для даты пика и даты спада биоритмов, поэтому его следует вынести в отдельную процедуру WRITEDATES. Кроме того, вряд ли Вы откажетесь от возможности вывода на экран дополнительной информации о том, сколько полных дней, часов, минут и секунд разделяют дату рождения человека и текущую дату. Однако реализация этого вывода не столь проста, как это может показаться на первый взгляд. Дело в том, что диапазон возможных значений данных типа INTEGER составляет от -32768 до +32767. Средняя продолжительность жизни человека - около 70 лет, т.е. 25550 дней. Это значение еще можно представить в Переменной типа INTEGER, однако часы, минуты и тем более секунды средней продолжительности жизни далеко превышают этот диапазон. Чтобы получить вывод достоверных данных, необходимо расширить диапазон значений целых чисел. Для этого в Турбо Паскале предусмотрен специальный тип данных LONGINT («длинный» целый), имеющий диапазон значений от -2147483648 до +2147483647 (см. гл. 4). Поэтому в процедуре WRITEDATES следует предусмотреть вспомогательную переменную этого типа, присвоить ей значение переменной DAYS и уже затем использовать «длинную» переменную для вычисления (и вывода) часов, минут, секунд. В результате начальный вариант процедуры WRITEDATES может быть таким:
Procedure WriteDates (dmin,dmax,days : Integer);
{Определение и вывод дат критических дней. Вывод дополнительной информации о количестве прожитых дней, часов, минут и секунд }
{---------------------}
Procedure WriteDate (text : String; dd : Integer);
{Определение даты для дня DD от момента рождения. В глобальных переменных d, m и у имеется текущая дата, в переменной DAYS -количество дней, прошедших от момента рождения до текущей даты.Выводится сообщение TEXT и найденная дата в формате
ДД-МЕС-ГГГГ}
begin {WriteDate}
end; {WriteDate}
{---------------------}
var
LongDays: Longlnt; {"Длинная" целая переменная для часов,минут и секунд }
begin {Wri teDates}
LongDays : = days ;
WriteLn( 'Прошло: ', LongDays,' дней, ' , longDays*24, ' часов, ', LongDays*24*60, ' минут, ', LongDays*24*60*60, ' секунд');
WriteDate ( 'Наименее благоприятный день: ', drain);
WriteDate ( 'Наиболее благоприятный день: ',dmax)
end; {WriteDates}
Реализация процедуры WRITEDATE не вызывает особых сложностей:
Procedure WriteDate (text: String; dd: Integer);
const
Names_of_Monthes : array [1..12] of String [3] =('янв','фев','мар','апр','мая', 'июн','июл','авг','сен','окт', 'ноя','дек');
var
d0,m0,y0,ddd : Integer;
begin {WriteDate}
d0 := d;
m0 := m;
y0 : = y;
ddd := days;
while ddd<>dd do begin
inc(d0); {Наращиваем число}
if (y0 mod 4 <> 0) and (d0 > Size_of_Month[m0]) or (y0 mod ,4=0) and (d0=30) then
begin {Корректируем месяц}
d0 := 1;
inc(m0);
if m0 = 13 then {Корректируем год}
begin
m0 := 1;
inc(y0)
end
end;
inc(ddd)
end;
WriteLn(text,d0,'-',Names_of_Monthes[m0] ,'-',y0)
end; {WriteDate}
Собрав воедино отдельные части, получим полный текст программы (прил.5.2), предназначенной для определения биоритмов.
Игра ним
Ним - одна из самых старых и увлекательных математических игр. Для игры в ним необходим партнер (в ним играют вдвоем), стол и набор фишек. В качестве фишек обычно используются камешки или монетки. В наиболее известном варианте нима 12 фишек раскладываются в три ряда так, как показано на рис. 2.3.
Рис.2.3. Фишки, расположенные для игры в ним по схеме 3-4-5
Правила нима просты. Игроки по очереди забирают одну или несколько фишек из любого ряда. Не разрешается за один ход брать фишки из нескольких рядов. Выигрывает тот, кто возьмет последнюю фишку (фишки).
Если Вы сыграете несколько партий в ним, то скоро заметите, что существует некоторая оптимальная последовательность ходов, которая гарантирует победу, если только Вы начинаете игру и первым ходом берете две фишки из первого ряда. Любой другой ход даст шанс Вашему сопернику, который в этом случае наверняка победит, если, в свою очередь, воспользуется оптимальной стратегией.
Полный анализ игры с обобщением на любое число рядов с любым числом фишек в каждом ряду впервые опубликовал в 1901 г. профессор математики из Гарвардского университета Чарльз Л.Бутон, который и назвал игру «ним» от устаревшей формы английских глаголов «стянуть», «украсть». Открытая им оптимальная стратегия основана на двоичной системе счисления и довольно проста. Каждую комбинацию фишек Бутон назвал либо опасной, либо безопасной: если позиция, создавшаяся после очередного хода игрока, гарантирует ему победу, она называется безопасной, если такой гарантии нет - опасной. Бутон строго доказал, что любую опасную позицию всегда можно превратить в безопасную нужным ходом. Наоборот, если перед очередным ходом игрока уже сложилась безопасная позиция, то любой его ход превращает позицию в опасную. Таким образом, оптимальная стратегия состоит в том, чтобы каждым ходом опасную позицию превращать в безопасную и заставлять противника «портить» ее. Использование оптимальной стратегии гарантирует победу игроку только тогда, когда он открывает партию и начальная позиция фишек опасна или он делает второй ход, а начальная позиция безопасна.
Чтобы определить, опасна позиция или безопасна, нужно количество фишек в каждом ряду записать в двоичной системе счисления. Если сумма чисел в каждом столбце (разряде) равна нулю или четна, позиция безопасна. Если же сумма нечетна хотя бы в одном разряде, то позиция опасна. Например, для начальной позиции по схеме 3-4-5 получим:
Сумма по разрядам 212
Сумма цифр в среднем столбце равна 1 - нечетному числу, что свидетельствует об опасности этой позиции. Поэтому первый игрок может сделать ее безопасной для себя, если он возьмет две фишки из первого ряда. В результате в первом ряду остается только 1 фишка (двоичное число также 1), и сумма чисел в среднем столбце изменится на ноль.
В привычной нам десятичной системе счисления емкость каждого разряда равна 10, а для записи значений разряда используются цифры от 0 до 9. В двоичной системе счисления емкость каждого разряда равна 2, а из всех цифр используются только 0 и 1. В этой системе число записывается в виде суммы степеней двойки и при переходе от одного разряда к соседнему левому вес разряда увеличивается в 2 раза. Если нужно записать число 2 в двоичной системе, следует действовать точно так же, как при записи числа 10 в десятичной системе: записать ноль в первом (младшем) разряде и единицу - слева от него, т.е. 10 в двоичной системе означает 2 в десятичной системе. Точно так же 100 в двоичной системе означает 4 в десятичной, 1000 - 8 и т.д.
Для перевода любого целого положительного числа из десятичной системы в двоичную можно использовать прием последовательного деления числа на 2. Например, для перевода десятичного числа 11 в двоичную систему используется такая цепочка делении:
Если, начиная с последнего результата, остатки от деления записать в обратном порядке, получим 1011 - это и есть двоичное представление десятичного числа 11. В этом легко убедиться, записав двоичное число 1011 как сумму степеней 2:
1х23+1х22+1х21+1 = 11
Попробуем разработать программу, которая будет выполнять роль партнера в игре, причем это будет весьма опасный противник, так как он будет «знать» оптимальную стратегию и умело ею пользоваться.
Представим себе на минутку, что Вы уже создали программу и начинаете работу с ней. Как организовать удобное взаимодействие с программой? Конечно, возможно простейшее решение: Вы предварительно раскладываете на столе монетки, по запросу программы вводите в нее Ваш ход, затем читаете на экране ход программы, делаете нужные изменения в раскладке монет и т.д. Вряд ли Вас удовлетворит такая программа. Гораздо эффектнее имитировать на экране игровое поле с фишками и своеобразное табло игры, в котором сообщается об очередных ходах противников. Однако использованные ранее средства вывода данных (процедуры WRITE и WRITELN) недостаточны для этих целей, ведь с их помощью нельзя указать конкретное место на экране, куда нужно поместить выводимую информацию. Вывод этими процедурами всегда начинается с той позиции на экране, которую в данный момент занимает курсор. Следовательно, для вывода текста в нужное место экрана требуется перед обращением к этим процедурам изменить положение курсора. Для этих целей служит процедура GOTOXY, которая хотя и является стандартной, но располагается в отдельной библиотеке (модуле) с именем CRT. Подробнее о модулях мы поговорим в гл.9, а сейчас просто учтем, что процедуры и функции из дополнительных библиотек становятся доступны в программе, если в самом ее начале объявить об их использовании. Так, указание об использовании библиотеки CRT делается таким образом:
Uses CRT;
После такого указания программе становятся доступны дополнительные процедуры и функции, с помощью которых можно организовать гибкое управление текстовым экраном, в том числе процедура GOTOXY, перемещающая курсор в произвольное место на экране.
Теперь попробуем составить алгоритм главной программы. В простейшем виде он таков:
Uses CRT; {Подключение библиотеки дополнительных
процедур и функций для управления экраном}
var
exit: Boolean; {Признак окончания работы}
begin
{Подготовить экран к работе}
repeat
{Ввести, проконтролировать и отобразить ход игрока}
{Найти и отобразить ход программы}
until exit
end.
В этом алгоритме выделяются три главных действия и организуется цикл, который будет выполняться до тех пор, пока где-то в программе переменной EXIT (выход) не будет присвоено значение TRUE.
Вначале экран подготавливается к работе: формируется игровое поле с фишками и выводится информация о правилах игры. Как уже говорилось, ним позволяет играть с произвольным количеством фишек. Разумно ввести в программу возможность, которая бы позволила пользователю самому указывать число рядов и количество фишек в рядах, т.е. настраивать программу на нужную раскладку фишек. Можно несколько модифицировать главную программу, чтобы предусмотреть эту возможность:
Uses CRT; {Подключение библиотеки дополнительных
процедур и функций для управления экраном}
var
exit : Boolean; {Признак окончания работы}
change : Boolean; {Признак изменения условий игры}
{----------------------}
Procedure Prepare; {Готовит экран к игре}
begin {Prepare}
end; {Prepare}
{----------------------}
Procedure GetPlayerMove;
{Получает, контролирует и отображает ход игрока}
begin {GetPlayerMove}
end; {GetPlayerMove}
{----------------------}
Procedure SetOwnerMove;
{Находит и отображает очередной ход программы}
begin {SetOwnerMove}
end; {SetOwnerMove}
{----------------------}
begin {Главная программа}
{Подготовить начальную расстановку фишек}
repeat {Цикл изменения условий игры}
Prepare; {Подготовить экран}
repeat {Игровой цикл}
GetPlayerMove; {Получить ход пользователя}
if not (exit or change) then
SetOwnerMove {Определить собственный ход} until exit or change
until exit
end.
В этом варианте главная программа содержит два вложенных друг в друга цикла Repeat. . .Until: внутренний цикл управляет игрой, внешний отвечает за изменение условий игры. Оба цикла управляются двумя логическими переменными, которые являются глобальными для трех основных процедур PREPARE, GETPLAYERMOVE, SETOWNERMOVE и, следовательно, могут изменяться внутри этих процедур.
Теперь настал момент подумать о том, каким способом в программе будет храниться и использоваться информация о текущем состоянии игры. Судя по всему, нам понадобятся хотя бы две переменные: в одной, назовем ее NROW, будет содержаться число рядов фишек, в другой (NCOL) - количество фишек в каждом ряду. Переменная NROW содержит одно целое положительное число, поэтому ее тип должен быть INTEGER. В переменной NCOL должно быть не менее NROW целых чисел, т.е. ее тип - это массив целых чисел. Поскольку в программе предусмотрена возможность изменения условий игры самим игроком, переменная NROW может меняться от партии к партии. В соответствии с этим должна была бы меняться и длина массива NCOL. Однако в Турбо Паскале нельзя использовать массивы, длина которых меняется динамически, т.е. в процессе работы программы. Эта длина должна определяться статически (на этапе компиляции) и не может меняться в работающей программе. Значит, понадобится массив достаточно большой длины, чтобы его хватило на все случаи. На экране одновременно можно отобразить максимум 25 строк по 80 символов в каждой строке. Однако использовать все строчки экрана как возможные ряды фишек вряд ли целесообразно: во-первых, сама игра при большом количестве рядов становится неинтересной, так как игрок не сможет проанализировать в уме все варианты ходов; во-вторых, на экране не останется места для вывода другой информации. Будем считать, что максимальное количество рядов фишек не должно превышать 14. Укажем это константой MAXROW - теперь, если Вы захотите назначить другое максимальное количество рядов, понадобится изменить значение этой константы и перекомпилировать программу. Именно таким способом программам придается дополнительная гибкость: Вы сосредоточиваете в нескольких константах параметры, которые выбраны Вами произвольно и которые Вы или кто-то другой, возможно, захочет изменить. Все размерности массивов или другие особенности программной реализации следует определять через эти константы, тогда процедура переделки программы предельно упростится.
С учетом сказанного назначим следующие глобальные константы и переменные:
const
MAXROW = 14; {Максимальное количество рядов}
MAXCOL = 20; {Максимальное количество фишек в ряду}
type
ColType= array [I..MAXROW] of Integer;
var
exit :Boolean; {Признак окончания работы}
change:Boolean; {Признак изменения условий игры}
nrow :Integer; {Количество рядов}
ncol :ColType; {Максимальное колич-во фишек по рядам}
col :ColType; {Текущее количество фишек по рядам}
Константа MAXCOL не участвует в формировании массивов, она будет использоваться для контроля горизонтальных размеров игрового поля. Поэтому она, а также пять переменных сделаны глобальными. Если считать, что начальная раскладка фишек соответствует схеме 3-4-5, то можно написать такой окончательный вариант главной программы:
Uses CRT; {Подключение библиотеки дополнительных
процедур и функций для управления экраном}
{------------------------}
Procedure Prepare; {Готовит экран к игре}
begin {Prepare}
end; {Prepare}
{------------------------}
Procedure GetPlayerMove;
{Получает, контролирует и отображает ход игрока}
begin {GetPlayerMove}
end ; {Get PlayerMove}
{------------------------}
Procedure SetOwnerMove;
{Находит и отображает очередной ход программы}
begin {SetOwnerMove}
end; {SetOwnerMove}
{------------------------}
begin {Главная программа}
nrow := 3; {Готовим игру... }
ncol [1]:= 3; { на поле из трех }
ncol [2]:= 4; { рядов фишек }
ncol [3]:= 5; { по схеме 3-4-5.}
repeat {Цикл изменения условий игры}
Prepare; {Подготовить экран}
repeat {Игровой цикл}
GetPlayerMove; {Получить ход пользователя}
if not (exit or change) then
SetOwnerMove {Определить собственный ход}
until exit or change
until exit
end.
Приступим к конструированию процедуры PREPARE. В ходе ее работы формируется значение переменной COL, соответствующее начальной раскладке фишек, и выводится информация о правилах игры. Чтобы было понятнее дальнейшее описание программной реализации, на рис. 2.4 показан вид экрана в начальном состоянии игры.
Процедура начинает свою работу с очистки экрана от имеющейся на нем информации. Это достигается обращением к стандартной процедуре без параметров CLRSCR. Затем выводятся три строчки с названием игры и кратким описанием ее правил. Кроме того, слева и справа на экране формируются заголовки для двух колонок цифр, в которых затем будут отображаться номер ряда (слева) и текущее количество фишек в ряду (справа). Эта информация поможет игроку сообщить программе свой ход. Для размещения информации на нужных участках экрана используется процедура GOTOXY(X,Y) , с помощью которой курсор перемещается нужным образом. Параметры X и Y этой процедуры задают новые координаты курсора. Начало координат соответствует точке (1,1) и размещается в левом верхнем углу экрана, поэтому горизонтальная координата увеличивается слева направо, а вертикальная - сверху вниз.
Рис.2.4. Вид экрана в начале игры ним
Procedure Prepare;
{Подготовка данных и экрана к игре}
const
Header0 = 'ИГРА НИМ';
Headerl = 'Вы можете взять любое число фишек из любого ряда.';
Header2 = 'Выигрывает тот, кто возьмет последнюю фишку.';
Headers = 'Номер ряда';
Header4 = 'Количество фишек';
var
i : Integer; begin {Prepare}
ClrScr; {Очищаем экран}
{Выводим строки заголовка:}
GotoXY((80-Length(Header0)) div 2,1);
Write(HeaderO) ;
GotoXY((80-Length(Headerl)) div 2,2);
Write(Headerl);
GotoXY((80-Length(Header2)) div2,3);
Writeln(Header2);
Write(Header3);
GotoXY(80-Length(Header4),4);
Write(Header4);
{Готовим начальную раскладку:}
for i := 1 to nrow do
col [i] := ncol[i]
end; {Prepare}
Для вывода верхних строк строго посередине экрана используется задание горизонтальной координаты курсора для процедуры GotoXY как половины от разницы между полной длиной экрана (80 позиций) и длиной выводимой строки (определяется с помощью функции LENGTH).
В процедуре GetPlayerMove осуществляются ввод, контроль и отображение на экране очередного хода игрока. Предварительно нужно показать игроку текущее состояние игрового поля. Поскольку поле будет обновляться как минимум дважды (после хода игрока и после хода программы), действия, связанные с изображением поля на экране, следует вынести в отдельную процедуру. Назовем ее ShowField и займемся ее реализацией.
Судя по всему, нам понадобится организовать цикл; в ходе цикла для каждого ряда игрового поля будет выведена строка, в левой части которой указывается номер ряда, в правой - текущее количество фишек в нем, а посередине выводятся символы, имитирующие фишки. В принципе, можно выбрать любой символ ПК для обозначения фишки, например, X или О. Я предпочел воспользоваться символом псевдографики с кодом 220: этот символ представляет собой небольшой квадратик и легко ассоциируется с фишкой.
Procedure ShowField;
{ Отображает на экране текущее состояние игрового поля }
const
FISH = #220; {Символ-указатель фишки}
Х0 = 4; {Левая колонка номеров рядов}
X1 =72; {Правая колонка количества фишек}
X = 20; {Левый край игрового поля}
var
i,j : Integer;
begin {ShowField}
for i := 1 to nrow do begin
GotoXY(X0,i+4);
Write(i); {Номер ряда}
GotoXY(X1,i+4);
Write(col[i]:2); {Количество фишек в ряду}
for j := 1 to ncol[i] do {Вывод ряда фишек:}
begin
GotoXY(X+2*j,i+4); if j[i] then
Write(FISH)
else
Write('.')
end
end
end; {ShowField}
Символы FISH (квадратики) выводятся через одну позицию, чтобы не сливались на экране. В те позиции, в которых ранее стояли уже снятые с поля фишки, выводится точка.
Теперь вернемся к процедуре GETPLAYERMOVE. При вводе любого очередного хода игрок должен задать два целых числа X1 и Х2. Первое из них указывает номер ряда, а второе - количество фишек, которые игрок хочет забрать из этого ряда. Программа должна проконтролировать правильность задания этих чисел: X1 должно указывать непустой ряд, Х2 не может превышать количество фишек в этом ряду. Кроме того, мы должны условиться о двух особых случаях:
пользователь больше не хочет играть и дает команду завершить работу программы;
пользователь хочет изменить условия игры.
Пусть ввод числа X1 =0 означает команду выхода из программы, а X1 = -1 - команду изменения условий игры. Тогда можно написать такой начальный вариант процедуры:
Procedure GetPlayerMove;
{Получает, контролирует и отображает ход игрока}
var
correctly : Boolean; {Признак правильности сделанного хода}
xl,x2 : Integer; {Вводимый ход}
begin {GetPlayerMove}
{ Показываем начальное состояние игрового поля}
ShowField;
{Сообщаем, игроку правила ввода хода}
repeat
{Приглашаем игрока ввести ход}
ReadLn(xl,x2); {Вводим очередной ход}
exit := xl=0; {Контроль команды выхода}
change := xl=-l; {Контроль команды изменения}
if not (exit or change) then
{Проверить правильность хода и установить нужное значение переменной CORRECTLY. Если ход правильный, сделать нужные изменения в раскладке фишек и показать поле.}
else
correctly := true {Случай EXIT или CHANGE}
until correctly; if change then
{ Изменить условия игры }
end; {GetPlayerMove}
В этом варианте в процедуре GetPlayerMove нет описания процедуры SHOWFIELD. Сделано это не случайно: процедура ShowField может понадобиться
также и при реализации процедуры SetOwnerMove, поэтому она должна быть глобальной по отношению и к GetPlayerMove, и к SetOwnerMove, т.е. ее описание должно в тексте программы предшествовать описаниям двух использующих ее процедур.
Действия
{ Сообщить игроку правила ввода хода } ,
{ Пригласить игрока ввести ход }
и
{Проверить правильность хода и установить нужное значение
переменной Correctly. Если ход правильный, сделать нужные
изменения в раскладке фишек и показать поле.}
не очень сложны в реализации, поэтому их можно осуществить непосредственно в теле процедуры GETPLAYERMOVE. Иное дело - изменение условий игры. Это действие полезно реализовать в отдельной процедуре GETCHANGE. С учетом этого второй вариант процедуры GETPLAYERMOVE примет такой вид:
Procedure GetPlayerMove;
{Получает, контролирует и отображает ход игрока}
const
ТЕХТ1 = 'Введите Ваш ход в формате РЯД КОЛИЧ ';
ТЕХТ01= ' (например, 2 3- взять из 2 ряда 3 фишки) ' ;
ТЕХТ2 = 'или введите 0 0 для выхода из игры; ' ; .
ТЕХТ02= '-1 0 для настройки игры';
ТЕХТЗ = 'Ваш ход: ';
Y = 20; {Номер строки для вывода сообщений}
var
correctly : Boolean; {Признак правильности сделанного хода}
xl,x2 : Integer; {Вводимый ход}
{-----------------}
Procedure GetChange;
{Устанавливает новую настройку игры (количество рядов и количество фишек в каждом ряду}
begin {GetChange}
end; {GetChange}
{-----------------}
begin {GetPlayerMove}
ShowField; {Показываем начальное состояние поля}
{Сообщить игроку правила ввода хода:}
GotoXY((80-Length(TEXT1+TEXT01)) div2,Y);
Write(TEXT1+TEXT01);
GotoXY((80-Length(TEXT2+TEXT02)) div2,Y+l);
Write(TEXT2+TEXT02);
repeat
{Пригласить игрока ввести ход:}
GotoXY(l,Y+2);
Write(TEXTS); {Выводим приглашение и стираем предыдущий ход}
GotoXY(WhereX-16,Y+2); { Курсор влево на 16 позиций}
ReadLn(xl,x2); {Вводим очередной ход}
exit := xl=0; {Контроль команды выхода}
change := xl=-l; {Контроль команды изменения}
if not (exit or change) then
begin
correctly := (xl > 0) and (xl <= nrow) and
(x2 <= col[xl]) and (x2 > 0) ;
if correctly then
begin {Ход правильный:}
col[xl] := col[xl]-x2; {Изменяем раскладку фишек}
ShowField {Показываем поле}
end
else
Write(#7) {Ход неправильный: дать звуковой сигнал}
end
else
correctly := true {Случай EXIT или CHANGE}
until correctly;
if change then
GetChange end; {GetPlayerMove}
Обратите внимание: константа
ТЕХТЗ = 'Ваш ход:
имеет длинный «хвост» из пробелов (их 17), поэтому после вывода этого приглашения курсор возвращается влево на 16 позиций оператором
GotoXY(WhereX-16,Y+2); {курсор влево на 16 позиций}
(функция WHEREX возвращает текущую горизонтальную координату курсора, а функция WHEREY - его вертикальную координату). Сделано это для того, чтобы в случае, если игрок ввел неверный ход и программа повторяет вывод приглашения, пробелы в константе ТЕХТЗ затерли бы строку предыдущего ввода.
Чтобы завершить создание процедуры GETPLAYERMOVE, нужно спроектировать процедуру GETCHANGE, в которой осуществляется изменение условий игры. Я привожу текст этой процедуры без пояснений и приглашаю Вас самостоятельно разобраться в том, как она работает:
Procedure GetChange;
{Устанавливает новую настройку игры (количество рядов и количество фишек в каждом ряду}
const
tl='HACTPOЙKA ИГРЫ';
t2 ='(ввод количества рядов и количества '+'фишек в каждом ряду)';
var
correctly : Boolean;
i : Integer; begin {GetChange}
ClrScr;
GotoXY( (80 -Length (tl) ) div2,l);
Write(tl) ;
GotoXY( (80 -Length (t2) ) div2,2);
Write (t2);
repeat
GotoXYd, 3) ;
Write ( 'Введите количество рядов (максимум ',
MAXROW, '):');
GotoXY(WhereX-6,WhereY) ;
ReadLn (nrow) ;
correctly := (nrow <= MAXROW) and (nrow > 1) ;
if not correctly then
Write (#7)
until correctly;
for i := 1 to nrow do
repeat
GotoXY(l,i+3) ;
Write (' ряд ',i,', количество фишек (максимум ',
MAXCOL , ' ) : ' ) ;
GotoXY (WhereX - 6 , WhereY) ;
ReadLn (ncol [i] ) ;
correctly := (ncol [i] <= MAXCOL) and (ncol[i] > 0) ;
if not correctly then
Write (#7)
until correctly
end; {GetChange}
Переходим к конструированию процедуры SETOWNERMOVE, в которой программа должна проконтролировать текущую ситуацию на игровом поле и выбрать собственный ход. Работа процедуры начинается с подсчета числа непустых рядов. В зависимости от этого подсчета реализуются следующие действия:
если все ряды пусты, значит предыдущим ходом игрок забрал последнюю фишку и он победил; нужно поздравить его с победой, усложнить игру и предложить сыграть еще раз;
если есть только один непустой ряд, то очередной ход программы очевиден -забрать все фишки, что означает победу машины: сообщить об этом и предложить сыграть еще раз;
если осталось два или более непустых ряда, выбрать собственный ход на основе оптимальной стратегии. Начальный вариант процедуры:
Procedure SetOwnerMove;
{Находит и отображает очередной ход программы}
{-----------------}
Function CheckField : Integer;
{Проверяет состояния игры. Возвращает 0, если нет ни одной фишки (победа игрока) , 1 - есть один ряд (победа машины) и - количество непустых рядов в остальных случаях}
begin {CheckField}
end; {CheckField}
{-----------------}
Procedure PlayerVictory;
{Поздравить игрока с победой и усложнить игру}
begin {PlayerVictory}
end; {PlayerVictory}
{-----------------}
Procedure OwnVictory;
{Победа машины}
begin {OwnVictory}
end; {OwnVictory}
{-----------------}
Procedure ChooseMove;
{Выбор очередного хода}
begin {ChooseMove}
end; {ChooseMove}
{-----------------}
begin {SetOwnerMove}
case CheckField of {Проверяем количество непустых рядов}
0 : PlayerVictory; {Все ряды пусты - победа игрока}
1 : OwnVictory; {Один непустой ряд - победа машины}
else
ChooseMove; {Выбираем очередной ход}
end; {case}
end; {SetOwnerMove}
Функция CHECKFIELD и процедуры PLAYERVICTORY и OWNVICTORY достаточно просты и их текст помещается без каких-либо пояснений в окончательный вариант программы (см. прил.5.3). Отмечу лишь, что в случае победы игрока нет смысла повторять партию заново с той же самой раскладкой фишек. Поэтому игра усложняется: в исходную раскладку добавляется еще по одной фишке в каждый ряд.
В процедуре CHOOSEMOVE анализируется позиция и выбирается очередной ход программы. Описание оптимальной стратегии уже приводилось выше. Действия программы заключаются в поиске первого слева (старшего) двоичного разряда, для которого сумма чисел нечетная. Если такой разряд не обнаружен, то текущая позиция безопасна для игрока, а значит любой ход программы сделает ее опасной. В этом случае для программы не существует оптимального выбора и она лишь убирает одну фишку из любого непустого ряда. Такая тактика означает пассивное ожидание ошибки игрока.
Если обнаружен разряд i с нечетной суммой, программа приступает к реализации оптимальной стратегии и тогда игрок обречен на поражение. Для выбора ряда, из которого следует взять фишки, программа просматривает последовательно все ряды и отыскивает тот ряд j, количество фишек в котором (в двоичном представлении) дает единицу в разряде i. Значение этого разряда для количества фишек в ряду j заменяется нулем. Затем программа продолжает подсчет суммы для оставшихся младших разрядов. Если в каком-либо из них вновь обнаружена нечетность, значение этого разряда для количества фишек в рядуj инвертируется, т.е. 0 заменяется на 1, а 1 на 0. Например, если двоичные представления числа фишек и четности сумм таковы:
число фишек в ряду j: 01001
четность сумм: 01011
(единицей указаны разряды с нечетными суммами), то в результате этой операции получим:
число фишек в ряду j: 00010
четность сумм: 00000
Таким образом, в исходном состоянии в ряду j было 1001 =9 фишек, безопасная позиция требует, чтобы в ряду осталось 0010 = 2 фишки, следовательно, из него нужно забрать 9-2 = 7 фишек.
Окончательный вариант программы представлен в прил.5.3. Попробуйте разобраться в ее деталях самостоятельно.
В программной реализации алгоритма широко используется то обстоятельство, что Ваш компьютер, как и все остальные вычислительные машины, работает с числами, представленными в двоичной системе счисления. Поэтому для получения двоичного представления числа в процедуре BITFORM оно проверяется на четность с помощью стандартной функции ODD, затем сдвигается вправо на один двоичный разряд (операция SHR), вновь осуществляется проверка на четность и т.д. до тех пор, пока не будут проверены все разряды. Максимальное число двоичных разрядов, достаточное для двоичного представления количества фишек в ряду MAXCOL=63, задается константой ВIТ=6.
Для получения суммы двоичных разрядов в процедуре CHOOSEMOVE используется суммирование разрядов по модулю 2 с помощью операции XOR. Такое суммирование дает 0, если количество единиц четное или равно нулю, и 1 - если нечетное. В этой же процедуре для инверсии двоичного разряда применяется оператор
if nbit[i] = 1 then
ncbit[j,i] := ord(ncbit[j,i]=0); {Инверсия разрядов},
в котором используется соглашение о внутреннем представлении логических величин в Турбо Паскале: 0 соответствует FALSE, а 1 - TRUE.
Массивы
Рассмотренные выше простые типы данных позволяют использовать в программе одиночные объекты - числа, символы, строки и т.п. В Турбо Паскале могут использоваться также объекты, содержащие множество однотипных элементов. Это массивы -формальное объединение нескольких однотипных объектов (чисел, символов, строк и т.п.), рассматриваемое как единое целое. К необходимости применения массивов мы приходим всякий раз, когда требуется связать и использовать целый ряд родственных величин. Например, результаты многократных замеров температуры воздуха в течение года удобно рассматривать как совокупность вещественных чисел, объединенных в один сложный объект - массив измерений.
При описании массива необходимо указать общее число входящих в массив элементов и тип этих элементов. Например:
var
а : array [1..10] of Real;
b : array [0..50] of Char;
с : array [-3..4] of Boolean;
Как видим, при описании массива используются зарезервированные слова ARRAY и OF (массив, из). За словом ARRAY в квадратных скобках указывается тип-диапазон, с помощью которого компилятор определяет общее число элементов массива. Тип-диапазон (подробнее см. в гл.4) задается левой и правой границами изменения индекса массива, так что массив А состоит из 10 элементов, массив В - из 51, а массив С - из 8 элементов. За словом OF указывается тип элементов, образующих массив.
Доступ к каждому элементу массива в программе осуществляется с помощью индекса - целого числа (точнее, выражения порядкового типа, см. гл.4), служащего своеобразным именем элемента в массиве (если левая граница типа-диапазона равна 1, индекс элемента совпадает с его порядковым номером). При упоминании в программе любого элемента массива сразу за именем массива должен следовать индекс элемента в квадратных скобках, например:
var
a: array [1..10] of Integer;
b: array [0..40] of Char;
c: array [-2..2] of Boolean;
k: Integer; begin
b[17] := 'F1;
c[-2] := a[l] > [2] ;
for k : = 1 to 10 do a[k] := 0;
...
end.
В правильно составленной программе индекс не должен выходить за пределы, определенные типом-диапазоном. Например, можно использовать элементы А[1], В[38], С[0], но нельзя A[0] или С[38] (определение массивов см. выше). Турбо Паскаль может контролировать использование индексов в программе на этапе компиляции и на этапе счета программы.
Для иллюстрации приемов работы с массивами составим программу (пример 2.9), которая создает массив случайных целых чисел, подсчитывает их среднее арифметическое, а также определяет и выводит на экран минимальное и максимальное из этих чисел.
Пример 2.9
Program Average;
{Программа создает массив из N случайных целых чисел, равномерно распределенных в диапазоне от 0 до MAX_VALUE-1, подсчитывает среднее арифметическое этих чисел, а также минимальное и максимальное из них.}
const
N = 1000;
MAX_VALUE = 100+1; {Диапазон значений случайных чисел}
var
m : array [1..N] of Integer; {Массив чисел}
i : Integer; {Индекс массива}
max, min : Integer; {Максимальное и минимальное число}
s : Real; {Сумма чисел}
begin
{Наполняем массив случайными числами:}
for i := 1 to N do
m[i] := random(MAX_VALUE); {Задаем начальные значения переменных:}
s := 0;
max : = m [ 1 ] ;
min : = m [ 1 ] ;
{Цикл вычисления суммы всех случайных чисел и поиска минимального и максимального:}
for i := 1 to N do
begin
s : = s + m [ i ] ;
if m[i] < min then
min := m[i]
else if m[i] > max then
max := m[i]
end;
{Вычисляем среднее значение и печатаем результат:}
WriteLn('MMH = ',min,' Макс = ', max, ' Среднее = ',s/N)
end.
Для создания массива используется встроенная функция RANDOM (MAX) , которая возвращает случайное целое число, равномерно распределенное в диапазоне от 0 до МАХ-1 (МАХ- параметр обращения).
Метки и операторы перехода
Можно теоретически показать, что рассмотренных операторов вполне достаточно для написания программ любой сложности. В этом отношении наличие в языке операторов перехода кажется излишним. Более того, современная технология структурного программирования основана на принципе «программировать без GOTO»: считается, что злоупотребление операторами перехода затрудняет понимание программы, делает ее запутанной и сложной в отладке.
Тем не менее, в некоторых случаях использование операторов перехода может упростить программу.
Оператор перехода имеет вид:
GOTO <метка>.
Здесь GOTO - зарезервированное слово (перейти [на метку]); <метка> - метка.
Метка в Турбо Паскале - это произвольный идентификатор, позволяющий именовать некоторый оператор программы и таким образом ссылаться на него. В целях совместимости со стандартным языком Паскаль в языке Турбо Паскаль допускается в качестве меток использование также целых чисел без знака.
Метка располагается непосредственно перед помечаемым оператором и отделяется от него двоеточием. Оператор можно помечать несколькими метками, которые в этом случае отделяются друг от друга двоеточием. Перед тем как появиться в программе, метка должна быть описана. Описание меток состоит из зарезервированного слова LABEL (метка), за которым следует список меток:
label
loop, 1b1, 1b2;
begin
.......
goto 1b1;
.......
loop: ......
.......
1b1:1b2: ......
........
goto 1b2;
.......
Действие оператора GOTO состоит в передаче управления соответствующему меченному оператору.
При использовании меток необходимо руководствоваться следующими правилами:
метка, на которую ссылается оператор GOTO, должна быть описана в разделе описаний и она обязательно должна встретиться где-нибудь в теле программы;
метки, описанные в процедуре (функции), локализуются в ней, поэтому передача управления извне процедуры (функции) на метку внутри нее невозможна.
Оператор выбора
Оператор выбора позволяет выбрать одно из нескольких возможных продолжений программы. Параметром, по которому осуществляется выбор, служит ключ выбора -выражение любого порядкового типа (любого из рассмотренных, кроме типов REAL и STRING, см. гл. 4).
Структура оператора выбора такова:
CASE <ключ_выбора> OF <список_выбора> [ELSE <операторы>] END
Здесь CASE, OF, ELSE, END - зарезервированные слова (случай, из, иначе, конец);
<ключ_выбора> - ключ выбора;
<список_выбора> - одна или более конструкций вида:
<константа_выбора> : <оператор>;
<константа_выбора> - константа того же типа, что и выражение<ключ_выбopa> ;
<операторы> - произвольные операторы Турбо Паскаля.
Оператор выбора работает следующим образом. Вначале вычисляется значение выражения <ключ_выбора>, а затем в последовательности операторов <список_выбора> отыскивается такой, которому предшествует константа, равная вычисленному значению. Найденный оператор выполняется, после чего оператор выбора завершает свою работу. Если в списке выбора не будет найдена константа, соответствующая вычисленному значению ключа выбора, управление передается операторам, стоящим за словом ELSE. Часть ELSE <оператор> можно опускать. Тогда при отсутствии в списке выбора нужной константы ничего не произойдет и оператор выбора просто завершит свою работу.
Составим программу (пример 2.8), имитирующую работу микрокалькулятора. Программа вводит две строки: первая содержит два произвольных числа, разделенных пробелом, вторая - символ арифметического действия, например:
2 2
*
или
18.35 0.12
/
Над введенными числами осуществляется соответствующее действие и результат выводится на экран. Признаком конца работы программы служит ввод любого символа, отличного от +,-,*, /.
Пример 2.8
Program Calc;
{Программа вводит два числа в первой строке и один из знаков +, -, *, / - во второй и выводит на экран результат соответствующего арифметического действия}
var
operation : Char; {Знак операции}
х, у, z : Real; {Операнды и результат}
stop : Boolean; {Признак ошибочной операции
и останова}
begin
stop := false;
repeat
WriteLn; {Пустая строка-разделитель}
Write('x,y= ' ) ;
ReadLn(x,y);
Write('операция: ') ;
ReadLn(operation);
case operation of
WriteLn('результат=',z)
until stop
end.
Любому из операторов списка выбора может предшествовать не одна, а несколько констант выбора, разделенных запятыми. Например, следующая программа при вводе одного из символов: у или Y выведет на экран слово «Да», а при вводе n или N - слово «Нет»:
var
ch : Char ;
begin
ReadLn (ch) ;
case ch of
'n','N' : WriteLn ('Нет' );
'y','Y' : WriteLn ('Да')
end
end.
Операторы языка
С одним из наиболее часто используемых операторов языка Турбо Паскаль - оператором присваивания мы уже познакомились. Ниже рассматриваются остальные операторы языка.
Операторы повторений
В языке Турбо Паскаль имеются три различных оператора, с помощью которых можно запрограммировать повторяющиеся фрагменты программ.
Счетный оператор цикла FOR имеет такую структуру:
FOR <пар_цик> := <нач_знач> ТО <кон_знач> DO <оператор>.
Здесь FOR, TO, DO - зарезервированные слова (для, до, выполнить);
<пар_цик> - параметр цикла - переменная типа INTEGER (точнее, любого порядкового типа, см. гл.4);
<нач_знач> - начальное значение - выражение того же типа;
<кон_знач> - конечное значение - выражение того же типа;
<оператор> - произвольный оператор Турбо Паскаля.
При выполнении оператора FOR вначале вычисляется выражение <нач_знач> и осуществляется присваивание <пар_цик> : = <нач_знач>. После этого циклически повторяется:
проверка условия <пар_цик> <= <кон_знач>; если условие не выполнено, оператор FOR завершает свою работу;
выполнение оператора <оператор>;
наращивание переменной <пар_цик> на единицу.
В качестве иллюстрации применения оператора FOR рассмотрим программу, осуществляющую ввод с клавиатуры произвольного целого числа N и вычисление суммы всех целых чисел от 1 до N (пример 2.5).
Пример 2.5
Program Summ_of_Integer;
{Программа вводит целое положительное число N и подсчитывает сумму всех целых чисел от 1 до N}
var
i, n, s : Integer;
begin
Write('N = ');
ReadLn(n); . {Вводим N}
s := 0; {Начальное значение суммы}
for i : = 1 to n do {Цикл подсчета суммы}
s : = s + i;
writeln('Сумма = ',s) {Выводим результат}
end.
Отметим два обстоятельства. Во-первых, условие, управляющее работой оператора FOR, проверяется перед выполнением оператора <оператор>: если условие не выполняется в самом начале работы оператора FOR, исполняемый оператор не будет выполнен ни разу. Другое обстоятельство - шаг наращивания параметра цикла строго постоянен и равен (+1). Существует другая форма оператора:
FOR<пар_цик>: = <нач_знач> DOWNTO <кон_знач> DO <оператор>
Замена зарезервированного слова ТО на DOWNTO означает, что шаг наращивания параметра цикла равен (-1), а управляющее условие приобретает вид <пар_цик> = <кон_знач>.
Пример 2.5 можно модифицировать так, чтобы сделать его пригодным для подсчета любых сумм - положительных и отрицательных:
..................
s := 0;
if n >= 0 then
for i := 1 to n do
s := s + i else
for i := -1 downto n do s : = s + i ;
...............
Два других оператора повторений лишь проверяют условие выполнения или повторения цикла, но не связаны с изменением счетчика цикла.
Оператор цикла WHILE с предпроверкой условия:
WHILE <условие> DO <оператор>.
Здесь WHILE, DO - зарезервированные слова (пока [выполняется условие], делать);
<условие> - выражение логического типа;
<оператор> - произвольный оператор Турбо Паскаля.
Если выражение <условие> имеет значение TRUE, то выполняется <оператор>, после чего вычисление выражения <условие> и его проверка повторяются. Если <условие> имеет значение FALSE , оператор WHILE прекращает свою работу.
Рассмотрим пример 2.6, иллюстрирующий использование оператора WHILE. Найдем так называемое «машинное эпсилон» - такое минимальное, не равное нулю вещественное число, которое после прибавления его к 1.0 еще дает результат, отличный от 1.0.
Пример 2.6
Program EpsilpnDetect;
{Программа вычисляет и выводит на экран значение "машинного эпсилон"}
var
epsilon: Real;
begin
epsilon := 1;
while epsilon/2 + 1 > 1 do
epsilon := epsilon/2
WriteLn('Машинное эпсилон = ',epsilon)
end.
У читателя, привыкшего к непрерывной вещественной арифметике, может вызвать недоумение утверждение о том, что в дискретной машинной арифметике всегда существуют такие числа 0<X<eps, что 1.0+Х=1.0. Дело в том, что внутреннее представление типа REAL может дать «лишь» приблизительно 1014 возможных комбинаций значащих разрядов в отведенных для него 6 байтах. Конечно же, это очень большое число, но оно несопоставимо с бесконечным множеством вещественных чисел. Аппроксимация бесконечного непрерывного множества вещественных чисел конечным (пусть даже и очень большим) множеством их внутреннего машинного представления и приводит к появлению «машинного эпсилон».
Оператор цикла REPEAT... UNTIL с постпроверкой условия:
REPEAT <тело_цикла> UNTIL <условие>.
Здесь REPEAT, UNTIL- зарезервированные слова (повторять до тех пор, пока не
будет выполнено условие);
<тело_цикла> - произвольная последовательность операторов Турбо Паскаля;
<условие> - выражение логического типа.
Операторы <тело_цикла> выполняются хотя бы один раз, после чего вычисляется выражение <условие>: если его значение есть FALSE, операторы <тело_цикла> повторяются, в противном случае оператор REPEAT. . . UNTIL завершает свою работу.
Для иллюстрации применения оператора REPEAT... UNTIL модифицируем программу из примера 2.3. Модификация (пример 2.7) состоит в том, что программа будет все время повторять цикл ввода символа и печати его кода до тех пор, пока очередным символом не будет символ CR (вводится клавишей Enter).
Пример 2.7
Program Codes_of_Chars;
{Программа вводит символ и выводит на экран его код. Для завершения работы программы нужно дважды нажать Enter}
var
ch : Char; {Вводимый символ}
const
CR = 13; {Код символа CR}
begin
repeat
ReadLn(ch);
WriteLn(ch,' = ',ord(ch))
until ord(ch) = CR
end.
Обратите внимание: пара REPEAT... UNTIL подобна операторным скобкам begin. .. end, поэтому перед UNTIL ставить точку с запятой необязательно.
Для гибкого управления циклическими операторами FOR, WHILE и REPEAT в состав Турбо Паскаля включены две процедуры:
BREAK - реализует немедленный выход из цикла; действие процедуры заключается в передаче управления оператору, стоящему сразу за концом циклического оператора;
CONTINUE - обеспечивает досрочное завершение очередного прохода цикла; эквивалент передачи управления в самый конец циклического оператора.
Введение в язык этих процедур практически исключает необходимость использования операторов безусловного перехода GOTO (см. ниже п.2.4.5).
Преобразованя типов и действия над ними
Как уже говорилось, тип переменной позволяет не только устанавливать длину ее внутреннего представления, но и контролировать те действия, которые выполняются над ней в программе. Контроль за использованием переменных еще на этапе компиляции программы - важное преимущество Турбо Паскаля перед другими языками программирования, в которых допускается автоматическое преобразование типов. В Турбо Паскале почти невозможны неявные (автоматические) преобразования типов. Исключение сделано только в отношении констант и переменных типа INTEGER (целые), которые разрешается использовать в выражениях типа REAL (вещественные). Если, например, переменные X и Y описаны следующим образом:
var
х: Integer;
у: Real;
то оператор
у := х + 2;
будет синтаксически правильным: хотя справа от знака присваивания стоит целочисленное выражение, а слева - вещественная переменная, компилятор сделает необходимые преобразования автоматически. В то же время оператор
х := 2.0;
будет неверным, так как автоматическое преобразование типа REAL (константа 2.0 содержит десятичную точку и, следовательно, принадлежит к типу REAL) в тип INTEGER в Турбо Паскале запрещено.
Разумеется, запрет на автоматическое преобразование типов еще не означает, что в Турбо Паскале нет средств преобразования данных. Они, конечно же, есть, но их нужно использовать явно (подробнее об этом см. гл.4). Для преобразования данных в языке существуют встроенные функции, которые получают в качестве параметра значение одного типа, а возвращают результат в виде значения другого типа. В частности, для преобразования REAL в INTEGER имеются даже две встроенные функции такого рода: ROUND округляет REAL до ближайшего целого, a TRUNC усекает REAL путем отбрасывания дробной части.
Например, ошибочным будет оператор
х := у/х;
но правильным
х := round(у/х);
(объявления переменных см. выше).
Понятие функции в Турбо Паскале близко к понятию процедуры. Как и процедура, функция вызывается своим именем и может содержать произвольное число операторов Турбо Паскаля и даже внутренних процедур и функций. Существенным отличием функции от процедуры является то обстоятельство, что функция имеет собственное значение и, следовательно, может использоваться наравне с переменными в выражениях соответствующего типа.
Для преобразования данных типа CHAR (символ) в целое число предназначена функция ORD, обратное преобразование INTEGER в CHAR осуществляет функция CHR.
С помощью следующей несложной программы (пример 2.3) Вы сможете узнать внутренний код произвольного символа.
Пример 2.3
Program Code_pf_Char;
{Программа читает символ с клавиатуры и выводит на экран
этот символ несоответствующий ему внутренний код}
var
ch: Char; {В эту переменную читается символ}
begin
Write('Введите любой символ: ');
ReadLn(ch); {Читаем один символ}
WriteLn(ch, ' = ',ord(ch)); {Преобразуем его к целому и выводим на экран}
END.
Обратите внимание: при вызове
WriteLntch,' = ',ord(ch));
третьим параметром обращения указан вызов функции ORD (СН) , что с точки зрения языка является выражением; как мы увидим дальше (см. гл.8), во многих случаях при вызове процедур и функций в качестве параметров вызова можно указывать не только переменные или константы, но и выражения с их участием.
По мере надобности мы будем знакомиться с другими функциями преобразования типов данных, а сейчас - о тех операциях, которые разрешены над различными типами.
Конечно же, в Турбо Паскале есть все четыре арифметические операции над переменными REAL И INTEGER:
+ - сложение;
- - вычитание;
* - умножение;
/ - деление вещественное;
div - деление целочисленное.
Наличие двух операций деления есть еще одно проявление основополагающего принципа Турбо Паскаля: программист должен явно подтверждать компилятору, что он готов к возможным последствиям преобразования типов. Если, например, в языке Фортран используется выражение 1/2 , то результат этого выражения будет зависеть от того, переменной какого типа он будет присвоен: если N есть переменная целого типа, а Х- вещественного, то в программе на Фортране присваивания
N = 1/2
X = 1/2
дадут значения 0 для N и 0.5 для X. В Турбо Паскале такой двусмысленности нет: выражение 1/2 всегда имеет значение 0.5 и поэтому оператор
var
N :Integer;
begin
N := 1/2;
просто недопустим. В то же время допустимый в Турбо Паскале оператор
var
X : Real;
begin
X := 1 div 2;
самим фактом использования операции целочисленного деления DIV свидетельствует о том, что программист сознательно отбрасывает дробную часть результата. (Надеюсь, что читатель извинит явную искусственность этих примеров, которая вызвана лишь стремлением проиллюстрировать обсуждаемые особенности языка).
Для данных типа INTEGER в Турбо Паскале есть еще одна операция MOD - получение остатка от целочисленного деления. Например:
5 mod 2 = 1
31 mod 16 = 15
18 mod 3 = 0
В Турбо Паскале отсутствует операция возведения в степень, что, очевидно, будет вызывать определенные неудобства при реализации вычислительных алгоритмов. Некоторым утешением может служить наличие встроенной функции SQR, возвращающей квадрат от значения параметра, причем тип результата определяется типом параметра.
И еще об одном существенном недостатке Турбо Паскаля: в нем отсутствуют комплексный тип и соответствующие операции над ним. Вообще, в отношении реализации разнообразных вычислительных процедур Турбо Паскаль значительно уступает некоторым другим языкам программирования, в частности, тому же Фортрану. В частности, в нем намного беднее набор встроенных математических функций (см. гл. 4).
При работе с целыми числами могут оказаться полезными две процедуры (здесь и далее в квадратных скобках указываются необязательные параметры):
DEC (X [, N] ) - уменьшает содержимое переменной X на значение выражения N (если N не задано, то на 1); тип переменной X и выражения N - INTEGER (точнее, любой целый, см. гл. 4);
INC (X [, N] ) - увеличивает значение X на N (если N не задано, то на 1).
Над символами и строками символов определена единственная операция - сцепление двух строк. Операция обозначается символом «+». Например, программа
var
st: String;
begin
st := 'Турбо'+'-'+'Паскаль';
WriteLn(st);
end.
напечатает строку
Турбо-Паскаль
Все остальные действия над строками и символами реализуются с помощью встроенных процедур и функций (см. гл.4).
И, наконец, об операциях отношения и логических операциях.
Над данными типа REAL, INTEGER, CHAR, STRING определены следующие операции отношения (сравнения):
= - равно;
<> - не равно;
< - меньше;
> - больше;
<= - меньше или равно,
>= - больше или равно.
В операциях сравнения должны участвовать однотипные операнды. Исключение сделано опять-таки в отношении REAL и INTEGER, которые могут сравниваться друг с другом. Результат применения операции отношения к любым операндам имеет тип BOOLEAN.
Сравнение двух строк осуществляется следующим образом. Символы строк сравниваются попарно друг с другом так, что первый символ первой строки сравнивается с первым символом второй строки, второй символ первой строки - со вторым символом второй и т.д. Символы сравниваются путем сравнения их кодов во внутреннем представлении (см. гл. 4). Если одна строка короче другой, недостающие символы заменяются нулем. Отношение первой несовпадающей друг с другом пары символов и принимается за отношение двух строк.
При сравнении данных типа BOOLEAN учитывается внутреннее соглашение Турбо Паскаля, в соответствии с которым FALSE есть нулевой байт, a TRUE - байт с единицей в младшем разряде. Заметим, что функция ORD преобразует к целому не только символы, но и логические величины, поэтому
ord(false) = 0,
ord(true) = 1.
В Турбо Паскале определены следующие логические операции:
not - логическое НЕ; or - логическое ИЛИ;
and - логическое И; хоr - исключающее ИЛИ.
Логические операции применимы к операндам целого и логического типов. Если операнды - целые числа, то результат логической операции есть тоже целое число (подробнее об этом сказано в гл.4). Логические операции над логическими данными дают результат логического типа.
При вычислении выражений любого типа приоритет вычислений определяется расставленными скобками, а при их отсутствии - по табл. 2.1 (в порядке убывания приоритета).
Таблица 2.1 Приоритет операций
Примечание. Операции @ (получение адреса), shl (сдвиг влево), shr (сдвиг вправо) и in (принадлежность к множеству) описаны в гл.4.
Следует учесть, что в отличие от многих других языков программирования в Турбо Паскале логические операции имеют более высокий приоритет, чем операции отношения. В связи с этим, в сложных логических выражениях обычно необходимо расставлять скобки. Если, например, b и с имеют тип INTEGER , то выражение
а = b and с < d
вызовет сообщение о синтаксической ошибке, так как сначала выполнится операция b and с. Правильным будет выражение:
(а = b) and (с < d)
Примеры программ
Мы познакомились с основными возможностями языка Турбо Паскаль. Как видите, ядро языка очень компактно и отличается простотой - именно в этом состоит главная заслуга автора Паскаля Н.Вирта: язык, придуманный им, прост и естественен, он легко осваивается, на нем не трудно писать самые разнообразные программы. Конечно, рассмотрены далеко не все свойства Турбо Паскаля, ведь его главная отличительная черта - это богатство типов данных. Однако уже рассмотренного вполне достаточно для написания многих полезных программ.
Приводимые ниже программы относительно сложны, поэтому они реализуются поэтапно, по методу нисходящего программирования. Мне кажется, что тем читателям, кто не имеет большого опыта в программировании или кто захочет подробнее ознакомиться с нисходящим программированием, изучение этой главы принесет определенную пользу. Если Вам будет скучно разбираться в «кухне» программирования, но Вас заинтересуют описываемые здесь программы и Вы захотите их повторить, то в прил.5 Вы найдете полный текст соответствующей программы; однако в каждой из них используются некоторые дополнительные возможности языка Турбо Паскаль, которые не рассматривались ранее и которые обсуждаются в пропущенных Вами фрагментах книги.
При оформлении программ я стремился использовать хороший стиль написания программ, т.е. такую их форму, которая дает наиболее полное представление о структуре программы в целом и ее отдельных частей. Не существует какого-либо стандарта, определяющего хороший стиль программы. Обычно это интуитивное понятие включает способ расположения операторов и описаний по строкам (не рекомендуется размещать более одного оператора на каждой строке), а также выделение отступами тела составных и условных операторов. Последнее особенно важно в программах Турбо Паскаля: сплошь и рядом в них встречаются операторные скобки begin... end, причем часто вложенные друг в друга; использование отступа служит дополнительным средством проверки правильности их расстановки - не случайно в редакторе среды предусмотрена соответствующая опция. Принятый мною стиль оформления программ не претендует на эталон, просто мне кажется, что таким образом оформленные программы читаются лучше. Если Вы всерьез намерены программировать на Турбо Паскале, имеет смысл составить собственное представление о хорошем стиле и далее неукоснительно придерживаться его - очень скоро некоторые дополнительные издержки на подготовку программ с лихвой окупятся их «читабельностью», а это поможет Вам вспомнить все детали реализации программы, которая была написана несколько месяцев тому назад.
Процедуры и функции
Процедуры и функции представляют собой важный инструмент Турбо Паскаля, позволяющий писать хорошо структурированные программы. В структурированных программах обычно легко прослеживается основной алгоритм, их нетрудно понять любому читателю, они проще в отладке и менее чувствительны к ошибкам программирования. Все эти свойства являются следствием важной особенности процедур (функций), каждая из которых представляет собой во многом самостоятельный фрагмент программы, связанный с основной программой лишь с помощью нескольких параметров. Самостоятельность процедур (функций) позволяет локализовать в них все детали программной реализации того или иного алгоритмического действия и поэтому изменение этих деталей, например, в процессе отладки обычно не приводит к изменениям основной программы.
Многие примеры в этой книге невелики по размерам (не более 30-40 строк), поэтому написать такие программы можно и без процедур. Иное дело - создание крупных программ в сотни, тысячи и десятки тысяч строк. Писать такие программы как нечто единое целое, без расчленения на относительно самостоятельные фрагменты, т.е. без структурирования, просто невозможно. Практически во всех языках программирования имеются средства структурирования. Языки, в которых предусмотрены такие механизмы, называются процедурно-ориентированными. К их числу принадлежит и Турбо Паскаль.
Процедурой в Турбо Паскале называется особым образом оформленный фрагмент программы, имеющий собственное имя. Упоминание этого имени в тексте программы приводит к активизации процедуры и называется ее вызовом. Сразу после активизации процедуры начинают выполняться входящие в нее операторы, после выполнения последнего из них управление возвращается обратно в основную программу и выполняются операторы, стоящие непосредственно за оператором вызова процедуры (рис.2.2).
Рис.2.2. Взаимодействие вызывающей программы и процедуры
Для обмена информацией между основной программой и процедурой используется один или несколько параметров вызова. Как мы увидим дальше (см. гл. 8), процедуры могут иметь и другой механизм обмена данными с вызывающей программой, так что параметры вызова могут и не использоваться. Если они есть, то они перечисляются в круглых скобках за именем процедуры и вместе с ним образуют оператор вызова процедуры.
Функция отличается от процедуры тем, что результат ее работы возвращается в виде значения этой функции, и, следовательно, вызов функции может использоваться наряду с другими операндами в выражениях.
С примерами процедур и функций мы уже сталкивались - это стандартные процедуры чтения И записи READ, READLN, WRITE, WRITELN, функции ORD, CHR, математические функции и др. Стандартными они называются потому, что созданы одновременно с системой Турбо Паскаль и являются ее неотъемлемой частью. В Турбо Паскале имеется много стандартных процедур и функций. Наличие богатой библиотеки таких программных заготовок существенно облегчает разработку прикладных программ. Однако в большинстве случаев некоторые специфичные для данной прикладной программы действия не находят прямых аналогов в библиотеках Турбо Паскаля, и тогда программисту приходится разрабатывать свои, нестандартные процедуры и функции.
Нестандартные процедуры и функции необходимо описать, чтобы компилятор мог установить связь между оператором вызова и теми действиями, которые предусмотрены в процедуре (функции). Описание процедуры (функции) помещается в разделе описаний и внешне выглядит как программа, но вместо заголовка программы фигурирует заголовок процедуры (функции).
Не вдаваясь в дальнейшие подробности, попробуем составить собственную процедуру, чтобы пояснить сказанное. Пусть в этой процедуре преобразуется некоторая символьная строка таким образом, чтобы все строчные буквы заменялись соответствующими прописными. В Турбо Паскале имеется стандартная функция UPCASE (см. гл.4), которая выполняет аналогичные действия над одиночным символом. Наша процедура (назовем ее UPSTRING) будет преобразовывать сразу все символы строки, причем сделаем ее пригодной не только для латинских букв, но и для букв русского алфавита.
Разработку программы проведем в два этапа. Сначала сконструируем основную (вызывающую) часть программы. Ее действия очень просты: она должна ввести входную строку (назовем ее Sinp) с клавиатуры, преобразовать ее с помощью процедуры UpString в выходную строку Sout и напечатать результат. Эти действия нетрудно запрограммировать, например:
Program CharsConvert;
Procedure UpString(si: String; var s2: String);
begin {UpString}
s2 := s1 {Пока еще нет преобразования!}
end; {UpString}
var
Sinp, Sout : String; {Исходная и преобразованная строки}
begin {Начало основной (вызывающей) программы}
Write('Введите строку: ');
ReadLn(Sinp); {Вводим исходную строку}
UpString(Sinp,Sout); {Преобразуем ее.к прописным буквам}
WriteLn (' Результат: ',Sout) {Печатаем результат}
end. {Конец вызывающей программы}
В этой программе используется замещение процедуры UPSTRING так называемой «заглушкой», т.е. процедурой, в которой на самом деле не осуществляется нужных нам действий, а выходная строка просто копирует входную. (Однако эта программа синтаксически абсолютно правильна и при желании ее можно запустить на счет.) Заглушка понадобилась нам по двум причинам. Во-первых, приведенная программа очень проста, в ней отсутствует детальная реализация процедуры и это позволяет наглядно проиллюстрировать механизм ее описания. Во-вторых, на ее примере мы знакомимся с универсальным методом конструирования сложных программ, получившим название нисходящее программирование. В соответствии с этим методом создание программы начинается «сверху», т.е. с разработки самого главного, генерального алгоритма. На верхнем уровне обычно еще не ясны детали реализации той или иной части программы, поэтому эти части следует заменить временными заглушками. Желательно, чтобы временный вариант программы был синтаксически правильным, тогда можно его откомпилировать и убедиться в отсутствии в нем синтаксических ошибок. Такой прогон даст определенную уверенность перед разработкой и реализацией алгоритмов нижнего уровня, т.е. перед заменой заглушек реально работающими процедурами. Если реализуемый в заглушке алгоритм достаточно сложен, его вновь структурируют, выделяя главный алгоритм и применяя новые заглушки, и т.д. Процесс продолжается «вниз» до тех пор, пока не будет создан полностью работоспособный вариант программы.
В дальнейшем мы еще не раз будем использовать метод нисходящего программирования, а сейчас вернемся к описанию нашей процедуры. Как видим, это описание начинается зарезервированным словом Procedure, за которым следуют имя процедуры и список формальных параметров. Список параметров заключается в круглые скобки и содержит перечень параметров с указанием их типа. Заметим, что перед параметром s2, с помощью которого в вызывающую программу возвращается результат преобразования, стоит зарезервированное слово VAR. Именно таким способом компилятору указываются те параметры, в которых процедура возвращает вызвавшей ее программе результат своей работы (подробнее см. гл. 8). Зарезервированное слово Procedure, имя процедуры и список ее параметров образуют заголовок процедуры. За заголовком следует тело процедуры, содержащее новый раздел описаний (этот раздел пока еще пуст) и раздел исполняемых операторов (оператор s2 : = s1).
Приступим к разработке алгоритма процедуры. Для этого обратимся к таблице кодировки символов, используемой в ПК (см. прил. 2). В соответствии с этой таблицей коды символов латинских строчных букв от а до z образуют непрерывный массив монотонно нарастающих чисел от 97 до 122, а коды соответствующих им прописных букв - непрерывный массив чисел от 65 до 90. Преобразование строчных латинских букв в прописные, следовательно, состоит в уменьшении кода буквы на 32. Сложнее обстоит дело с символами русского алфавита (кириллицей). В зависимости от принятого способа кодировки русские строчные буквы могут образовывать один сплошной массив (кодировки ГОСТ и MIC), два массива (альтернативная кодировка), несплошной массив (кодировка типа ЕСТЕЛ), неупорядоченный массив (кодировка КОИ-8). Если исключить два последних варианта кодировки, использовавшихся на устаревших ПК, то задача преобразования буквы состоит в том, чтобы к внутреннему коду русской буквы А (для букв от а до п) или к коду буквы Р (для букв от р до я) прибавить разницу в кодах текущего символа и кодах букв а и и. Например, если преобразуется
буква б, то к коду А нужно прибавить разницу между кодами а и б, т.е. единицу, в результате получим код буквы Б. Точно так же при преобразовании буквы ф к коду буквы П будет прибавлено число 5 (как разница кодов ф и п), поэтому в результате получится код буквы Ф. С учетом этого можно составить следующий алгоритм реализации процедуры: для каждого символа исходной строки s1 определить, к какому подмассиву a...z, a...p или п...я принадлежит код этого символа, и затем изменить его, добавив к кодам букв А (латинская), А (русская) или Я соответствующую разницу. Если символ не принадлежит ни к какому из подмассивов, нужно поместить его код в выходную строку без изменений.
Вот возможный вариант процедуры:
Procedure UpString(sl: String; var s2: String);
var
i: Integer; {Счетчик цикла преобразования}
с: Char; {Рабочая переменная преобразования}
begin {UpString}
s2 := ' '; {Вначале выходная строка пуста}
{Цикл посимвольного анализа исходной строки}
for i := 1 to Length(si) do
begin
{ Берем из входной строки очередной символ}
с := sl[i];
{Проверяем символ на принадлежность к одному из трех подмассивов}
if (с >= 'a') and (с <= 'z') then
с := chr(ord('A')+ord(c)-ord('a1)) {А,а - латинские!}
else if (c >= 'a') and (c <= 'n') then
с := chr(ord('A')+ord(с)-ord('a')) {А,а - русские!}
else if (c >= 'p') and (с <= 'я') then
с := chr(ord('PI)+ord(c)-ord('p'));
s2 := s2+c
end
end; {UpString}
В процедуре вначале с помощью оператора
s2 := '';
подготавливается «пустая» выходная строка, т.е. строка нулевой длины. Затем используется цикл от 1 до длины входной строки s1 (эта длина получается с помощью стандартной функции Length), в ходе которого проверяется принадлежность очередного символа указанным подмассивам и осуществляется необходимая коррекция его внутреннего кода. Для доступа к отдельным символам строки используется замечательное свойство типа данных STRING, позволяющее рассматривать строку как набор (массив) символов. Первый символ этого набора имеет индекс 1, второй - 2 и т.д. Индекс указывается сразу за именем строки в квадратных скобках. Таким образом, s1 [i] -это i-ый символ строки s1. Преобразованный символ добавляется в конец выходной строки.
Добавив комментарии и поместив тело процедуры вместо заглушки в первоначальный вариант программы, получим окончательно ее рабочий вариант (пример 2.10).
Пример 2.10
Program CharsConvert;
{Программа вводит произвольную текстовую строку, преобразует все входящие в нее буквы в прописные и печатает результат преобразования}
PROCEDURE UpString(sl : String; var s2 : String);
{Эта процедура преобразует буквы входной строки si в прописные буквы латинского или русского алфавита и помещает результат преобразования в выходную строку s2. Используется предположение о том, что последовательности латинских букв от «а» до «z» и русских букв. от «а» до «п» и от «р» до «я», а также последовательности соответствующих им прописных букв образуют непрерывные массивы}
var
i: Integer; {Счетчик цикла преобразования}
с: Char; {Рабочая переменная преобразования}
begin {UpString}
s2 :=' ' ; {Вначале выходная строка пуста}
{ Цикл посимвольного анализа исходной строки}
for i := 1 to Length(s1) do
begin
{Берем из входной строки очередной символ}
с := s1[i] ;
{Проверяем символ на принадлежность к одному из трех подмассивов}
if (с >= 'a') and (с <= 'z') then
с := chr(ord('А')+ord(c)-ord('a')) {A,a - латинские!}
else if (c >= 'a') and (c <= 'n') then
с := chr(ord('A')+ord(c)-ord('a')) {A,a -русские!}
else if (c >= 'p') and (с <= 'я') then
с := chr(ord('P')+ord(c)-ord('p'));
s2 := s2+c
end
end; {UpString}
var
Sinp, Sout : String; {Исходная и преобразованная строки}
begin {Начало основной (вызывающей) программы}
Write('Введите строку: ');
ReadLn(Sinp); {Вводим исходную строку}
UpString(Sinp,Sout); {Преобразуем ее к прописным буквам}
WriteLn(' Результат: ',Sout) {Печатаем результат}
end. {Конец вызывающей программы}
Рассмотрим иной способ реализации той же программы: оформим алгоритм преобразования в виде функции. Кроме того, с помощью стандартной функции UPCASE преобразуем каждый очередной символ (это преобразование осуществляется только для букв латинского алфавита) и тем самым исключим проверку принадлежности символа к строчным латинским буквам:
Function UpString(s1: String): String;
var
i : Integer; с : Char;
s2: String; {Результат преобразования}
begin {UpString}
s2 := ' ';
for i := 1 to Length(si) do
begin
{Получаем и преобразуем очередной символ}
с := UpCase(si [i]);
if (с >= 'a') and (с <= 'п') then
с := chr(ord('A')+orcKcJ-ord('a'))
else
if (c >= 'p') and (с <= 'я') then
с := chr(ord('P')+ord(c)-ord('p'));
s2 := s2+c
end;
UpString := s2 {Присваиваем значение функции UpString}
end; {UpString}
var
Sinp: String;
begin {Начало основной программы}
Write('Введите строку: ') ;
ReadLn(Sinp);
WriteLn(' Результат: ',UpString(Sinp))
end. {Конец основной программы}
Программа получилась несколько проще за счет того, что функцию можно использовать в качестве параметра обращения к другой процедуре (в нашем случае к WriteLn). Обратите внимание: в теле любой функции нужно осуществить присваивание ей вычисленного значения (см. оператор UpString := s2). В левой части оператора присваивания в этом случае указывается имя функции.
Составной оператолр и пустой оператор
Составной оператор - это последовательность произвольных операторов программы, заключенная в операторные скобки - зарезервированные слова begin . . . end. Составные операторы - важный инструмент Турбо Паскаля, дающий возможность писать программы по современной технологии структурного программирования (без операторов перехода GOTO).
Язык Турбо Паскаль не накладывает никаких ограничений на характер операторов, входящих в составной оператор. Среди них могут быть и другие составные операторы - Турбо Паскаль допускает произвольную глубину их вложенности:
begin
.......
begin
.......
begin
......
......
end;
......
end;
......
end;
Фактически, весь раздел операторов, обрамленный словами begin . . . end, представляет собой один составной оператор. Поскольку зарезервированное слово end является закрывающей операторной скобкой, оно одновременно указывает и конец предыдущего оператора, поэтому ставить перед ним символ «;» необязательно, и далее во всех примерах мы не будем этого делать. Наличие точки с запятой перед end в предыдущих примерах означало, что между последним оператором и операторной скобкой end располагается пустой оператор. Пустой оператор не содержит никаких действий, просто в программу добавляется лишняя точка с запятой. В основном пустой оператор используется для передачи управления в конец составного оператора.
Типы данных
Структура рассмотренной программы имеет следующий вид:
Program MyFirstProgram;
{Раздел описаний}
begin
{Раздел операторов}
end.
Слова Program, begin и end выделяют две части программы - раздел описаний и раздел операторов. Такая структура обязательна для любой программы, что является следствием жесткого требования языка: любой нестандартный идентификатор, используемый в исполняемых операторах, должен быть предварительно описан в разделе описаний. (Стандартные идентификаторы связаны с предварительно объявленными объектами и входят в стандартную библиотеку Турбо Паскаля. Таким, например, является идентификатор WriteLn. Стандартные идентификаторы, если они используются в программе, описывать не нужно).
Требование предварительного описания идентификаторов кажется чрезмерно строгим и делающим язык менее свободным. На самом деле в нем проявляется тенденция развития языков программирования в сторону повышения надежности создаваемых программ. Кто программировал на Фортране или Бэйсике (в этих языках не требуется предварительное описание идентификаторов), знает, как порой бывает трудно обнаружить в большой программе ошибочно введенный или пропущенный символ в идентификаторе. Если, например, всюду в программе используется переменная с именем EPSILON, а в одном месте ошибочно написано EPSLION, то программа может благополучно откомпилироваться и даже давать почти правдоподобный результат для некоторых наборов данных, но в какой-то момент начнет вести себя странно. Обязательное предварительное описание идентификаторов в Турбо Паскале защищает программы от такого рода ошибок и повышает их надежность.
Описать идентификатор - это значит указать тип связанного с ним объекта программы (константы или переменной). Понятие типа - одно из фундаментальных понятий Турбо Паскаля. В гл.4 подробно рассмотрены различные типы; чтобы пояснить описываемые ниже особенности языка и при этом не слишком забегать вперед, укажем, что тип определяет, во-первых, способ внутреннего для компьютера представления объекта и, во-вторых, действия, которые разрешается над ним выполнять.
В рассматриваемых далее в этой главе программах понадобятся следующие типы данных:
INTEGER - целочисленные данные, во внутреннем представлении занимают 2 байта; диапазон возможных значений - от -32768 до +32767; данные представляются точно;
REAL - вещественные данные, занимают 6 байт; диапазон возможных значений модуля - от 2.9Е-39 до 1.7Е+38; точность представления данных - 11...12 значащих цифр;
CHAR - символ, занимает 1 байт;
STRING - строка символов, занимает МАХ+1 байт, где МАХ - максимальное число символов в строке;
BOOLEAN - логический тип, занимает 1 байт и имеет два значения: FALSE
(ложь) и TRUE (истина).
Тип константы определяется способом записи ее значения. Например:
const
cl = 17;
с2 = 3 .14 ;
сЗ = 'А';
с4 = '3.14 ' ;
с5 = False;
При анализе этого фрагмента программы компилятор отнесет первую константу к типу INTEGER, вторую - к типу REAL, третью - к CHAR, четвертую - к STRING и последнюю - к BOOLEAN. Признаком, позволяющим отнести константу к REAL или к INTEGER, является наличие или отсутствие десятичной точки в ее значении. Разумеется, константы С2 и С4 относятся к разным типам: С2 - к REAL (в константе есть десятичная точка), а С4 - к STRING (константа обрамлена апострофами). Константу СЗ компилятор будет считать относящейся к типу CHAR: одиночный символ в апострофах относится к CHAR, в то время как несколько символов - к STRING.
В отличие от константы переменная именует объект программы, который может изменять свое значение в ходе счета. При описании переменных за идентификатором ставятся двоеточие и имя типа. Несколько однотипных переменных можно объединять в список, разделяя их запятыми. В начале раздела описания переменных должно стоять зарезервированное слово VAR (VARiables - переменные). Например:
var
sigma :Real; а,b,с,d :Char;
textl :String[15];
text2 :String;
flag :Boolean;.
Как уже говорилось, тип данных определяет длину внутреннего представления соответствующих переменных. В частности, длина внутреннего представления переменных типа STRING (строка символов) зависит от максимального числа символов, которые могут составлять строку. В приведенном выше примере переменная text l описана с указанием ее максимальной длины (15 символов), а в описании переменной text2 максимальная длина не указана и компилятор установит для нее предельно допустимую в Турбо Паскале длину - 255 символов.
Рассмотрим еще одну несложную программу (пример 2.2). Ее назначение: ввести с клавиатуры два целых числа, найти результат деления первого числа на второе и вывести полученный результат на экран.
Пример 2.2
Program Input_0utput; {Программа вводит два целых числа
и выводит частное от деления 1-го на 2-е}
var
n1,n2 : Integer; {n1 и n2 - вводимые целые}
х : Real;{x - результат}
BEGIN
Write( 'n1 = ');{Сообщаем о вводе n1}
ReadLn (n1) ;{Вводим n1}
Write ( 'n2 = ');{Сообщаем о вводе п2}
ReadLn (n2);{Вводим п2}
x := n1/n2;{Находим результат}
WriteLn('n1/n2 =',х);{Выводим его}
END.
Прежде всего бросается в глаза появление в программе поясняющих комментариев. Комментарий в Турбо Паскале - это произвольная последовательность любых символов, обрамленная фигурными скобками. Комментарий разрешается вставлять в любое место программы, где по смыслу может стоять пробел. В качестве ограничителей комментария допускается использование фигурных скобок «{» и «}», а также пары символов: «(*» - слева от комментария и «*)» - справа от него:
{ Это - комментарий }
(* Это - тоже комментарий *)
Редактор Турбо Паскаля выделяет комментарии наклонным шрифтам (курсивом).
Комментарии с однотипными ограничителями нельзя вкладывать друг в друга, т.е. недопустимы последовательности вида
{...{...}...} или (*...(*...*)...*)
Однако можно вкладывать комментарии с ограничителями разных типов (не более одной глубины вложения):
{ ... (* ...*)...} или (* ... { ... } ... *)
Последнее обстоятельство проясняет кажущуюся странной избыточность ограничителей: если всюду в программе будут использоваться ограничители одного типа, то для того, чтобы временно исключить из программы какой-либо фрагмент текста, достаточно заключить его в ограничители другого типа.
Наличие комментариев в программе избавляет меня от необходимости пояснять назначение отдельных строк программы. Несколько слов о вводе данных. Пары операторов
Write (..);
ReadLn(..);
работают следующим образом. Вначале оператор Write выводит строку на экран и оставляет курсор в конце только что выведенной строки текста. Заметим, что оператор
WriteLn(Text);
в примере 1 после вывода текста осуществлял перевод строки и устанавливал курсор в начало следующей строки экрана. Именно в этом простом действии (переводе строки) заключается единственное отличие в работе процедуры WriteLn от процедуры
Write.
Затем по оператору ReadLn вызывается встроенная процедура ввода данных и программа останавливается в ожидании ввода. В этот момент необходимо набрать на клавиатуре нужное число и нажать клавишу Enter. Сразу после этого программа продолжит работу: проанализирует введенное число и перейдет к вводу следующего числа или вычислению результата. Таким образом, сигналом окончания подготовки очередного числа является нажатие на клавишу Enter, до этого момента можно стирать любой ошибочно введенный символ клавишей Backspace.
Для вычисления отношения введенных чисел используется один из основных операторов Турбо Паскаля - оператор присваивания. В его левой части указывается имя переменной, правая часть представляет собой выражение того же типа, что и переменная. Пара символов «: =», связывающая левую и правую части оператора присваивания, означает «присвоить значение». Запомним: в операторах присваивания Турбо Паскаля всегда используются символы «: =», в то время как при описании констант -одиночный символ «=». С точки зрения синтаксиса языка, два символа «: =» рассматриваются как один специальный символ и обязательно пишутся слитно.
Оператор присваивания используется практически во всех языках программирования. В некоторых языках, например в Фортране или Бейсике, символом присваивания является знак равенства, однако новичка, привыкшего к строгости математических формул, может озадачить типичная форма записи фортран-оператора присваивания, например, такая:
X = X + 1
Вариант записи этого же оператора на Турбо Паскале:,
X := X + 1;
в этом смысле кажется более логичным. Разумеется, вряд ли кому-нибудь придет в голову видеть уравнения там, где их нет и не может быть. Конечно же, и в том, и в другом случае реализуется одно и то же алгоритмическое действие: к содержимому X прибавляется 1 и полученный результат вновь присваивается переменной X. Обратите внимание на оператор вывода результатов
WriteLn('n1/n2 = ',х);
В нем в качестве одного из параметров явно указывается константа типа строка символов 'nl/n2 = '. Конечно же, константы (в отличие от переменных) вовсе не обязательно описывать в разделе описаний, так как их тип легко определяется компилятором по форме записи константы. С учетом этого можно было бы записать программу из примера 1 предельно лаконично:
begin WriteLn('Я программирую на Турбо Паскале'); end.
Условный оператор
Условный оператор позволяет проверить некоторое условие и в зависимости от результатов проверки выполнить то или иное действие. Таким образом, условный оператор - это средство ветвления вычислительного процесса.
Структура условного оператора имеет следующий вид:
IF <условие> THEN <оператор1> ELSE <оператор2>,
где IF, THEN, ELSE - зарезервированные слова (если, то, иначе); <условие> - произвольное выражение логического типа; <оператор1>, <оператор2> - любые операторы языка Турбо Паскаль.
Условный оператор работает по следующему алгоритму. Вначале вычисляется условное выражение <условие>. Если результат есть TRUE (истина), то выполняется <оператор1>, а <оператор2> пропускается; если результат есть FALSE (ложь), наоборот, <оператор1> пропускается, а выполняется <оператор2>. Например:
var
х, у, max: Integer;
begin
.......
if x > max then
у := max else
y := x;
При выполнении этого фрагмента переменная Y получит значение переменной X, если только это значение не превышает МАХ, в противном случае Y станет равно МАХ.
Часть ELSE <оператор2> условного оператора может быть опущена. Тогда при значении TRUE условного выражения выполняется <оператор1>, в противном случае этот оператор пропускается:
var
х, у, max: Integer;
begin
.......
if x > max then
max := x;
Y := x;
В этом примере переменная Y всегда будет иметь значение переменной X,а в МАХ запоминается максимальное значение X.
Поскольку любой из операторов <оператор1> и <оператор2> может быть любого типа, в том числе и условным, а в то же время не каждый из «вложенных» условных операторов может иметь часть ELSE <оператор2>, то возникает неоднозначность трактовки условий. Эта неоднозначность в Турбо Паскале решается следующим образом: любая встретившаяся часть ELSE соответствует ближайшей к ней «сверху» части THEN условного оператора. Например:
var
a,b,c,d : Integer; begin
a := 1; b := 2; c= 3; d := 4;
if a > b then
if с < d then
if с < 0 then
с := 0 else
a := b; {а равно 1}
if a > b then
if с then
if с then
с := 0
else
else
else
a := b; {а равно 2}
Рассмотрим программу (пример 2.4), которая вводит произвольное десятичное целое число в диапазоне 0...15, преобразует его к шестнадцатеричному и выводит на экран полученный результат.
Пример 2.4
Program Hex;
{Программа вводит с клавиатуры целое число в диапазоне от 0 до 15, преобразует его к шестнадцатеричной системе счисления и выводит результат на экран}
var
n : Integer; {Вводимое число}
ch : Char; {Результат}
begin
Write ( ',n = ' ) ;
ReadLn(n); { Вводим число }
{Проверяем число на принадлежность к диапазону 0...15}
if (n >= 0) and (n <= 15) then
begin {Да, принадлежит диапазону}
if n < 10 then
ch := chr(ord('0') + n)
else
ch := chr(ord('A') + n- 10);
WriteLn('n = ',ch)
end
else {He принадлежит диапазону}
WriteLn('Ошибка')
end.
В шестнадцатеричной системе счисления используется 16 цифр в каждом разряде: цифры 0...9 обозначают первые 10 возможных значений разряда, буквы A...F - остальные шесть.
В программе учитывается непрерывность и упорядоченность множеств цифр 0...9, букв A...F и их кодов (см. гл.4).
Ваша первая программа
Для знакомства с языком Турбо Паскаля попробуем составить несложную программу, осуществляющую вывод какого-либо сообщения на экран ПК. Пусть это будет фраза «Я программирую на Турбо Паскале». Вот возможный вариант такой программы:
Пример 2.1
Program My_First_Program;
const
Text = 'Я программирую на Турбо Паскале';
begin
WriteLn(Text);
end.
Прежде всего проанализируем форму представления текста. В программе шесть строк. Строки программы обычно выделяют некоторые смысловые фрагменты текста и могут не связываться с конкретными действиями в программе: расположение текста программы по строкам - дело вкуса программиста, а не требование синтаксиса языка. Ту же программу можно было бы написать, например, так:
Program My_First_Program; const Text =
'Я программирую на Турбо Паскале';begin WriteLn(Text); end.
В отличие от некоторых других языков программирования пробел в языке Турбо Паскаль используется как разделитель отдельных конструкций языка, поэтому программа
PROGRAMMy_First_Program;constText=
'Я программирую на Турбо Паскале';BEGINWriteLn(Text);end.
будет неверной.
В Турбо Паскале игнорируется различие в высоте букв (заглавные или строчные), если только это не связано с текстовыми константами. Начало программы могло бы, например, выглядеть так:
program my_first_program;
Теперь о смысле отдельных строк. Первая строка
Program My_First_Program;
начинается словом Program и содержит объявление имени программы. Слово Program зарезервировано в Турбо Паскале, т.е. не может использоваться ни в каких иных целях, кроме как для объявления имени программы. В Турбо Паскале имеется множество зарезервированных слов (см. гл.З). Любое из них нельзя использовать в качестве идентификатора (имени) какого-либо объекта программы - переменной, константы и т.д. Замечу, что редактор среды Турбо Паскаля обычно выделяет зарезервированные слова цветом. В связи с этим в тексте книги эти слова выделены жирным шрифтом. Поскольку имя программы никак в дальнейшем не используется, требование его объявления кажется излишним. В Турбо Паскале можно опускать объявление имени оператором Program без каких-либо последствий для программы.
В рассматриваемом примере имя My_First_Program есть не что иное, как английская фраза «Моя Первая Программа», но только написанная без пробелов - пробел является разделителем и не может использоваться произвольно (вместо пробелов в идентификаторах разрешается использовать символ подчеркивания).
Первая строка заканчивается особым разделителем - точкой с запятой. Этот разделитель в языке Турбо Паскаль отмечает конец оператора или описания. Использование особого разделителя позволяет располагать несколько операторов на одной строке.
Вторая строка
const
содержит единственное зарезервированное слово const, означающее, что далее будут описаны одна или несколько констант (CONSTants - константы). Константами в языке считаются такие объекты программы, которые не могут изменять своего значения. В отличие от многих других языков программирования, константа в Турбо Паскале может иметь собственное имя, что соответствует принятой в научных и инженерных расчетах практике именования часто используемых констант. Например, со школы мы помним о существовании константы п—3.14159265. При обработке программы имя константы pi будет заменяться компилятором на ее значение.Описать константу в Турбо Паскале - значит указать ее имя и значение. Такое указание содержится в третьей строке
Text = 'Я программирую на Турбо Паскале';
в которой константе с именем Text присваивается в качестве значения строка символов «Я программирую на Турбо Паскале».
В Турбо Паскале могут использоваться константы разного типа - целые или вещественные числа, символы, строки символов, массивы и т.д. Признаком того, что Text является константой типа строка символов, служат два апострофа, обрамляющих строку, причем сами апострофы этой строке не принадлежат, а лишь указывают компилятору на то, что все заключенные в них символы следует рассматривать как единое целое - текстовую константу. Если понадобится включить сам апостроф в текстовую константу, достаточно его написать дважды подряд. Например, описание
Text = 'Турбо' 'Паскаль';
создаст константу со значением
Турбо'Паскаль
Все три первые строки не связаны с какими-либо конкретными действиями при работе программы. Они сообщают компилятору некоторые сведения о самой программе и использующихся в ней объектах. Эта часть программы называется разделом описаний. Зарезервированное слово begin в четвертой строке сигнализирует компилятору о начале другой части программы - раздела операторов. В нашем примере этот раздел содержит оператор
WriteLn(Text);
который, собственно, и выводит сообщение на экран компьютера.
Завершает всю программу зарезервированное слово end с точкой. Точка оповещает компилятор о конце текста программы. За сочетанием end. можно размещать какой угодно текст - он не будет обрабатываться компилятором.
Перед тем как попробовать откомпилировать и исполнить нашу программу, обсудим ее единственный исполняемый оператор
WriteLn(Text);
Любопытно, что в Паскале вообще и Турбо Паскале, в частности, нет специальных операторов ввода-вывода. Для обмена информацией с окружающим миром в программах, написанных на языке Турбо Паскаль, используются специальные стандартные процедуры. Таким образом, по своей сути оператор
WriteLn(Text);
является оператором обращения к встроенной процедуре вывода данных (свое название она получила от WRITE LiNe - записать строку).
Понятие процедуры (см. гл.8) - одно из центральных понятий Турбо Паскаля. Процедура - это некоторая последовательность операторов, к которой можно обратиться по имени. Всякий раз, когда мы называем в операторе имя процедуры, инициируется последовательность запрограммированных в ней действий.
Процедура WriteLn относится к стандартным или встроенным процедурам Турбо Паскаля. Стандартная процедура не нуждается в предварительном описании, она доступна любой программе, в которой содержится обращение к ней. Разница между оператором вывода и обращением к процедуре вывода состоит в том, что имя процедуры вывода, как и любой другой процедуры Турбо Паскаля, не является зарезервированным словом, а следовательно, пользователь может написать свою собственную процедуру с именем WriteLn. Впрочем, эта возможность для большинства пользователей остается лишь языковой тонкостью и очень редко используется на практике.
Процедура WriteLn - одна из немногих процедур Турбо Паскаля, при обращении к которым допускается использование произвольного числа параметров. Параметры передаются процедуре в виде списка, располагающегося в круглых скобках сразу за именем процедуры. В нашем примере процедуре передается единственный параметр -константа Text. Как мы увидим дальше (см. гл.5), самым первым параметром при обращении к процедуре WriteLn можно указать адрес приемника информации - устройство или дисковый файл, в который направляется вывод. Таким способом программист может легко переадресовать вывод данных. Если, как это сделано в нашем примере, адрес вывода не указан, вывод направляется на экран дисплея.
Анализируя всю программу в целом, мы обнаружим, что четыре использовавшихся в ней слова (Program, const, begin и end) являются зарезервированными. Слово WriteLn, как уже отмечалось, не относится к зарезервированным, но вряд ли может возникнуть необходимость переопределить его, так как в этом случае программа лишится мощного и удобного средства вывода данных. Два слова My_First_Program и Text служат идентификаторами (именами) некоторых объектов программы. Программист может использовать в качестве идентификаторов любые последовательности символов, которые удовлетворяют следующим ограничениям:
идентификатор может состоять из букв латинского алфавита, цифр, знака подчеркивания; никакие другие символы в идентификаторе недопустимы;
идентификатор не может начинаться с цифры;
идентификатор не может совпадать ни с одним из зарезервированных слов;
длина идентификатора может быть произвольной, но значащими считаются первые 63 символа.
Как и всюду в программе, в идентификаторах игнорируется разница в высоте букв, поэтому, например, идентификаторы Text, text и TEXT с точки зрения компилятора идентичны.
Теперь попробуйте выполнить программу. Для этого после набора ее текста нажмите Ctrl-F9. Если Вы не ошиблись при вводе текста, то спустя несколько секунд заметите быструю смену изображений на экране: сразу после загрузки программы Турбо Паскаль очищает экран, предоставляя его в распоряжение работающей программы пользователя. Такой экран называется окном программы. После завершения прогона (работа программы часто называется ее прогоном) на экране вновь появится окно редактора с текстом программы. Если Вы не успели разглядеть изображение окна программы, нажмите Alt-F5. После нажатия на любую клавишу среда вернет экран в режим воспроизведения окна редактора.
Перед тем, как двигаться дальше, полезно подробнее ознакомиться с некоторыми возможностями среды Турбо Паскаля. Нажмите клавишу F10, чтобы перейти к режиму выбора из главного меню, подведите указатель к опции Debug (отладка) и нажмите клавишу Enter - на экране раскроется меню второго уровня, связанное с этой опцией. Новое меню как бы «выпало» из верхней строки, поэтому такое меню часто называют выпадающим. Отыщите в новом меню опцию Output (вывод программы), подведите к ней указатель и нажмите клавишу Enter еще раз. На экране вновь появится окно программы, но оно уже не будет исчезать после нажатия на любую клавишу - экран будет связан с этим окном постоянно. Теперь добьемся того, чтобы на экране демонстрировались два окна одновременно: вновь нажмите клавишу F10, выберите Window, нажмите клавишу Enter, подведите указатель к опции Tilе(черепица) и нажмите клавишу Enter еще раз. Если все сделано правильно, экран приобретет вид, показанный на рис.2.1.
Рис. 2.1. Вид экрана с окнами редактора и программы
Двойная рамка, очерчивающая окно программы, свидетельствует о том, что именно это окно активно в данный момент. Сделаем активным окно редактора: нажмем клавишу Alt и, не отпуская ее, - клавишу с цифрой 1 (окно редактора имеет номер 1, окно программы - номер 2, см. верхние правые углы рамок на рис.2.1). Теперь все готово к дальнейшим экспериментам с программой.
Попробуем изменить выводимый на экран текст. Например, уберем в конце треть-ей строки точку с запятой и отредактируем ее таким образом:
Text = 'я учусь программировать на турбо паскале'
Если Вы запустите программу вновь, нажав клавиши Ctrl-F9, компилятор сообщит:
Error 85: ";" expected. (Ошибка 85: Отсутствует ";".),
а редактор установит курсор на первый символ слова begin, показывая то место, где при разборе текста обнаружена ошибка (разделитель «;» может отделять от конца оператора сколько угодно пробелов; компилятор пропускает эти пробелы в поисках разделителя до тех пор, пока не обнаружит зарезервированное слово, - вот почему курсор стоит не в конце строки с объявлением константы, а перед словом begin). Исправьте программу - поставьте в конце третьей строки разделитель «;» и вновь запустите счет. На этот раз все пройдет нормально, но вывод программы изменится, и в окне программы появится текст
я учусь программировать на турбо паскале
Этот текст строго соответствует заданному в текстовой константе набору символов, поэтому в нем отсутствуют прописные буквы.
Вычисление дня недели
Случалось ли Вам мучительно вспоминать, какой именно день недели приходился на то или иное число год или два назад, или вычислять, на какой день недели в этом году приходится Ваш день рождения? Если да, то Вас, думаю, заинтересует простая программа, позволяющая по заданной дате мгновенно вычислить соответствующий день недели. В ее основе лежит такая формула:
день недели = остаток от деления X на 7,
где X = abs(trunc(2.6*m-0.2)+d+y/4+y+c/4-2*c);
m - номер месяца (см. ниже);
d - число (день месяца);
с - номер столетия (см. ниже);
у - номер года в столетии.
При использовании этой формулы следует учесть два обстоятельства. Во-первых, формула верна для григорианского календаря нового стиля (от 1582 до 4903 года). Во-вторых, год и месяц следует предварительно преобразовать так, как если бы начало года приходилось на 1 марта. Иными словами, март в этой формуле имеет порядковый номер 1, апрель 2, ..., январь 11 и февраль 12, причем январь и февраль следует отнести к предыдущему году. Например, для 1 февраля 1991 года номер месяца должен быть равен 12, а год 1990, в то время как для 31 декабря 1991 года номер месяца - 10, а год - 1991. Результат вычисления дается в виде целого числа в диапазоне от 0 до 6, причем 0 соответствует воскресенью.
Приступим к разработке программы. Прежде всего, предположим, что программа уже создана и Вы осуществляете ее прогон. Какая форма взаимодействия с программой кажется Вам наиболее подходящей? Вряд ли Вас удовлетворит однократное ее исполнение (ввод некоторой даты и вывод на экран соответствующего дня недели). Скорее всего Вы захотите повторить работу программы для нескольких дат, например, поинтересоваться, в какой день недели Вы родились, затем, на какой день недели приходится в этом году Ваш день рождения, дни рождения близких, друзей; может быть, определить, в какой день родились известные Вам исторические деятели, и т.д. Таким образом, в программе следует предусмотреть многократное выполнение действий <ввод даты> - <вычисление дня недели>, причем число циклов вычисления заранее не известно. Сразу же возникает новый вопрос: как сообщить программе, что Вы завершаете работу с ней? Для этого можно условиться, что ввод некоторой заранее обусловленной или недопустимой даты должен интерпретироваться программой, как указание на прекращение работы. С учетом сказанного, напишем такой начальный вариант программы:
var
IsCorrectDate: Boolean; {Признак правильной даты}
d,m,y : Integer; {Вводимая дата - день, месяц и год}
begin
repeat
{Ввести в переменные d, л? и у очередную дату и проверить ее. Если дата правильная, установить IsCorrectDate=True, иначе IsCorrectDate=False}
if IsCorrectDate then
{Вычислить и выдать на экран день недели};
until not IsCorrectDate
end.
Если Вы попытаетесь запустить эту программу на счет, то ее поведение будет зависеть от начального значения переменной IsCorrectDate. Это значение случайно, так как компилятор Турбо Паскаля не проводит начальной инициализации переменных. Скорее всего, тот байт оперативной памяти, в котором она разместится, окажется нулевым, что в Турбо Паскале расценивается как логическое значение FALSE, поэтому с большой вероятностью ничего не произойдет, и программа сразу же завершит свою работу (условие not IsCorrectDate будет выполнено). Если начальное значение IsCorrectDate окажется не нулевым, то цикл REPEAT. . .UNTIL будет выполняться до тех пор, пока Вы не выключите компьютер или не нажмете клавиши Ctrl-Break.
Будем считать, что необходимые действия осуществляются в двух процедурах с именами InputDate (ввод даты) и WriteDay (вычисление и печать дня недели). В процедуру InputDate не нужно ничего передавать из программы, так как в ней самой осуществляются ввод и контроль даты. Поэтому заголовок процедуры может иметь такой вид:
Procedure InputDate(var d,m,y: Integer; var correctly:
Boolean);
Процедура WriteDay, напротив, только получает из программы нужные ей данные и ничего не возвращает в программу, поэтому в ее заголовке параметры описываются без слова VAR:
Procedure WriteDay(d,m,у : Integer);
С учетом этого программу можно уточнить следующим образом:
var
IsCorrectDate: Boolean; {Признак правильной даты}
d,m,y : Integer; {Вводимая дата - день, месяц и год}
{...............................}
Procedure InputDate(var d,m,y : Integer;
var correctly : Boolean);
{Вводит в переменные d, m и у очередную дату и проверяет ее. Если дата правильная, устанавливает correctly=true, иначе correctly=false }
begin {InputDate}
correctly := false
end; {InputDate}
{...............................}
Procedure WriteDay(d,m,у: Integer);
{Вычисляет день недели и выводит его на экран}
begin {WriteDay}
end; {WriteDay}
{..............................}
begin
repeat
InputDate(d,m,y,IsCorrectDate);
if IsCorrectDate then
WriteDay(d,m,y)
until not IsCorrectDate
end.
Теперь можно разработать процедуру INPUTDATE. Ввод даты не вызывает трудностей - стандартные процедуры WRITE и READLN отлично приспособлены для этой цели. Для проверки правильности даты нужно проверить принадлежность месяца диапазону 1...12 и года - диапазону 1582...4903. Кроме того, число не должно выходить из диапазона 1...31. Если Вы не очень настаиваете на более точной проверке числа в зависимости от месяца и года (для февраля), то программная реализация процедуры будет следующей:
Procedure InputDate(var d,m,y : Integer;
var correctly : Boolean);
{Вводит в переменные d, m и у очередную дату и проверяет ее. Если дата правильная, устанавливает correctly=true, иначе correctly=false }
begin {InputDate}
Write('Введите дату в формате ДД ММ ГГ: ');
ReadLn(d,m,y);
correctly := (d>=l)and (d<=31) and (m>=l)
and (m<=12) and (y>=1582) and (y<=4903)
end; {InputDate}
При выполнении этой процедуры ввод, например, трех нулей приведет к присвоению переменной CORRECTLY значения FALSE, что вызовет завершение работы программы.
Теперь разберемся с процедурой WRITEDAY. Получив в параметрах обращения день, месяц и год, она должна:
преобразовать месяц и год так, как описано выше (год должен начинаться 1 марта);
вычислить день недели;
выдать на экран результат.
Первое и второе действия очень просты и легко программируются. Что касается выдачи на экран, то можно потребовать от программы, чтобы эта выдача была не просто числом от 0 до 6, а одной из строк «воскресенье», «понедельник», ..., «суббота». Для этого потребуются дополнительные усилия: нужно сначала создать массив строковых констант с именем, например, DAYS_OF_WEEK (дни_недели), а затем выбрать из этого массива и выдать на экран нужную строку. Создать массив текстовых констант можно с помощью объявления типизированной константы (см. гл. 7):
const
Days_of_week: array [0..6] of String [11] =
('воскресенье','понедельник','вторник', 'среда','четверг','пятница','суббота');
В этом объявлении идентификатор Days_of_week описывается в разделе констант, однако справа от него указан тип данных (массив строк), как если бы описывалась переменная, а уже только после типа стоит знак равенства и заключенный в круглые скобки список элементов массива. В результате получим следующую процедуру:
Procedure WriteDay(d,m,y : Integer);
const
Days_of_week: array [0..6] of String [11] =
('воскресенье','понедельник','вторник', ' среда', ' четверг', ' пятница', ' суббота.') ;
var
с, w :Integer;
begin
if m <3 then
begin {Месяц январь или февраль}
m := m + 10;
у := у - 1
end
else
m : = m - 2; {Остальные месяцы}
с := у div 100; {Вычисляем столетие}
у := у mod 100; {Находим год в столетии}
w := abs(trunc(2.6*m-0.2)+d+y div 4+y+c div 4-2*c) mod 7;
WriteLn(Days_of_week[w] )
end;
Окончательный вариант программы приведен в прил.5.1.
Иденитификаторы
Идентификаторы в Турбо Паскале - это имена констант, переменных, меток, типов, объектов, процедур, функций, модулей, программ и полей в записях. Идентификаторы могут иметь произвольную длину, но значащими (уникальными в области определения) являются только первые 63 символа.
Идентификатор всегда начинается буквой, за которой могут следовать буквы и цифры. Напомню, что буквой считается также символ подчеркивания, поэтому идентификатор может начинаться этим символом и даже состоять только из одного или нескольких символов подчеркивания. Пробелы и специальные символы алфавита не могут входить в идентификатор.
Примеры правильных идентификаторов:
а
ALPHA
MyProgramIsBestProgram
date_27_sep_39
external
_beta
Примеры неправильных идентификаторов:
1Program {начинается цифрой}
block#l {содержит специальный символ}
My Prog {содержит пробел}
mod {зарезервированное слово}
Константы
В качестве констант в Турбо Паскале могут использоваться целые, вещественные и шестнадцатеричные числа, логические константы, символы, строки символов, конструкторы множеств и признак неопределенного указателя NIL.
Целые числа записываются со знаком или без него по обычным правилам и могут иметь значение от -2147483648 до +2147483647. Следует учесть, что, если целочисленная константа выходит за указанные границы, компилятор дает сообщение об ошибке. Такие константы должны записываться с десятичной точкой, т.е. определяться как вещественные числа.
Вещественные числа записываются со знаком или без него с использованием десятичной точки и/или экспоненциальной части. Экспоненциальная часть начинается символом е или Е, за которым могут следовать знаки «+» или «-» и десятичный порядок. Символ е (Е) означает десятичный порядок и имеет смысл «умножить на 1.0 в степени». Например,
3.14Е5 - 3.14 умножить на 10 в степени 5;
-17е-2 - минус 17 умножить на 10 в степени минус 2.
Если в записи вещественного числа присутствует десятичная точка, перед точкой и за ней должно быть хотя бы по одной цифре. Если используется символ экспоненциальной части е (Е), за ним должна следовать хотя бы одна цифра десятичного порядка.
Шестнадцатеричное число состоит из шестнадцатеричных цифр, которым предшествует знак доллара $ (код 36 в ASCII). Диапазон шестнадцатеричных чисел - от $00000000 ДО $FFFFFFFF.
Логическая константа - это либо слово FALSE (ложь), либо слово TRUE (истина).
Символьная константа - это любой символ ПК, заключенный в апострофы:
'z' - символ z;
'Ф' - символ Ф.
Если необходимо записать собственно символ апострофа, он удваивается:
'''' - символ ' (апостроф).
Допускается использование записи символа путем указания его внутреннего кода, которому предшествует символ # (код 35), например:
#97 - символ а;
#90 - символ Z;
#39 - символ ';
#13 - символ CR.
Строковая константа - любая последовательность символов (кроме символа CR -возврат каретки), заключенная в апострофы. Если в строке нужно указать сам символ апострофа, он удваивается, например:
'Это - строка символов;
'That' 's string.'.
Строка символов может быть пустой, т.е. не иметь никаких символов в обрамляющих ее апострофах. Строку можно составлять из кодов нужных символов с предшествующими каждому коду символами #, например, строка #83#121#109#98#11#108 эквивалентна строке ' Symbol'.
Наконец, в строке можно чередовать части, записанные в обрамляющих апострофах, с частями, записанными кодами. Таким способом можно вставлять в строки любые управляющие символы, в том числе и символ CR (код 13), например:
#7'Ошибка !'#13'Нажмите любую клавишу ...'#7 .
Конструктор множества - список элементов множества, обрамленный квадратными скобками, например:
[1,2,4..7,12]
[blue, red]
[]
[true]
В отличие от стандартного Паскаля, в Турбо Паскале разрешается в объявлении констант использовать произвольные выражения, операндами которых могут быть ранее объявленные нетипизированные константы, имена типов и объектов, а также следующие функции от них;
abs lo ptr swap
chr odd rpund trunc
hi ord sizeof
length pred succ
Например:
const
MaxReal = Maxlnt div SizeOf(real);
NumChars = ord('Z') - ord('a') + 1;
Ln10 = 2.302585092994;
Ln10R = 1 / Lnl0;.
Операции
В Турбо Паскале определены следующие операции:
унарные not, @;
мультипликативные
*, /, div, mod, and, shl, shr;
аддитивные +, -, or, xor;
отношения =, <>, <, >, <=, >=,in.
Приоритет операций убывает в указанном порядке, т.е. наивысшим приоритетом обладают унарные операции, низшим - операции отношения. Порядок выполнения нескольких операций равного приоритета устанавливается компилятором из условия оптимизации кода программы и не обязательно слева направо. При исчислении логических выражений операции равного приоритета всегда вычисляются слева направо, причем будут вычисляться все или только достаточные операции в зависимости от установленной в среде Турбо Паскаля опции OPTIONS/COMPILER/ COMPLETE BOOLEAN EVAL: при установленном значении этой опции вычисляются все операции отношения, при не установленном - только те, которые достаточны для получения результата.
Это обстоятельство необходимо учитывать при использовании операций отношения с функциями, в которых изменяются глобальные переменные или параметры, передаваемые по имени, например:
Function AddI(var x: Integer): Integer;
begin {AddI}
inc(x);
AddI := x end {AddI} ;
var
a,b : Integer;
begin {main}
if (a > b) or (Addl (a) > 100) then b := a;
.......
При выполнении этого фрагмента значение переменной А будет зависеть от настройки опции: если опция активизирована, значение А всегда наращивается на 1, если не активизирована - только в случае А <= В .
Правила использования операций с операндами различного типа приводятся в табл. 3.1.
Таблица 3.1
При действиях с вещественным типом одним из операндов может быть значение любого целого типа. Результат операций имеет указанный в таблице тип EXTENDED только для установленного в среде Турбо Паскаля режима генерации кода, рассчитанного на арифметический сопроцессор или на его эмуляцию (см. прил.1). Если этот режим не установлен, результат будет иметь значение типа REAL.
Унарная операция @ применяется к операнду любого типа и возвращает результат типа POINTER (см. гл.6), в котором содержится адрес операнда. Пусть, например, задано описание
type
TwoChar = array [1..2] of char; var
Int : integer; TwoCharPtr : ATwoChar;
Тогда оператор
TwoCharPtr := @Int;
приведет к тому, что в TwoCharPtr будет храниться адрес целочисленной переменной INT, которая может теперь интерпретироваться как совокупность двух символов. Поэтому возможен, например, такой оператор:
if TwoCharPtr^[1] = 'С' then...
Если операция @ применяется к процедуре, функции или методу в объекте, ее результатом будет адрес точки входа в эту процедуру (функцию, метод). Этот адрес можно использовать только в подпрограмме, написанной на ассемблере, или в фрагментах INLINE.
В Турбо Паскале определены следующие логические операции:
not - логическое НЕ;
and - логическое И;
or - логическое ИЛИ;
хоr - исключительное ИЛИ.
Логические операции применимы к операндам целого и логического типов. Если операнды - целые числа, то результат логической операции есть тоже целое число, биты которого (двоичные разряды) формируются из битов операндов по правилам, указанным в табл. 3.2.
Таблица 3.2
К логическим же в Турбо Паскале обычно относятся и две сдвиговые операции над целыми числами:
i shl j - сдвиг содержимого i на j разрядов влево; освободившиеся младшие
разряды заполняются нулями;
i shr j - сдвиг содержимого i на j разрядов вправо; освободившиеся старшие
разряды заполняются нулями.
В этих операциях i и j - выражения любого целого типа.
С помощью программы примера 3.1 можно вывести на экран результат применения логических операций к двум целым числам.
Пример 3.1
{Программа вводит два целых числа и печатает результат применения к ним логических операций. Для выхода из программы ввести Ctrl-z и нажать Enter}
var
n,m : integer; begin
while not EOF do begin
Write('n,m='); ReadLn(n,m);
end.
В программе организуется ввод двух произвольных целых чисел и печать результата применения к ним всех логических операций. Для выхода из программы следует нажать Ctrl-z, и Enter.
Логические операции над логическими данными дают результат логического типа по правилам, указанным в табл. 3.3.
Таблица 3.3
Операция отношения IN применяется к двум операндам. Первым (левым) операндом должно быть выражение любого порядкового типа, вторым - множество, состоящее из элементов того же типа, или идентификатор множественного типа. Операция дает TRUE, если левый операнд принадлежит множеству, например:
var
с: char; type
digit = set of ' 0 '..' 9 ' ; begin
if с in digit then .......
Структура программы
Структура любой программной единицы (программы, процедуры или функции) должна быть такой: ;
<Объявление программной единицы>
{Раздел описаний}
BEGIN
{Раздел исполняемых операторов}
END <символ конца программной единицы>
Здесь «Объявление программной единицы> - заголовок программы, процедуры или функции; заголовок программы можно опускать без каких-либо последствий для программы; для процедур и функций наличие заголовка обязательно; <символ конца программной единицы> - символ «.» для программы или символ «;» - для процедуры и функции.
Любой из двух разделов программной единицы - раздел описаний или исполняемых операторов, или оба одновременно могут быть пустыми, т.е. не содержать никаких описаний или исполняемых операторов.
В разделе описаний должны содержаться описания всех идентификаторов, используемых в разделе исполняемых операторов. Исключением являются идентификаторы, определенные в интерфейсных частях программных модулей (библиотек), а также
глобальные для процедуры или функции идентификаторы (см. гл. 8). Если программная единица использует идентификатор из интерфейсной части, какого-либо модуля (см. гл. 9), в начале программы в предложении USES необходимо указать имя этого модуля. Последнее не относится к идентификаторам, определенным в стандартном модуле SYSTEM, т.е. имя этого модуля в предложении USES указывать не нужно. Более того, модуль SYSTEM считается предварительно объявленным, поэтому объявление
Uses System;
компилятор расценит как попытку двойного объявления модуля SYSTEM и даст соответствующее сообщение об ошибке. В разделе описаний объявляются идентификаторы типов, объектов, констант, переменных, а также метки, процедуры и функции. Описанию типов и объектов должно предшествовать зарезервированное слово TYPE, описанию констант - CONST, переменных - VAR и меток - LABEL, например:
type
DigType = set of '0' .. '9' ;
StrType = string [40];
const
N = 100;
EPS = 1e-9;
var
x,y:real ;
st :StrType;
label
1b1, 1b2;
В отличие от стандартного Паскаля разделы TYPE, CONST, VAR, LABEL могут следовать друг за другом в любом порядке и встречаться в разделе описаний сколько угодно раз.
Описание процедуры или функции заключается в указании заголовка этой процедуры (функции) и ее тела (подробнее см. в гл. 8).
Структура программных библиотек (модулей) описана в гл.9.
Выражения
Основными элементами, из которых конструируется исполняемая часть программы, являются константы, переменные и обращения к функциям. Каждый из этих элементов характеризуется своим значением и принадлежит к какому-либо типу данных. С помощью знаков операций и скобок из них можно составлять выражения, которые фактически представляют собой правила получения новых значений.
Частным случаем выражения может быть просто одиночный элемент, т.е. константа, переменная или обращение к функции. Значение такого выражения имеет, естественно, тот же тип, что и сам элемент. В более общем случае выражение состоит из нескольких элементов (операндов) и знаков операций, а тип его значения определяется типом операндов и видом примененных к ним операций. Примеры выражений:
Y
21
(а + b) * с
sin(t)
а > 2
not Flag and (а = b)
NIL
[1, 3..7] * set1
Массивы
Массивы в Турбо Паскале во многом схожи с аналогичными типами данных в других языках программирования. Отличительная особенность массивов заключается в том, что все их компоненты суть данные одного типа (возможно, структурированного). Эти компоненты можно легко упорядочить и обеспечить доступ к любому из них простым указанием его порядкового номера, например:
type
digit =array[0..9] of Char ;
matrix =array[Byte] of Single;
var
m : matrix;
d : digit;
i : Integer;
begin
.......
m[17] := ord(d[i-l])/10;
.......
end.
Описание типа массива задается следующим образом:
<имя типа> = ARRAY [ <сп.инд.типов> ] OF <тип>
Здесь <имя типа> - правильный идентификатор;
ARRAY, OF - зарезервированные слова (массив, из);
<сп.тд.типов> - список из одного или нескольких индексных типов, разделенных запятыми; квадратные скобки, обрамляющие список, - требование синтаксиса;
<тип> - любой тип Турбо Паскаля.
В качестве индексных типов в Турбо Паскале можно использовать любые порядковые типы, кроме LONGINT и типов-диапазонов с базовым типом LONGINT.
Определить переменную как массив можно и непосредственно при описании этой переменной, без предварительного описания типа массива, например:
var
а,b : array [1..10] of Real;
Обычно в качестве индексного типа используется тип-диапазон, в котором задаются границы изменения индексов. Так как тип <тип>, идущий за словом OF, - любой тип Турбо Паскаля, то он может быть, в частности, и другим массивом, например:
type
mat = array [0..5] of array [-2..2] of array [Char] of Byte;
Такую запись можно заменить более компактной:
type
mat = array [0..5,-2..2,Char] of Byte;
Глубина вложенности структурированных типов вообще, а следовательно, и массивов - произвольная, поэтому количество элементов в списке индексных типов (размерность массива) не ограничено, однако суммарная длина внутреннего представления любого массива, как уже говорилось, не может быть больше 65520 байт. В памяти ПК элементы массива следуют друг за другом так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива. Если, например,
var
а : array[1. .2,1. .2] of Byte;
begin
a [1,1]:=1;
a [2,1]:=2;
a [l, 2]:=3;
a [2,2]:=4;
end.
то в памяти последовательно друг за другом будут расположены байты со значениями 1,3,2, 4 . Это обстоятельство может оказаться важным при использовании стандартной процедуры копирования памяти MOVE.
В Турбо Паскале можно одним оператором присваивания передать все элементы одного массива другому массиву того же типа, например:
var
а,b:array [1..5] of Single;
begin
.....
a := b;
.....
end.
После этого присваивания все пять элементов массива А получат те же значения, что и в массиве В. Однако над массивами не определены операции отношения. Нельзя, например, записать
if a = b then ...
Сравнить два массива можно поэлементно, например:
var
a,b:array [1..5] of Single;
eq:Boolean;
i:Byte;
begin
.....
eq := True; for i := 1 to 5 do
if a[i] <> b[i] then
eq := False;
if eq then
.....
end.
Множества
Множества - это наборы однотипных логически связанных друг с другом объектов. Характер связей между объектами лишь подразумевается программистом и никак не контролируется Турбо Паскалем. Количество элементов, входящих в множество, может меняться в пределах от 0 до 256 (множество, не содержащее элементов, называется пустым). Именно непостоянством количества своих элементов множества отличаются от массивов и записей.
Два множества считаются эквивалентными тогда и только тогда, когда все их элементы одинаковы, причем порядок следования элементов в множестве безразличен. Если все элементы одного множества входят также и в другое, говорят о включении первого множества во второе. Пустое множество включается в любое другое.
Пример определения и задания множеств:
type
digitChar= set of '0'..'9';
digit = set of 0. .9;
var
sl,s2,s3 :digitChar;
s4,s5,s6 :digit;
begin
.....
s1:=['1','2','3'];
s2:=['3','2','1'];
s3:=['2','3'];
s4:=[0..3,6];
s5:=[4,5];
s6:=[3..9];
.....
end.
В этом примере множества S1 и S2 эквивалентны, а множество S3 включено в S2 , но не эквивалентно ему.
Описание типа множества имеет вид:
<имя типа> = SET OF <баз.тип>
Здесь <имя типа> - правильный идентификатор;
SET, OF - зарезервированные слова (множество, из);
<баз.тип> - базовый тип элементов множества, в качестве которого может
использоваться любой порядковый тип, кроме WORD, INTEGER, LONGINT.
Для задания множества используется так называемый конструктор множества: список спецификаций элементов множества, отделяемых друг от друга запятыми; список обрамляется квадратными скобками (см. предыдущий пример). Спецификациями элементов могут быть константы или выражения базового типа, а также - тип-диапазон того же базового типа.
Над множествами определены следующие операции:
* пересечение множеств; результат содержит элементы, общие для обоих множеств; например, S4*S6 содержит [3], S4*S5 - пустое множество (см. выше);
+ объединение множеств; результат содержит элементы первого множества, дополненные недостающими элементами из второго множества:
S4+S5 содержит [0,1,2,3,4,5,6];
S5+S6 содержит [3,4,5,6,7,8,9];
- разность множеств; результат содержит элементы из первого множества, которые не принадлежат второму:
S6-S5 содержит [3,6,7,8,9];
S4-S5 содержит [0,1,2,3,6];
= проверка эквивалентности; возвращает TRUE, если оба множества эквивалентны;
<> проверка неэквивалентности; возвращает TRUE, если оба множества неэквивалентны;
<= проверка вхождения; возвращает TRUE, если первое множество включено во второе;
>= проверка вхождения; возвращает TRUE, если второе множество включено в первое;
IN проверка принадлежности; в этой бинарной операции первый элемент - выражение, а второй - множество одного и того же типа; возвращает TRUE , если выражение имеет значение, принадлежащее множеству:
3 in s6 возвращает TRUE;
2*2 in s1 возвращает FALSE.
Дополнительно к этим операциям можно использовать две процедуры. INCLUDE - включает новый элемент во множество. Обращение к процедуре:
INCLUDE (S,I)
Здесь S - множество, состоящее из элементов базового типа TSetBase;
I - элемент типа TSetBase, который необходимо включить во множество.
EXCLUDE - исключает элемент из множества. Обращение:
EXCLUDE(S,I)
Параметры обращения - такие же, как у процедуры INCLUDE.
В отличие от операций + и -, реализующих аналогичные действия над двумя множествами, процедуры оптимизированы для работы с одиночными элементами множеcтва и поэтому отличаются высокой скоростью выполнения.
В примере 4.1, иллюстрирующем приемы работы с множествами, реализуется алгоритм выделения из первой сотни натуральных чисел всех простых чисел. В его основе лежит прием, известный под названием «решето Эратосфена». В соответствии с этим алгоритмом вначале формируется множество BEGINSET, состоящее из всех целых чисел в диапазоне от 2 до N. В множество PRIMERSET (оно будет содержать искомые простые числа) помещается 1. Затем циклически повторяются следующие действия:
взять из BEGINSET первое входящее в него число NEXT и поместить его в PRIMERSET;
удалить из BEGINSET число NEXT и все другие числа, кратные ему, т.е.2*NEXT, 3*NEXT и т.д.
Цикл повторяется до тех пор, пока множество BEGINSET не станет пустым.
Эту программу нельзя использовать для произвольного N, так как в любом множестве не может быть больше 256 элементов.
Пример 4.1
Program Primer_numbers_detect;
{Выделение всех простых чисел из первых N целых}
const
N = 255; {Количество элементов исходного множества}
type
SetOfNumber = set of 1..N;
var
n1,next,i : Word; {Вспомогательные переменные}
BeginSet, {Исходное множество}
PrimerSet : SetOfNumber; {Множество простых чисел} .
begin
BeginSet := [2. .N] ; {Создаем исходное множество}
PrimerSet:= [1]; {Первое простое число}
next:= 2; {Следующее простое число}
while BeginSet <> [] do {Начало основного цикла}
begin
n1 := next;{n1-число,кратное очередному простому (next)}
{Цикл удаления из исходного множества непростых чисел:}
while n1 <= N do
begin
Exclude(BeginSet,nl);
n1 := n1+next {Следующее кратное}
end; {Конец цикла удаления}
Include(PrimerSet,next);
{Получаем следующее простое, которое есть первое невычеркнутое из исходного множества}
repeat
inc(next)
until (next in BeginSet) or (next > N)
end; {Конец основного цикла}
{Выводим результат:}
for i := 1 to N do
if i in PrimerSet then Write(i:8);
WriteLn
END.
Перед тем как закончить рассмотрение множеств полезно провести небольшой эксперимент. Измените описание типа SETOFNUMBER следующим образом:
type
SetOf Number = set of 1. . 1 ;
и еще раз запустите программу из предыдущего примера. На экран будет выведено
1 3 5 7
Множества BeginSet и PrimerSet состоят теперь из одного элемента, а программа сумела поместить в них не менее семи! Секрет этого прост: внутреннее устройство множества таково, что каждому его элементу ставится в соответствие один двоичный разряд (один бит); если элемент включен во множество, соответствующий разряд имеет значение 1, в противном случае - 0. Минимальной единицей памяти является один байт, содержащий 8 бит. Компилятор выделил множествам по одному байту, в результате мощность каждого из них стала равна 8 элементов. Максимальная мощность множества - 256 элементов. Для таких множеств компилятор выделяет по 16 смежных байт.
И еще один эксперимент: измените диапазон базового типа на 1.256. Хотя мощность этого типа составляет 256 элементов, при попытке компиляции программы компилятор сообщит:
Error 23: Set base type out of range.
(Ошибка 23: Базовый тип множества выходит за допустимые
границы.)
Компилятор разрешает использовать в качестве базового типа целочисленный тип-диапазон с минимальной границей 0 и максимальной 255 или любой перечисляемый тип не более чем с 256 элементами (максимальная мощность перечисляемого типа -5536 элементов).
Порядковые типы
К порядковым типам относятся (см. рис.4.1) целые, логический, символьный, перечисляемый и тип-диапазон. К любому из них применима функция ORD(X), которая возвращает порядковый номер значения выражения X. Для целых типов функция ORD(X) возвращает само значение X, т.е. ORD(X) = X для X, принадлежащего любому шелому типу. Применение ORD(X) к логическому, символьному и перечисляемому типам дает положительное целое число в диапазоне от 0 до 1 (логический тип), от 0 до 155 (символьный), от 0 до 65535 (перечисляемый). Тип-диапазон сохраняет все свойства базового порядкового типа, поэтому результат применения к нему функции ORD(X) зависит от свойств этого типа.
К порядковым типам можно также применять функции:
PRED (X) - возвращает предыдущее значение порядкового типа (значение, которое соответствует порядковому номеру ORD(X)- 1), т.е.
ORD(PRED(X)) = ORD(X) - 1;
SUCC (X) - возвращает следующее значение порядкового типа, которое соответствует порядковому номеру ORD(X) +1, т.е.
ORD(SUCC(X)) = ORD(X) + 1.
Например, если в программе определена переменная
var
с : Char;
begin
с := '5' ;
end.
то функция PRED(C) вернет значение '4', а функция SUCC(C) - значение '6'.
Если представить себе любой порядковый тип как упорядоченное множество значий, возрастающих слева направо и занимающих на числовой оси некоторый отрезок, то функция PRED(X) не определена для левого, a SUCC(X) - для правого конца этого отрезка.
Целые типы. Диапазон возможных значений целых типов зависит от их внутреннего представления, которое может занимать один, два или четыре байта. В табл. 4.1 приводится название целых типов, длина их внутреннего представления в байтах и диапазон возможных значений.
Таблица 4.1
При использовании процедур и функций с целочисленными параметрами следует руководствоваться «вложенностью» типов, т.е. везде, где может использоваться WORD, допускается использование BYTE (но не наоборот), в LONGINT «входит» INTEGER, который, в свою очередь, включает в себя SHORTINT.
Перечень процедур и функций, применимых к целочисленным типам, приведен в табл.4.2. Буквами b, s, w, i, l обозначены выражения соответственно типа BYTE, SHORTINT, WORD, INTEGER и LONGINT, x - выражение любого из этих типов; буквы vb, vs, vw, vi, vl, vx обозначают переменные соответствующих типов. В квадратных скобках указывается необязательный параметр.
Таблица 4.2
При действиях с целыми числами тип результата будет соответствовать типу операндов, а если операнды относятся к различным целым типам, - типу того операнда, который имеет максимальную мощность (максимальный диапазон значений). Возможное переполнение результата никак не контролируется, что может привести к недоразумениям, например:
var
а : Integer;
х, у : Real;
begin
а := 32767; {Максимально возможное значение типа INTEGER}
х := а + 2; {Переполнение при вычислении этого выражения!}
у := LongInt(а)+2; {Переполнения нет после приведения переменной к более мощному типу}
WriteLn(x:10:0, у:10:0)
end.
В результате прогона программы получим
-32767 32769
Логический тип. Значениями логического типа может быть одна из предварительно объявленных констант FALSE (ложь) или TRUE (истина). Для них справедливы правила:
ord(False) = 0;
ord(True) = 1;
False < True;
succ(False)= True;
pred(True) = False.
Поскольку логический тип относится к порядковым типам, его можно использовать в операторе счетного типа, например:
var
1 : Boolean;
begin
for 1 := False to True do ....
Символьный тип. Значением символьного типа является множество всех символов ПК. Каждому символу приписывается целое число в диапазоне 0...255. Это число служит кодом внутреннего представления символа, его возвращает функция ORD.
Для кодировки используется код ASCII (American Standard Code for Information Interchange - американский стандартный код для обмена информацией). Это 7-битный код, т.е. с его помощью можно закодировать лишь 128 символов в диапазоне от 0 до 127. В то же время в 8-битном байте, отведенном для хранения символа в Турбо Паскале, можно закодировать в два раза больше символов в диапазоне от 0 до 255. Первая половина символов ПК с кодами 0...127 соответствует стандарту ASCII (табл. 4.3). Вторая половина символов с кодами 128...255 не ограничена жесткими рамками стандарта и может меняться на ПК разных типов (в прил.2 приведены некоторые распространенные варианты кодировки этих символов).
Таблица 4.3
Символы с кодами 0...31 относятся к служебным кодам. Если эти коды используются в символьном тексте программы, они считаются пробелами. При использовании их в операциях ввода-вывода они могут иметь следующее самостоятельное значение:
К типу CHAR применимы операции отношения, а также встроенные функции: СНR(В) - функция типа CHAR; преобразует выражение В типа BYTE в символ и возвращает его своим значением;
UPCASE(CH) - функция типа CHAR; возвращает прописную букву, если СН -строчная латинская буква, в противном случае возвращает сам символ СН, например:
var
cl,c2: Char;
begin
cl := UpCase('s') ;
c2 := UpCase ('Ф') ;
WriteLn(cl,' ',c2)
end.
Так как функция UPCASE не обрабатывает кириллицу, в результате прогона этой
программы на экран будет выдано
S ф
Перечисляемый тип. Перечисляемый тип задается перечислением тех значений, которые он может получать. Каждое значение именуется некоторым идентификатором и располагается в списке, обрамленном круглыми скобками, например:
typе
colors =(red, white, blue);
Применение перечисляемых типов делает программы нагляднее. Если, например, в программе используются данные, связанные с месяцами года, то такой фрагмент программы:
type
ТипМесяц=(янв,фев,мар,апр,май,июн,июл,авг,сен,окт,ноя,дек);
var
месяц : ТипМесяц;
begin
.......
if месяц = авг then WriteLn('Хорошо бы поехать к морю!');
.......
end.
был бы, согласитесь, очень наглядным. Увы! В Турбо Паскале нельзя использовать кириллицу в идентификаторах, поэтому мы вынуждены писать так:
type
TypeMonth=(jan,feb,mar,may,jun,jul,aug,sep,oct,nov,dec);
var
month: TypeMonth;
begin
.......
if month = aug then WriteLn('Хорошо бы поехать к морю!');
.......
end.
Соответствие между значениями перечисляемого типа и порядковыми номерами этих значений устанавливается порядком перечисления: первое значение в списке получает порядковый номер 0, второе - 1 и т.д. Максимальная мощность перечисляемого типа составляет 65536 значений, поэтому фактически перечисляемый тип задает некоторое подмножество целого типа WORD и может рассматриваться как компактное объявление сразу группы целочисленных констант со значениями О, 1 и т.д.
Использование перечисляемых типов повышает надежность программ благодаря возможности контроля тех значений, которые получают соответствующие переменные. Пусть, например, заданы такие перечисляемые типы:
type
colors = (black, red, white);
ordenal= (one, two, three);
days = (monday, tuesday, Wednesday);
С точки зрения мощности и внутреннего представления все три типа эквивалентны:
ord(black)=0, ..., ord(white)=2,
ord(one)=0, ...ord(three)=2,
ord(monday)=0, ...ord(Wednesday)=2.
Однако, если определены переменные
var
col :colors; num :ordenal;
day : days;
то допустимы операторы
col := black;
num := succ(two);
day := pred(tuesday);
но недопустимы
col := one;
day := black;
Как уже упоминалось, между значениями перечисляемого типа и множеством целых чисел существует однозначное соответствие, задаваемое функцией ORD(X). В Турбо Паскале допускается и обратное преобразование: любое выражение типа WORD можно преобразовать в значение перечисляемого типа, если только значение целочисленного выражения не превышает мощное1™ перечисляемого типа. Такое преобразование достигается применением автоматически объявляемой функции с именем перечисляемого типа (см. п. 4.4). Например, для рассмотренного выше объявления типов эквивалентны следующие присваивания:
col := one;
col := colors(0);
Разумеется, присваивание
col := 0;
будет недопустимым.
Переменные любого перечисляемого типа можно объявлять без предварительного описания этого типа, например:
var
col: (black, white, green);
Тип-диапазон. Тип-диапазон есть подмножество своего базового типа, в качестве которого может выступать любой порядковый тип, кроме типа-диапазона. Тип-диапазон задается границами своих значений внутри базового типа:
<мин.знач.>..<макс.знач.>
Здесь <мин.знач. > - минимальное значение типа-диапазона;
<макс.знач.> - максимальное его значение.
Например:
type
digit = '0'..'9';
dig2= 48..57;
Тип-диапазон необязательно описывать в разделе TYPE, а можно указывать непосредственно при объявлении переменной, например:
var
date : 1..31;
month: 1. .12;
Ichr : 'A'..'Z';.
При определении типа- диапазона нужно руководствоваться следующими правилами:
два символа «..» рассматриваются как один символ, поэтому между ними недопустимы пробелы;
левая граница диапазона не должна превышать его правую границу. Тип-диапазон наследует все свойства своего базового типа, но с ограничениями, связанными с его меньшей мощностью. В частности, если определена переменная
type
days = (mo,tu,we,th,fr,sa,su);
WeekEnd = sa .. su;
var
w : WeekEnd;
begin
.....
w := sa;
.....
end;
то ORD(W) вернет значение 5 , в то время как PRED(W) приведет к ошибке.
В стандартную библиотеку Турбо Паскаля включены две функции, поддерживающие работу с типами-диапазонами:
НIGН(Х) - возвращает максимальное значение типа-диапазона, к которому принадлежит переменная X;
LOW(X) -возвращает минимальное значение типа-диапазона.
Следующая короткая программа выведет на экран строку
-32768...32767
var
k: Integer;
begin
WriteLn(Low(k),'..',High(k))
end.
Простые типы
К простым типам относятся порядковые и вещественные типы. Порядковые типы отличаются тем, что каждый из них имеет конечное число возможных значений. Эти значения можно определенным образом упорядочить (отсюда -название типов) и, следовательно, с каждым из них можно сопоставить некоторое целое число - порядковый номер значения.
Вещественные типы, строго говоря, тоже имеют конечное число значений, которое определяется форматом внутреннего представления вещественного числа. Однако количество возможных значений вещественных типов настолько велико, что сопоставить с каждым из них целое число (его номер) не представляется возможным.
Совместимость и преобразование типов
Как уже неоднократно отмечалось, Турбо Паскаль - это типизированный язык. Он построен на основе строгого соблюдения концепции типов, в соответствии с которой все применяемые в языке операции определены только над операндами совместимых типов. При обсуждении операций над вещественными данными мы уже затрагивали проблему совместимости вещественных и целых типов. Аналогичные проблемы возникают при операциях над строками разной длины, строками и символами и т.д. Ниже приводится более полное определение совместимости типов.
Два типа считаются совместимыми, если:
оба они есть один и тот же тип;
оба вещественные;
оба целые;
один тип есть тип-диапазон второго типа;
оба являются типами-диапазонами одного и того же базового типа;
оба являются множествами, составленными из элементов одного и того же базового типа;
оба являются упакованными строками (определены с предшествующим словом PACKED) одинаковой максимальной длины;
один тип есть тип-строка, а другой - тип-строка, упакованная строка или символ;
один тип есть любой указатель, а другой - нетипизированный указатель;
один тип есть указатель на объект, а другой - указатель на родственный ему объект;
оба есть процедурные типы с одинаковыми типом результата (для типа-функции), количеством параметров и типом взаимно соответствующих параметров.
Совместимость типов приобретает особое значение в операторах присваивания. Пусть T1 - тип переменной, а Т2 - тип выражения, т.е. выполняется присваивание T1 := T2. Это присваивание возможно в следующих случаях:
T1 и T2 есть один и тот же тип и этот тип не относится к файлам или массивам файлов, или записям, содержащим поля-файлы, или массивам таких записей;
T1 и T2 являются совместимыми порядковыми типами и значение T2 лежит в диапазоне возможных значений T1;
T1 и T2 являются вещественными типами и значение T2 лежит в диапазоне возможных значений T1;
T1 - вещественный тип и T2 - целый тип; ,
T1 - строка и T2 - символ;
T1 - строка и T2 - упакованная строка;
T1 и T2 - совместимые упакованные строки;
T1 и T2 - совместимые множества и все члены T2 принадлежат множеству возможных значений T1;
T1 и T2 - совместимые указатели;
T1 и T2 - совместимые процедурные типы;
T1 - объект и T2 - его потомок.
В программе данные одного типа могут преобразовываться в данные другого типа. Такое преобразование может быть явным или неявным.
При явном преобразовании типов используются вызовы специальных функций преобразования, аргументы которых принадлежат одному типу, а значение - другому. Таковыми являются уже рассмотренные функции ORD, TRUNC, ROUND, CHR. В гл. 6 описывается функция PTR, преобразующая четырехбайтный целочисленный аргумент к типу-указателю.
В Турбо Паскале может использоваться и более общий механизм преобразования типов, согласно которому преобразование достигается применением идентификатора имени) стандартного типа или типа, определенного пользователем, как идентификатора функции преобразования к выражению преобразуемого типа (так называемое автоопределенное преобразование типов). Например, допустимы следующие вызовы функций:
type
МуТуре = (а, Ь, с, d);
.....
МуТуре (2)
Integer ('D')
pointer (longint(a)+ $FF)
Char (127 mod c)
Byte (k)
При автоопределенном преобразовании типа выражения может произойти изменение длины его внутреннего представления (длина может увеличиться или уменьшиться).
В Турбо Паскале определен еще один явный способ преобразования данных: в ту область памяти, которую занимает переменная некоторого типа, можно поместить значение выражения другого типа, если только длина внутреннего представления вновь размещаемого значения в точности равна длине внутреннего представления переменной. С этой целью вновь используется автоопределенная функция преобразования типов, но уже в левой части оператора присваивания:
type
byt = array [1..2] of Byte;
int = array [1..2] of Integer;
rec = record
x, у : Integer
end;
var
vbyt : byt;
vint : int;
vrec : rec;
begin
byt(vint[1])[2] := 0;
int(vrec)[1] := 256
end.
Неявное преобразование типов возможно только в двух случаях:
в выражениях, составленных из вещественных и целочисленных переменных, последние автоматически преобразуются к вещественному типу, и все выражение в целом приобретает вещественный тип;
одна и та же область памяти попеременно трактуется как содержащая данные то одного, то другого типа (совмещение в памяти данных разного типа).
Совмещение данных в памяти может произойти при использовании записей с вариантными полями (см. 4.2.2), типизированных указателей, содержащих одинаковый адрес (см. гл. 6), а также при явном размещении данных разного типа по одному и тому же абсолютному адресу. Для размещения переменной по нужному абсолютному адресу она описывается с последующей стандартной директивой ABSOLUTE, за которой помещается либо абсолютный адрес, либо идентификатор ранее определенной переменной. Абсолютный адрес указывается парой чисел типа WORD, разделенных двоеточием; первое число трактуется как сегмент, второе - как смещение адреса (см. гл. 6). Например:
b : Byte absolute $0000:$0055; w : Longlnt absolute 128:0;
Если за словом ABSOLUTE указан идентификатор ранее определенной переменной, то происходит совмещение в памяти данных разного типа, причем первые байты внутреннего представления этих данных будут располагаться по одному и тому же абсолютному адресу, например:
var
х : Real;
у : array [1..3] of Integer absolute x;
В этом примере переменные X и Y будут размещены, начиная с одного и того же абсолютного адреса. Таким образом, одну и ту же область памяти длиной 6 байт, а следовательно, и размещенные в этой области данные теперь можно рассматривать как данные либо типа REAL, либо как массив из трех данных типа INTEGER. Например, следующая программа выдаст на экран содержимое первых двух байт внутреннего представления вещественного числа п = 3.1415 в виде целого числа:
var
х : Real; у : array[1..3] of Integer absolute x;
begin
х := pi; WriteLn(y[1])
end.
На экран будет выдан результат 8578.
Неявные преобразования типов могут служить источником трудно обнаруживаемых ошибок в программе, поэтому везде, где это возможно, следует избегать их.
Сьруктурированные типы
Любой из структурированных типов (а в Турбо Паскале их четыре: массивы, запиcи, множества и файлы) характеризуется множественностью образующих этот тип элементов, т.е. переменная или константа структурированного типа всегда имеет неcколько компонентов. Каждый компонент, в свою очередь, может принадлежать cтруктурированному типу, что позволяет говорить о возможной вложенности типов. В Турбо Паскале допускается произвольная глубина вложенности типов, однако суммарная длина любого из них во внутреннем представлении не должна превышать -5520 байт.
В целях совместимости со стандартным Паскалем в Турбо Паскале разрешается черед описанием структурированного типа ставить зарезервированное слово PACKED, предписывающее компилятору, по возможности, экономить память, отводимую под объекты структурированного типа; но компилятор фактически игнорирует это указание: «упаковка» данных в Турбо Паскале осуществляется автоматически зезде, где это возможно.
Строки
Тип STRING (строка) в Турбо Паскале широко используется для обработки текстов. Он во многом похож на одномерный массив символов ARRAY[O..N] OF CHAR, однако, в отличие от последнего, количество символов в строке-переменной может меняться от 0 до N, где N - максимальное количество символов в строке. Значение N определяется объявлением типа STRING [N] и может быть любой константой порядкового типа, но не больше 255 . Турбо Паскаль разрешает не указывать N, в этом случае длина строки принимается максимально возможной, а именно N=255 .
Строка в Турбо Паскале трактуется как цепочка символов. К любому символу в строке можно обратиться точно так же, как к элементу одномерного массива ARRAY [0..N] OF CHAR, например:
var
st : String;
begin
.....
if st[5] = 'A' then...
end.
Самый первый байт в строке имеет индекс 0 и содержит текущую длину строки, первый значащий символ строки занимает второй байт и имеет индекс 1. Над длиной троки можно осуществлять необходимые действия и таким способом изменять длину. Например, удалить из строки все ведомые пробелы можно следующим образом:
var
st : String;
i : Byte;
begin
i := ord(st [0] ) ; {i - текущая длина строки}
while (i <> 0) and (st[i] = ' ') do
begin
dec(i);
st[0] := chr(i)
end;
.....
end.
Значение ORD(st[0]) , т.е. текущую длину строки, можно получить и с помощью функции LENGTH(st), например:
while (Length(st)<>0) and (st[Length(st)]=' ') do
st[0] := chr(Length(st)-1)
К строкам можно применять операцию «+» - сцепление, например:
st := 'а1 + 'b';
st := st + 'с'; {st содержит "abc"}
Если длина сцепленной строки превысит максимально допустимую длину N, то «лишние» символы отбрасываются. Следующая программа, например, напечатает символ 1:
var
st: String [1] ;
begin
St:='123';
WriteLn(st)
end.
Все остальные действия над строками и символами реализуются с помощью описываемых ниже стандартных процедур и функций.
CONCAT(S1 [,S2, ... , SN] ) - функция типа STRING; возвращает строку, представляющую собой сцепление строк-параметров SI, S2, ..., SN.
COPY(ST, INDEX, COUNT) - функция типа STRING; копирует из строки ST COUNT символов, начиная с символа с номером INDEX.
DELETE (ST, INDEX, COUNT) - процедура; удаляет СОUNT символов из строки ST, начиная с символа с номером INDEX.
INSERT (SUBST, ST, INDEX) - процедура; вставляет подстроку SUBST в строку ST, начиная с символа с номером INDEX.
LENGTH (ST) - функция типа INTEGER; возвращает длину строки ST.
POS (SUBST, ST) - функция типа INTEGER; отыскивает в строке STпервое вхождение подстроки SUBST и возвращает номер позиции, с которой она начинается; если подстрока не найдена, возвращается ноль.
STR(X [; WIDTH [: DECIMALS] ], ST) - процедура; преобразует число X любого вещественного или целого типов в строку символов ST так, как это делает процедура WRITELN перед выводом; параметры WIDTH и DECIMALS, если они присутствуют, задают формат преобразования: WIDTH определяет общую ширину поля, выделенного под соответствующее символьное представление вещественного или целого числа X, a DECIMALS - количество символов в дробной части (этот параметр имеет смысл только в том случае, когда Х- вещественное число).
VAL(ST, X, CODE) - процедура; преобразует строку символов ST во внутреннее представление целой или вещественной переменной X, которое определяется типом этой переменной; параметр CODE содержит ноль, если преобразование прошло успешно, и тогда в X помещается результат преобразований, в противном случае он содержит номер позиции в строке ST, где обнаружен ошибочный символ, и в этом случае содержимое Х не меняется; в строке ST могут быть ведущие пробелы, однако ведомые пробелы недопустимы; например, обращение val (' 123',k,i) пройдет успешно: k получит значений 123, в i будет помещен 0, в то время как обращение val (' 123 ' , k, i) будет ошибочным: значение k не изменится, a i будет содержать 4.
UPCASE (СН) - функция типа CHAR; возвращает для символьного выражения СН, которое должно представлять собой строчную латинскую букву, соответствующую заглавную букву; если значением СН является любой другой символ (в том числе строчная буква русского алфавита), функция возвращает его без преобразования.
Примеры:
var
х : Real;
у : Integer;
st,st1: String;
begin
st := concat('12','345'); {строка st содержит 12345}
st1 := copy(st,3,Length(st)-2); {st1 содержит 345}
insert('-',st1,2); {строка st1 содержит 3-45}
delete(st,pos('2',at),3); {строка st содержит 15}
str(pi:6:2,st); {строка st содержит 3.14}
val(''3,1415' ,x,y) ; {у содержит 2, х остался без изменения}
end.
Операции отношения =, о, >, <, >=, <= выполняются над двумя строками посимвольно, слева направо с учетом внутренней кодировки символов (см. табл.4.1 и прил.2). Если одна строка меньше другой по длине, недостающие символы короткой строки заменяются значением СНR(0) .
Следующие операции отношения дадут значение TRUE:
'''' < ' . '
'А' > '1'
'Turbo' <' Turbo Pascal'
'Паскаль' >'Turbo Pascal'
Вещественные типы
В отличие от порядковых типов, значения которых всегда сопоставляются с рядом целых чисел и, следовательно, представляются в ПК абсолютно точно, значения вещественных типов определяют произвольное число лишь с некоторой конечной точностью, зависящей от внутреннего формата вещественного числа.
Таблица 4.4
Как видно из табл.4.4, вещественное число в Турбо Паскале занимает от 6 до 10 смежных байт и имеет следующую структуру в памяти ПК:
Здесь s - знаковый разряд числа; е - экспоненциальная часть; содержит двоичный порядок; m - мантисса числа.
Мантисса m имеет длину от 23 (для SINGLE) до 63 (для EXTENDED) двоичных разрядов, что и обеспечивает точность 7.. .8 для SINGLE и 19.. .20 для EXTENDED десятичных цифр. Десятичная точка (запятая) подразумевается перед левым (старшим) разрядом мантиссы, но при действиях с числом ее положение сдвигается влево или вправо в соответствии с двоичным порядком числа, хранящимся в экспоненциальной части, поэтому действия над вещественными числами называют арифметикой с плавающей точкой (запятой).
Как видим, Турбо Паскаль характеризуется богатой гаммой вещественных типов, однако доступ к типам SINGLE, DOUBLE и EXTENDED возможен только при особых режимах компиляции. Дело в том, что эти типы рассчитаны на аппаратную поддержку арифметики с плавающей точкой и для их эффективного использования в состав ПК должен входить арифметический сопроцессор. Компилятор Турбо Паскаля позволяет создавать программы, работающие на любых ПК (с сопроцессором или без него) и использующие любые вещественные типы. Необходимая для этого настройка компилятора описана в прил.1. В процессе запуска Турбо Паскаль проверяет состав аппаратных средств и выявляет наличие или отсутствие сопроцессора.
В некоторых случаях бывает необходимо отключить автоконтроль. Для этого перед запуском Турбо Паскаля следует дать такую команду ДОС:
set 87=N
команда
set 87=Y
напротив, включает автоконтроль - эта команда активна по умолчанию.
Отметим, что арифметический сопроцессор всегда обрабатывает числа в формате EXTENDED, а три других вещественных типа в этом случае получаются простым усечением результатов до нужных размеров и применяются в основном для экономии памяти.
Например, если «машинное эпсилон» (см. пример 2.6 в гл.2) вычисляется с помощью такой программы:
{$N+,E+}
type
RealType = Real:
var
epsilon : RealType;
begin
epsilon := 1;
while 1+epsilon/2 > 1 do
epsilon := epsilon/2;
WriteLn(epsilon)
end.
то независимо от объявления типа REALTYPE (он может быть SINGLE, REAL, DOUBLE или EXTENDED) на печать будет выдан результат
1.08420217248550Е-0019
что соответствует типу EXTENDED. Происходит это по той причине, что все операнды вещественного выражения 1 + epsilon/2 в операторе WHILE, перед вычислением автоматически преобразуются к типу EXTENDED. Чтобы получить правильный результат (например, для типа REALTYPE = REAL он будет 9 . 09494701772928Е-0013), программу необходимо изменить следующим образом:
{$N+,E+}
type
RealType= Real;
var
epsilon, epsl:RealType;
begin
epsilon := 1;
repeat
epsilon := epsilon/2;
epsl := 1 + epsilon
until epsl = 1;
WriteLn(2*epsilon)
end.
Следует учесть, что тип REAL оптимизирован для работы без сопроцессора. Если Ваш ПК оснащен сопроцессором, использование типа REAL приведет к дополнительным затратам времени на преобразование REAL к EXTENDED. Поэтому никогда не используйте REAL на ПК с сопроцессором, т.к. дополнительные затраты времени на преобразование типов могут свести на нет все преимущества сопроцессора. При разработке программ, критичных ко времени счета, следует заменять его типами SINGLE или DOUBLE: по сравнению с типом REAL скорость вычислений на машинах с сопроцессором в этом случае увеличивается в 2...3 раза. Если в ПК нет арифметического сопроцессора, скорость обработки данных всех вещественных типов приблизительно одинакова.
Особое положение в Турбо Паскале занимает тип СОМР, который трактуется как вещественное число без экспоненциальной и дробной частей. Фактически, СОМР - это «большое» целое число со знаком, сохраняющее 19...20 значащих десятичных цифр (во внутреннем представлении СОМР занимает 8 смежных байт). В то же время в выражениях СОМР полностью совместим с любыми другими вещественными типами: над ним определены все вещественные операции, он может использоваться как аргумент математических функций и т.д. Наиболее подходящей областью применения типа СОМР являются бухгалтерские расчеты: денежные суммы выражаются в копейках или центах и действия над ними сводятся к операциям с достаточно длинными целыми числами.
Для работы с вещественными данными могут использоваться встроенные математические функции, представленные в табл. 2.5. В этой таблице REAL означает любой вещественный тип, INTEGER - любой целый тип.
Таблица 4.5
Записи
Запись - это структура данных, состоящая из фиксированного числа компонентов, называемых полями записи. В отличие от массива, компоненты (поля) записи могут быть различного типа. Чтобы можно было ссылаться на тот или иной компонент записи, поля именуются.
Структура объявления типа записи такова:
<имя типа> = RECORD <сп.полей> END
Здесь <имя типа> - правильный идентификатор;
RECORD, END - зарезервированные слова (запись,конец); <сп.полей> - список полей; представляет собой последовательность разделов записи, между которыми ставится точка с запятой.
Каждый раздел записи состоит из одного или нескольких идентификаторов полей, отделяемых друг от друга запятыми. За идентификатором (идентификаторами) ставится двоеточие и описание типа поля (полей), например:
type
BirthDay = record
day,month : Byte;
year : Word
end;
var
a,b : Birthday;
.......
В этом примере тип BIRTHDAY (день рождения) есть запись с полями DAY, MONTH и YEAR (день, месяц и год); переменные А и В содержат записи типа BIRTHDAY.
Как и в массиве, значения переменных типа записи можно присваивать другим переменным того же типа, например
а := b;
К каждому из компонентов записи можно получить доступ, если использовать составное имя, т.е. указать имя переменной, затем точку и имя поля:
а.day := 27;
b.year := 1939;
Для вложенных полей приходится продолжать уточнения:
type
BirthDay = record
day,month: Byte;
year : Word
end;
var
с : record
name : String;
bd : BirthDay
end;
begin
.....
if c.bd.year = 1939 then ...
end.
Чтобы упростить доступ к полям записи, используется оператор присоединения WITH:
WITH <переменная> DO <оператор>
Здесь WITH, DO - ключевые слова (с, делать);
<переменная> - имя переменной типа запись, за которым, возможно, следует список вложенных полей; <оператор> - любой оператор Турбо Паскаля.
Например:
with c.bd do month := 9;
Это эквивалентно
with с do with bd do month := 9;
или
with c,bd do month := 9;
или
c.bd.month := 9;
Турбо Паскаль разрешает использовать записи с так называемыми вариантными полями, например:
type
Forma = record
Name: String;
case Byte of
0: (Birthplace: String [40]);
1: (Country : String [20];
EntryPort : String [20];
EntryDate : 1. . 31;
ExitDate : 1..31)
end;
В этом примере тип FORMA определяет запись с одним фиксированным полем NAME и вариантной частью, которая задается предложением CASE... OF. Вариантная часть состоит из нескольких вариантов (в примере - из двух вариантов: 0 и 1). Каждый вариант определяется константой выбора, за которой следует двоеточие и список полей, заключенный в круглые скобки. В любой записи может быть только одна вариантная часть, и, если она есть, она должна располагаться за всеми фиксированными полями.
Замечательной особенностью вариантной части является то обстоятельство, что все заданные в ней варианты «накладываются» друг на друга, т.е. каждому из них выделяется одна и та же область памяти. Это открывает дополнительные возможности преобразования типов, например:
var
mem4 : record case Byte of
0 : (by : array'[0..3] of Byte);
1 : (wo : array [0..1] of Word);
2 : (lo : longint);
end;
В этом примере запись МЕМ4 имеет три варианта, каждый из которых занимает в памяти один и тот же участок из 4 байт. В зависимости от того, к какому полю записи мы обращаемся в программе, этот участок может рассматриваться как массив из 4 байт (поле ВТ), массив из двух целых типа WORD (поле WO) или, наконец, как одно целое число типа LONGINT (поле LO). Например, этой записи можно сначала присвоить значение как длинному целому, а затем проанализировать результат по байтам или словам:
var
х : Word;
xb: Byte;
x1: Longint;
begin
.....
with m do
begin
lo := trunc(2*pi*x);
if wo[1] = 0
then if by[l] = 0 then
xb := x[0]
else
x := wo[0]
else
x1 := lo
end;
.....
end.
Предложение CASE... OF, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле лишь играет роль своеобразного служебного слова, обозначающего начало вариантной части. Именно поэтому в конце вариантной части не следует ставить END как пару к CASE... OF. (Поскольку вариантная часть - всегда последняя в записи, за ней все же стоит END, но лишь как пара к RECORD). Ключ выбора в предложении CASE... OF фактически игнорируется компилятором: единственное требование, предъявляемое к нему Турбо Паскалем, состоит в том, чтобы ключ определял некоторый стандартный или предварительно объявленный порядковый тип. Причем сам этот тип никак не влияет ни на количество следующих ниже вариантных полей, ни даже на характер констант выбора. В стандартном Паскале в качестве ключа выбора необходимо указывать некоторую переменную порядкового типа, причем в исполняемой части программы можно присваивать значение этой переменной и таким образом влиять на выбор полей. В Турбо Паскале также можно в поле ключа выбора указывать переменную порядкового типа и даже присваивать ей в программе значение, что однако не влияет на выбор поля: значения констант выбора в Турбо Паскале могут быть произвольными, в том числе повторяющимися, например:
type
reel = record
a : Byte;
b : Word;
end;
rec2 = record
с : longint;
case x : Byte of
1 : (d : Word);
2 : (e : record
case Boolean of
3 :( freel);
3 :( g Single);
'3':( с Word);
end)
end;
var
r : rec2;
begin
r.x := 255;
if r.e.g = 0 then
WriteLn('O.K. ')
else
WriteLn(r.e.g)
end.
В этом примере предложение
case Boolean of
в записи, определяемой в поле Е, объявляет ключом выбора логический тип, который, как известно, имеет лишь два значения - TRUE и FALSE. Константы же выбора следующих далее вариантов не только содержат совершенно не свойственные этому типу значения, но и две из них повторяются, а общее количество вариантов - три, а не два, как следовало бы ожидать.
Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако, если записи содержат поля-записи, т.е. вложены одна в другую, имена могут повторяться на разных уровнях вложенности (см. поле С в последнем примере).