алгоритмический язык паскаль - Псковский государственный

advertisement
Федеральное агентство по образованию
ПСКОВСКИЙ ГОСУДАРСТВЕННЫЙ
ПОЛИТЕХНИЧЕСКИЙ ИНСТИТУТ
Полетаев И.А., Полетаев Д.И., Полетаева О.А.
ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ
ВЫСОКОГО УРОВНЯ ПАСКАЛЬ
Учебное пособие
Для студентов технических специальностей всех форм обучения
Рекомендовано к изданию научно-методическим советом
Псковского государственного политехнического института
Псков
Издательство ППИ
2010
УДК 681.3.062;004.432
ББК 32.973.26-018.1
П 49
Рекомендовано к изданию научно-методическим советом
Псковского государственного политехнического института
Рецензенты:
Колесников Ю.В. начальник управления информационных технологий
Администрации Псковской области
Ильин С.Н. зам. генерального директора ОАО «СКБ Вычислительной техники»
Полетаев И.А., Полетаев Д.И., Полетаева О.А. Программирование на языке
высокого уровня Паскаль. Учебное пособие. – Псков, Изд-во ППИ, 2010. – 160 с.
В учебном пособии «Программирование на языке высокого уровня Паскаль»
изложены основные принципы и этапы разработки программного обеспечения с
использованием языков программирования, включая составление алгоритмов.
Описывается алгоритмический язык Паскаль с использованием системы программирования Турбо-Паскаль для ПЭВМ в среде MS DOS. Рассмотрены структура
программы, стандартные типы данных, операторы языка, использование подпрограмм и модулей, динамические структуры и доступ к аппаратным функциям.
Освещаются общие вопросы объектно-ориентированного программирования.
Пособие предназначено для студентов всех форм обучения специальностей
230101– «Вычислительные машины, комплексы, системы и сети», 230201 - «Информационные системы и технологии», а так же может быть использовано для
студентов других технических специальностей.
Табл. 5. Ил. 12. Библиогр. 14 назв.
©
©
Полетаев И.А., Полетаев Д.И.,
Полетаева О.А., 2010
Псковский государственный
политехнический институт, 2010
Содержание
Глава1. Основные понятия программирования ......... 6
§1.1. Этапы разработки программного обеспечения ............. 6
§1.2. Основные сведения об алгоритмах ................................ 9
Понятие алгоритма................................................................. 9
Свойства алгоритма.............................................................. 10
Формы записи алгоритмов .................................................... 11
Блок-схемы алгоритмов ........................................................ 13
§1.3. Языки программирования ........................................... 22
§1.4. Паскаль и Object Pascal, Турбо-Паскаль и Delphi ......... 25
Язык, оболочка и интегрированная среда разработки ...... 28
Контрольные вопросы............................................................ 29
Глава 2. Общие сведения о языке Паскаль ............... 31
§2.1. Алфавит и служебные слова ........................................ 31
Описание общих конструкций языка ................................... 31
§2.2. Структура программы ................................................. 32
§2.3. Разделы описаний........................................................ 34
Описание меток ..................................................................... 34
Определение констант ......................................................... 35
Определение типов ................................................................ 36
Описание переменных ............................................................ 37
Контрольные вопросы............................................................ 37
Глава 3. Простые типы данных ............................... 39
Иерархия типов ...................................................................... 39
§3.1. Целые типы.................................................................. 40
§3.2. Вещественные типы .................................................... 41
§3.3. Логический тип............................................................ 43
§3.4. Литерный тип .............................................................. 44
§3.5. Перечисляемый тип ..................................................... 45
§3.6. Диапазоны (интервальный тип) ................................... 46
Дополнительные типы в Delphi ........................................... 46
Контрольные вопросы............................................................ 47
Глава 4. Операторы языка Паскаль ........................ 50
Выражения .............................................................................. 50
§4.1. Оператор присваивания .............................................. 52
§4.2. Ввод-вывод данных ..................................................... 52
§4.3. Составной оператор..................................................... 54
§4.4. Пустой оператор .......................................................... 54
§4.5. Безусловный оператор перехода ................................. 54
§4.6. Условный оператор ...................................................... 55
3
§4.7. Оператор цикла с параметром FOR ............................. 56
§4.8. Оператор цикла с предусловием WHILE ...................... 57
§4.9. Оператор цикла с постусловием REPEAT..................... 59
§4.10. Оператор выбора CASE ............................................. 60
Контрольные вопросы............................................................ 62
Глава 5. Процедурное программирование ................. 65
§5.1. Функции ...................................................................... 65
Параметры – значения .......................................................... 66
Параметры – переменные..................................................... 66
Бестиповые параметры ........................................................ 67
§5.2. Процедуры ................................................................... 67
Правила использования подпрограмм .................................. 68
Рекурсия .................................................................................. 69
Директивы .............................................................................. 70
Процедурные типы ................................................................ 71
Пример использования подпрограмм ................................... 72
Контрольные вопросы............................................................ 74
Глава 6. Структурированные типы данных ........... 75
§6.1. Массивы ...................................................................... 75
§6.2. Строки ......................................................................... 76
§6.3. Записи ......................................................................... 78
§6.4. Множества ................................................................... 81
Пример использования множеств ........................................ 83
§6.5. Файлы .......................................................................... 84
Установочные и завершающие операции ............................ 86
Операции ввода-вывода .......................................................... 87
Перемещения по файлу ......................................................... 87
Пример работы с файлом ..................................................... 88
Обработка ошибок ввода-вывода .......................................... 89
Специальные операции .......................................................... 90
Пример объединения двух файлов ........................................ 91
Текстовые файлы ................................................................... 92
Стандартные текстовые файлы ........................................ 94
Файлы без типа ...................................................................... 96
Контрольные вопросы............................................................ 97
Глава 7. Динамические структуры данных .......... 100
§7.1. Динамические переменные ....................................... 100
§7.2. Указатели................................................................... 101
Работа с динамическими переменными ............................ 104
Пример двухсвязанного циклического списка .................... 107
Указатели без типа ............................................................. 109
4
Контрольные вопросы.......................................................... 109
Глава 8. Низкоуровневые возможности Паскаля .. 111
§8.1. Язык Ассемблер ......................................................... 111
§8.2. Доступ к аппаратуре ................................................. 116
Доступ к памяти ................................................................. 116
Доступ к портам ввода-вывода .......................................... 116
Работа по прерываниям ...................................................... 118
Контрольные вопросы.......................................................... 120
Глава 9. Модули ........................................................ 122
§9.1. Основные понятия ..................................................... 122
§9.2. Использование модулей ............................................. 124
§9.3. Стандартные модули ................................................. 126
§9.4. Модуль Crt ................................................................. 127
Работа с экраном в текстовом режиме ............................ 130
Работа с клавиатурой ......................................................... 131
Работа со звуком .................................................................. 132
§9.5. Модуль DOS ............................................................... 133
§9.6. Графический режим монитора .................................. 138
Контрольные вопросы.......................................................... 142
Глава 10. Введение в объектно-ориентированное
программирование ................................................... 145
§10.1. История развития программирования .................... 145
Понятие объекта ................................................................. 145
§10.2. Свойства объектов ................................................... 147
Раннее и позднее связывание ............................................... 151
§10.3. Виртуальные методы ............................................... 152
Конструкторы и деструкторы .......................................... 153
§10.4. Динамические объекты ........................................... 155
Скрытые поля и методы..................................................... 156
Контрольные вопросы.......................................................... 156
Литература ............................................................. 158
5
Глава 1. Основные понятия
программирования
§1.1. Этапы разработки программного
обеспечения
Профессиональное программирование подразумевает, что
результатом труда, – программным продуктом, – будет пользоваться определенный круг людей, пользователей. На этапе разработки программы, в которой может участвовать группа
людей, пользователей представляет Заказчик.
Для выполнения задачи создания и эксплуатации программного обеспечения ее разбивают на определенные этапы:
1.
2.
3.
4.
5.
Постановка задачи.
Составление алгоритма.
Составление и ввод программы.
Отладка и тестирование программы.
Сопровождение программного продукта.
Создание любой программы начинается с постановки
задачи. Изначально задача ставится в терминах некоторой
предметной области, и необходимо перевести ее в понятия и
выражения, более близкие к программированию. Поскольку
программист первоначально редко досконально разбирается в
предметной области, а Заказчик – в программировании, то постановка задачи может стать весьма непростым итерационным
процессом.
Постановка задачи заканчивается созданием технического задания, а затем и внешней спецификации программы,
которая включает в себя:
1.
2.
3.
4.
Описание исходных данных и результатов (виды, представление, точность, ограничения и т.п.).
Описание задачи, реализуемой программой.
Способ обращения к программе.
Описание возможных особых и аварийных ситуаций и
ошибок пользователя.
На этом этапе программа рассматривается как «черный
ящик», для которого определяется выполняемая им функция,
входные и выходные данные. Каким образом достигается выполнение функций, здесь не указывается.
6
На втором этапе разрабатываются алгоритмы, задаваемые спецификациями, и формируется (проектируется) общая
структура программ [3]. Здесь обычно применяется технология
нисходящего проектирования с использованием метода пошаговой детализации. То есть сначала составляется укрупненный алгоритм в самом общем виде. Затем уточняются шаги
(блоки) с более подробным описанием. На этом этапе описания
производятся на языке, понятном человеку, используя определенную форму записи алгоритма. В программировании широко
используется графическая форма записи в виде блок-схем или
граф-схем.
Третий этап как раз и является непосредственно программированием на языке, понятном ЭВМ. По своей сути третий этап является продолжением второго, так как программа
тоже есть форма записи алгоритма с максимальной степенью
детализации, – программная.
Изучению одного из языков программирования высокого
уровня и посвящается данный курс.
Четвертый этап подразумевает устранение всех ошибок
и недопониманий, возникших на предыдущих этапах. Человеку
свойственно ошибаться, поэтому четвертому этапу уделяется
много внимания.
Существуют самые разнообразные методы и рекомендации по тестированию и отладке. Необходимо различать эти два
понятия. Тестирование представляет собой процесс, посредством которого проверяется правильность функционирования
программы и соответствие всем проектным спецификациям. В
частности, для этих целей создается набор тестов. Отладка –
процесс исправления ошибок в программе. Так, при отладке
исправляются синтаксические ошибки, алгоритмические,
ошибки, обнаруженные при тестировании и другие.
Пятый этап наступает, когда программный продукт
сдан в эксплуатацию (или начались его продажи). Здесь так же
возможно обнаружение не найденных на этапе тестирования
ошибок, – их необходимо локализовать и исправить. Кроме этого, возможно изменение свойств программы для удобства эксплуатации: элементов интерфейса, некоторых функций и т.д.
Казалось бы, пятый этап самый простой. Но ему отводится самая большая часть затрат времени и средств: до половины и
более.
7
Все эти этапы разработки и сопровождения программного продукта, включая завершение поддержки эксплуатации,
составляют жизненный цикл программы.
Возможно и другое деление на этапы [1] с приблизительным делением по времени реализации, как указано на рис. 1.1:
1.
2.
3.
4.
5.
6.
Анализ требований.
Определение спецификаций.
Проектирование.
Кодирование.
Автономное тестирование.
Комплексное тестирование.
Рис. 1.1. Временные затраты на реализацию этапов цикла
разработки программного обеспечения (за исключением этапа
эксплуатации и сопровождения) [1]
На последний же этап эксплуатации и сопровождения
объемных программных продуктов отводится более половины
времени: до 67% от общего времени жизненного цикла.
Классическим называется следующий набор технологических этапов (процессов) [2]:
1.
2.
3.
4.
5.
6.
7.
Возникновение и исследование идеи
Управление
Анализ требований
Проектирование
Программирование
Тестирование и отладка
Ввод в действие
8
8.
9.
Эксплуатация и сопровождение
Завершение эксплуатации
Процессы жизненного цикла программного обеспечения
определены международным стандартом ISO 12207 [ISO/IEC
12207:1995] и делятся на три группы (без привязки ко времени) [2]:
 Основные процессы: приобретение, поставка, разработка,
эксплуатация, сопровождение.
 Вспомогательные процессы: документирование, управление конфигурацией, обеспечение качества, верификация,
аттестация, совместная оценка, аудит, разрешение проблем.
 Организационные процессы: управление, создание инфраструктуры, усовершенствование, обучение.
§1.2. Основные сведения об алгоритмах
Понятие алгоритма
Алгоритм – это формальное предписание, однозначно
определяющее содержание и последовательность операций,
переводящих совокупность исходных данных в искомый результат – решение задачи.
Иначе говоря, алгоритм – это набор понятных исполнителю инструкций (команд), точное выполнение которых приводит к достижению требуемого результата.
Алгоритм позволяет формализовать выполнение информационного процесса. Если исполнителем является человек, то
он может выполнять алгоритм формально, не вникая в содержание поставленной задачи, а только строго выполняя последовательность
действий,
предусмотренную
алгоритмом.
Представление информационного процесса в форме алгоритма
позволяет поручить его автоматическое исполнение различным
техническим устройствам, среди которых особое место занимает компьютер. При этом говорят, что компьютер исполняет
программу (последовательность команд), реализующую алгоритм.
С алгоритмами связаны следующие области исследований [2].
 Анализ алгоритмов. Предмет этой области состоит в том,
чтобы для заданного алгоритма определить рабочие харак9
теристики. Например, часто требуется, чтобы алгоритм
был быстрым.
 Теория алгоритмов. К этой области относятся вопросы
существования или отсутствия эффективных алгоритмов
вычисления определенных величин.
 Построение алгоритмов. В этой области рассматриваются стандартные приемы и методы, используемые при создании алгоритмов.
Свойства алгоритма
Все алгоритмы обладают рядом свойств. Основными
свойствами алгоритмов являются [4]:
1.
2.
3.
4.
Конечность (результативность, финитность). Свойство так определять процесс преобразования исходных
данных, чтобы он через конечное число шагов для любых
допустимых исходных данных приводил к искомому результату. Процедуру, обладающую всеми характеристиками алгоритма, за исключением конечности, можно
назвать вычислительным методом.
Определенность (детерминированность). Предполагает
такое составление алгоритма, которое не допускает различных толкований или искажения результата. При словесном описании алгоритмов оно дается на обычном
разговорном языке, поэтому не исключена возможность
неточного понимания предписания, например, человеком. Чтобы преодолеть эту трудность, для описания алгоритмов разработаны формально определенные языки
программирования, в которых каждое утверждение имеет абсолютно точный смысл.
Ввод (массовость, наличие входных данных). Определяет возможность использования любых исходных данных
из некоторого определенного множества для однотипных
задач. Так, правило умножения столбиком является алгоритмом, т.к. оно используется для любых чисел (как целых, так и вещественных или дробных), но таблица
умножения — не алгоритм. Это свойство подразумевает в
программе наличие блока ввода, но в некоторых случаях
число входных данных может быть равно нулю [4].
Вывод (наличие выходных данных). Алгоритм имеет
одно или несколько выходных данных, имеющих определенную связь с входными данными. Здесь подразумевает10
5.
ся наличие в программе блока вывода, иначе выполнение
программы становится бессмысленным.
Эффективность. Алгоритм считается эффективным, если его операторы достаточно просты для того, чтобы их
можно было точно выполнить в течение конечного промежутка времени.
Иногда приводят и дополнительные свойства алгоритмов,
например:
Направленность. Означает наличие способа однозначного перехода от одного действия к другому.
Дискретность. Свойство, означающее, что алгоритм разбивается на последовательные команды, возможность выполнения которых человеком или машиной (исполнителем) не
вызывает сомнений.
Понятность. Означает, что все команды алгоритма
должны быть понятны для конкретных исполнителей.
Формы записи алгоритмов
Существуют три основных способа записи или представления алгоритма [5]:
1.
2.
3.
словесная (текстуальная) запись алгоритма;
описание на алгоритмическом языке;
структурная схема (графическая схема).
Словесное описание алгоритма представляет собой
текст, в котором на каком-либо разговорном языке (например,
на русском) по пунктам записана последовательность действий. Строгие требования к форме такой записи не предъявляются, но существуют определенные правила, выполнение
которых облегчает понимание алгоритма. Все действия расписываются по шагам или нумеруются, чтобы было удобно ссылаться при необходимости по номеру шага или пункта. Начало
алгоритма и его окончание иногда отмечают словами «начало»
и «конец». Можно указывать в одном пункте не одно действие,
а группу простых действий.
Пример. Вычисление 3 a . Вычисление производится по
итерационной формуле

1 a
xn 1  
 2 xn  .

3  xn2

11
При последовательных приближениях необходимо задавать точность, с которой подсчитывается выходная величина.
Обозначим ее как ε. За начальное приближение примем само
значение а.
Шаг 1. Определим начальные значения а и ε.
Шаг 2. Выберем начальное приближение хп равным а.
Шаг 3. Вычислим следующее приближение хп+1 по формуле

1 a
xn 1  
 2 xn  .

3  xn2

Шаг 4. Если xn1  xn   , то хп+1 – искомый результат, и алгоритм окончен.
Шаг 5. Положим хп = хп+1 и перейдем к шагу 3.
Количество шагов для этой задачи может быть и другим,
например, если в шаге 4 записать «…, то перейти к шагу 6». И
далее
Шаг 6. Вывести значение хп+1.
Шаг 7. Конец алгоритма.
В словесной форме удобно задавать алгоритмы для людей.
Например, инструкции по эксплуатации различных бытовых
приборов можно рассматривать как алгоритм. Но для ЭВМ при
сложных задачах, с множеством ветвлений и циклов такая
форма записи не наглядна.
Любая программа является представлением алгоритма.
Но наиболее наглядными с точки зрения человека являются
программы на алгоритмических языках высокого уровня. Но
программа есть представление алгоритма с максимальной степенью детализации, и укрупненные алгоритмы в такой форме
представления записывать нельзя. По способу записи эта форма близка к словесной, но со строгими формальными правилами.
Графическое представление алгоритма изображается
в виде блок-схем или граф-схем. Рисунки, или графические
изображения считаются наиболее наглядным представлением
информации, поэтому и сложные алгоритмы наиболее удобны и
понятны при их графическом представлении. Здесь отдельные
шаги представляются в виде геометрических фигур, соединенных линиями, указывающими последовательность перехода от
12
блока к блоку. Конструкторскую документацию по алгоритмам
при разработке программных продуктов оформляют в виде
блок-схем, поэтому существуют определенные стандарты в
ЕСПД, регламентирующие запись алгоритмов [6]. Более подробно они будут рассмотрены далее.
Существуют и другие формы записи алгоритмов [3]:
1.
2.
3.
4.
операторный способ;
с использованием диаграммы Нэсси-Шнейдермана;
с использованием Р-схемы;
с помощью псевдокода.
Последний способ получил достаточно широкое распространение и является промежуточным перед записью алгоритма в терминах выбранного языка программирования. По сути
своей является симбиозом естественного языка и языка программирования. В некоторых случаях псевдокод практически
является алгоритмическим языком высокого уровня.
Блок-схемы алгоритмов
В настоящее время в вычислительной технике для записи
алгоритмов используется «ГОСТ 19.701-90. Схемы алгоритмов,
программ, данных и систем. Условные обозначения и правила
выполнения». Он входит в группу стандартов единой системы
программной документации (ЕСПД) [6]. Этот стандарт введен
взамен «ГОСТ 19.002-80. Схемы алгоритмов и программ. Правила выполнения. ГОСТ 19.003-80. Схемы алгоритмов и программ. Обозначения условные графические» [7,8]. Но стандарт
от 1990 года не совсем полный, в частности, там отсутствуют
размеры и отношения сторон блоков, изменены название и
трактовка некоторых из них. Поэтому совместно с ГОСТ
19.701-90 рекомендуется использовать и ГОСТ 19.002-80, ГОСТ
19.003-80.
Далее приводятся выдержки из этих стандартов. В них
графические блоки называются символами.
2.2. Схема программы.
2.2.1. Схемы программ отображают последовательность
операций в программе.
2.2.2. Схема программы состоит из:
1) символов (т.е. блоков) процесса, указывающих фактические операции обработки данных (включая символы, опреде-
13
ляющие путь, которого следует придерживаться с учетом логических условий);
2) линейных символов, указывающих поток управления;
3) специальных символов, используемых для облегчения
написания и чтения схемы.
Из-за разночтений в табл. 1.1 приводится описание основных символов (в стандарте их существенно больше) как по
ГОСТ 19.701-90, так и по ГОСТ 19.003-80.
Таблица 1.1. Наименование и обозначение символов и
отображаемые ими функции
Обозначение и
размеры
Наименование и функция по ГОСТ 19.701-90
Наименование и функция по ГОСТ 19.003-80
Процесс. Символ отображает функцию обработки данных любого
вида (выполнение операции или группы операций, приводящие к
изменению
значения,
формы или размещения
информации
или
к
определению, по которому из нескольких
направлений
потока
следует двигаться).
Линия. Символ отображает поток данных
или управления. При
необходимости или для
повышения удобочитаемости могут быть добавлены
стрелкиуказатели.
Процесс. Выполнение
операций или группы
операций, в результате
которых
изменяется
значение, форма представления или расположение данных.
14
Линия потока. Указание последовательности
перехода между символами. Направления линии потока сверху вниз
и слева направо принимают за основные и,
если линии потока не
имеют изломов, стрелками можно не обозначать.
В
остальных
случаях
направление
линии потока обозначать стрелкой обязательно.
Продолжение табл. 1.1.
1
2
3
Решение. Символ отображает решение или функцию
переключательного
типа, имеющую один вход
и ряд альтернативных выходов, один и только один
из которых может быть
активизирован после вычисления условий, определенных
внутри
этого
символа.
Соответствующие результаты вычисления могут быть записаны
по соседству с линиями,
отображающими эти пути.
Подготовка.
Символ
отображает модификацию
команды или группы команд с целью воздействия
на некоторую последующую функцию (установка
переключателя, модификация индексного регистра или инициализация
программы).
Граница цикла. Символ,
состоящий из двух частей,
отображает начало и конец цикла. Обе части символа имеют один и тот же
идентификатор. Условия
для инициализации, приращения, завершения и
т.д. помещаются внутри
символа вначале или в
конце в зависимости от
расположения операции,
проверяющей условие.
Решение.
Выбор
направления выполнения алгоритма или
программы в зависимости от некоторых
переменных
условий.
15
Модификация. Выполнение операций,
меняющих команды
или группу команд,
изменяющих
программу.
(отсутствует)
Продолжение табл. 1.1.
1
2
3
Данные. Символ отоб- Ввод-вывод. Преображает данные, носитель разование данных в
данных не определен.
форму,
пригодную
для обработки (ввод)
или отображения результатов обработки
(вывод)
Ручной ввод. Символ
отображает данные, вводимые вручную во время
обработки с устройств
любого типа (клавиатура,
переключатели, кнопки,
световое перо, полоски со
штриховым кодом).
Документ. Символ отображает данные, представленные на носителе в
удобочитаемой
форме
(машинограмма,
документ для оптического или
магнитного считывания,
микрофильм, рулон ленты с итоговыми данными,
бланки
ввода
данных).
Предопределенный
процесс. Символ отображает
предопределенный процесс, состоящий
из одной или нескольких
операций или шагов программы, которые определены в другом месте (в
программе, модуле).
16
Ручной ввод. Ввод
данных вручную при
помощи неавтономных устройств с клавиатурой,
набором
переключателей, кнопок.
Документ.
Вводвывод данных, носителем которых служит
бумага
Предопределенный
процесс.
Использование ранее созданных
и
отдельно
описанных алгоритмов или программ.
Окончание табл. 1.1.
1
2
3
Терминатор.
Символ
отображает
выход
во
внешнюю среду и вход из
внешней среды (начало
или конец схемы программы, внешнее использование и источник или
пункт назначения данных).
Соединитель.
Символ
отображает
выход
в
часть схемы и вход из
другой части этой схемы
и используется для обрыва линии и продолжения
ее в другом месте. Соответствующие
символысоединители должны содержать одно и то же
уникальное обозначение.
Комментарий. Символ
используют для добавления описательных комментариев
или
пояснительных надписей
в целях объяснения или
примечаний.
Пуск
–
останов.
Начало, конец, прерывание
процесса
обработки
данных
или выполнения программы.
Соединитель.
Указание связи между
прерванными линиями потока, связывающими символами.
Комментарий. Связь
между
элементом
схемы и пояснением.
Расстояния между параллельными линиями потока должно быть не менее 3 мм, между остальными символами схемы не менее 5 мм.
Размер a должен выбираться из ряда 10, 15, 20 мм. Допускается увеличивать размер a на число, кратное 5. Размер b
равен 1,5a.
Записи внутри символа или рядом с ним должны быть
краткими. Сокращение слов и аббревиатуры, за исключением
установленных государственными стандартами, должны быть
расшифрованы в нижней части поля схемы или в документе, к
которому эта схема относится.
Координаты зоны символа или порядковый номер проставляют в верхней части символа в разрыве его контура по
аналогии с примером, изображенным на рис. 1.2.
17
Рис. 1.2. Пример фрагмента схемы с обозначением порядковых
номеров символов на схеме
При числе исходов не более трех признак условия решения (Да, Нет, =, <, >) проставляют над каждой выходящей линией потока или справа от линии потока. При числе исходов
более трех условие исхода проставляется в разрыве линии потока. Адрес исхода проставляется в продолжении условия исхода и отделяется от него пробелом (см. рис. 1.3).
Рис. 1.3. Возможные варианты отображения блока решения
Здесь Yi - условие i-го исхода, 011E1, 016A3, 005B5,
015E4 – адреса исходов.
18
Имя цикла,
условие
завершения
Имя цикла
Процесс
Процесс
Имя цикла
Условие
завершения,
имя цикла
Рис. 1.4. Пример использования символов границы цикла.
На рис. 1.4 приводится фрагмент алгоритма с введенным
в ГОСТ 19.701-90 символом «Граница цикла».
19
Начало
Ввод а, ε
xn = a

1 a
xn1   2  2xn 

3 xn

Да
xn1  xn  
Нет
Вывод xn+1
xn= xn+1
Конец
Рис. 1.5. Пример блок-схемы алгоритма вычисления кубического
корня с блоком «решение»
20
Начало
Ввод а, ε
xn+1=a
Цикл 1
xn= xn+1

1 a
xn1   2  2xn 

3 xn

Цикл 1, пока
не станет
xn1  xn  
Вывод xn+1
Конец
Рис. 1.6. Пример блок-схемы алгоритма вычисления кубического
корня с блоками границы цикла
На рис. 1.5 и 1.6 изображены примеры блок-схем одного
и того же алгоритма, но с использованием разных символов.
21
§1.3. Языки программирования
Язык – это естественная или искусственная знаковая система для общения и передачи информации.
По большому счету, при разработке и отладке программ
участвует несколько языков, представленных на рис. 1.7.
Языки моделирования
Языки программирования
высокого уровня
Ассемблеры
Естественные языки
Машинные языки
Рис. 1.7. Языки, участвующие в создании программы
Основные характеристики языков программирования [2]:
1.
2.
3.
4.
5.
6.
7.
Уровень языка. Характеризуется сложностью задач, решаемых с помощью этого языка. Программирование
представляет собой отображение программных объектов,
понятий и явлений предметной области задачи. Чем более
адекватно можно выполнить это отображение, тем выше
уровень языка программирования. А отображение будет
выполнено тем лучше, чем богаче возможности
типообразования языка программирования.
Мощность языка. Характеризуется количеством и многообразием задач, алгоритмы решений которых можно
записать, используя язык.
Концептуальная целостность. Характеризуется свойствами совокупности понятий, служащих для описания
этого языка.
Надежность языка. Должна обеспечивать минимум
ошибок при написании программ. Более того, язык должен быть таким, чтобы неправильные программы было
трудно писать.
Удобочитаемость. Это легкость восприятия программ
человеком. Особенно важна при коллективной работе.
Полнота. Характеризует способность описать класс задач
в некоторой предметной области.
Гибкость. Характеризует легкость выражения необходимых действий.
22
8.
9.
Мобильность. Независимость от аппаратных средств,
обеспечивающая переносимость программного обеспечения.
Эффективность реализации. Должна включать не только эффективную реализацию самого транслятора, но и
генерацию им кода программ.
Некоторые характеристики конфликтуют друг с другом.
Это подчеркивает сложности, с которыми приходится сталкиваться создателям новых языков.
Уровень языка связан с историей и эволюцией языков
программирования:
1.
2.
3.
4.
5.
6.
7.
Поколение 0 (языки нулевого уровня). Это машинные
языки. Они появились с появлением первых ЭВМ в середине 40-х годов ХХ века.
Первое поколение – ассемблеры. Фактически это те же
машинные языки, но программа составляется не в виде
двоичных наборов символов, а с помощью более наглядных мнемонических обозначений. Хотя в них одна ассемблерная команда порождает одну машинную (язык типа
1:1).
Второе поколение – макроассемблеры. Это языки типа
1:n. Используют дополнительные более сложные конструкции. Эти два поколения иногда объединяют. Появились в конце 50-х годов.
Третье поколение – процедурные языки. Это универсальные языки высокого уровня, с помощью которых
можно решать задачи из любых областей. Появились в
начале 60-х годов. Это Алгол, Кобол, Фортран, позже Паскаль, Си и другие.
Четвертое поколение – проблемные языки или языки
поддержки сложных структур данных. Это специализированные языки описания задач типа «заполни бланк».
Используя подобный язык, пользователь сообщает только,
какую задачу надо решить и с какими данными. Например SQL.
Пятое поколение – языки искусственного интеллекта,
такие, как Prolog, Lisp.
Шестое поколение – языки нейронных сетей или самообучающиеся языки.
23
Такое деление имеет прямое отношение и к мобильности.
Первые поколения языков являются машинно-зависимыми,
начиная с процедурных языков – машинно-независимые.
К перечисленным категориям относятся не только языки
для создания программ, но и такие, как языки моделирования,
языки запросов (к базам данных), языки гипертекстовой разметки, языки сценариев и другие. Иногда их все называют
языками программирования, но в этом курсе рассматриваются
только языки, порождающие программу для выполнения на
ЭВМ, а конкретнее – языки третьего поколения.
Существует очень много разнообразных языков программирования, которые определенным образом классифицируются, группируются и объединяются в разнообразные семейства.
Вот некоторые из них:
Семейство первых универсальных языков (прародителей):
Fortran (1954)
Algol 60 (1960)
Basic (1963)
Algol 68 (1968)
PL/1 (1968).
С-семейство языков:
C (1972)
C K&R (1978)
C++ (1983)
C++ ARM (1990)
ISO C90 (1990)
Java (1995)
Limbo (1996)
C++ Std (1998)
ISO C99 (1999)
C# (2000)
Pascal-семейство языков:
Pascal (1970)
Modula-2 (1978)
Object Pascal (1985)
Modula-3 (1988)
Oberon (1988)
Oberon-2 (1991)
Component Pascal (1997)
24
Но широкое распространение получили только некоторые
из них. При универсальном программировании на персональных компьютерах наиболее часто используются языки Си (C++),
Паскаль (Object Pascal), Бейсик (VBA).
Язык С++ используется для профессионального программирования и требует знания аппаратных средств ЭВМ. Язык
Паскаль был специально разработан для обучения компьютерной грамотности и рекомендуется для начального обучения
программированию, поэтому именно он в этом курсе и рассматривается.
§1.4. Паскаль и Object Pascal, Турбо-Паскаль и
Delphi
В 1970 году появилось сообщение еще об одном языке
программирования, названном в честь математика Блеза Паскаля, который еще в 1642 г. изобрел цифровой калькулятор.
Автор – Никлаус Вирт, директор Института информатики
Швейцарской высшей политехнической школы, автор многочисленных работ в области программирования, языков Эйлер,
Модула, Модула-2, методики пошаговой разработки программ.
Первоначальная цель разработки языка диктовалась
необходимостью инструмента «для обучения программированию как систематической дисциплине», в частности, студентов. Однако очень скоро обнаружилась чрезвычайная
эффективность языка Паскаль в самых разнообразных приложениях, от решения небольших задач до разработки сложных
программных систем. Но прошло много времени с момента появления Паскаля на рынке программных продуктов, прежде
чем он получил всеобщее признание, были созданы многочисленные реализации языка практически для всех машинных архитектур. И только в 1983 году был принят международный
стандарт языка ISO 7185:1983.
Первая версия Турбо-Паскаля была написана молодым
датчанином Андерсом Хейльсбергом и продавалась как
Kompass Pascal. Филипп Канн (США) приобрел ее и создал в
1983 г. компанию Borland International на концепции продажи компилятора за минимальную цену (менее 50$). Даже самый первый Турбо-Паскаль имел интегрированные редактор и
компилятор, в нем впервые была реализована функция: когда
обнаруживалась ошибка, загружался редактор и курсор указывал на ошибку.
25
В версиях 2.0 (1984 г.) и 3.0 (1985 г.) снимались некоторые ограничения на размер создаваемой программы, стали
поддерживаться команды для арифметического сопроцессора
и двоично-кодированной десятичной арифметики. Для IBMсовместимых компьютеров поставлялся набор графических
функций, с чего и началась ориентация на эту платформу, то
есть использование с операционной системой DOS.
В версии 4.0 (1987 г.) был существенно улучшен компилятор: благодаря «интеллектуальной» компоновке размер кода
программы существенно уменьшился, увеличилось быстродействие программ. Появилась современная интегрированная
среда разработки (Integrated Development Environment,
IDE), в частности содержащая систему меню и контекстночувствительную систему помощи.
В версии 5.0 (1988 г.) появились два отладчика: встроенный в IDE и мощный автономный. Была обеспечена поддержка
расширенной памяти, то есть стала возможна работа с памятью свыше 1 Мб, а так же улучшена реализация аппарата перекрытий
(оверлеи),
позволяющего
работать
большим
программам в малых по объему областях памяти. В этой версии были улучшены библиотеки графических процедур, и обеспечивалась полная совместимость с графическими адаптерами
класса VGA.
Версия 5.5 (1989 г.) явилась существенным шагом вперед: в ней была реализована концепция объектноориентированного программирования (ООП), то есть за основу стал принят Object Pascal. При этом были добавлены только четыре новых зарезервированных слова, взятых из
объектного Паскаля и Си++. Кроме этого, наряду с внутренними улучшениями и новыми возможностями встроенной справочной системы Help, с инструментальной системой стал
поставляться электронный учебник, содержащий большое количество примеров, которые можно было скопировать прямо в
программу. Это существенно облегчило переход программистов
к объектно-ориентированным методам программирования.
В версии 6.0 (1990 г.) появляется собственная библиотека
объектов – Turbo Vision. Интегрированная среда стала полностью соответствовать стандарту и поддерживать мышь, появилась
возможность
редактировать
несколько
файлов
одновременно. Существенное нововведение – встроенный ассемблер.
В 1992 г. была представлена очередная версия языка –
Турбо-Паскаль 7.0, одновременно являющаяся и последней для
26
DOS. Наряду со всеми улучшениями, унаследованными от
предыдущей версии, появилась возможность выделять определенным цветом различные элементы исходного текста, позволяющая даже неопытным пользователям устранять ошибки на
этапе ввода программы. Кроме этого были расширены как сам
язык программирования, так и библиотека объектов, компилятор стал более эффективным.
Кроме этого в пакет входил Турбо-Паскаль для Windows
(TPW), родоначальник Delphi, сначала (с 1991 г.) выпускавшийся отдельно. Это был первый компилятор с распространенного
языка высокого уровня для Windows, имеющий IDE, базирующуюся на концепциях Windows. Он имел библиотеку Object
Windows (OWL), обеспечивающую объектно-ориентированную
среду, избавляющую программиста от необходимости прямого
обращения к функциям Windows API.
В 1994 г. вышла новая версия языка, настолько сильно
отличающаяся от предыдущих, что фирма Borland решила дать
ей новое имя – Delphi 1.0. Здесь широко используются возможности Графического интерфейса пользователя (GUI): пиктографические меню и библиотеки объектов, перетаскивание
элементов с помощью мыши и т.д. Более того, базовая часть
исходного текста программы генерируется автоматически.
Программирование в этой среде является визуальным, основанным на объектах и управляемое событиями.
Если Delphi 1.0 являлась 16-разрядной инструментальной
системой, предназначенной для работы с операционной системой Windows 3.х, то 1996 г. выходит 32-разрядная версия для
Windows 95 и NT – Delphi 2.0. Различие этих ОС обуславливает
и различие версий Delphi.
Следующие версии Delphi (3, 4, 5, 6, 7) являлись следствием постепенного развития среды разработки – улучшались
существующие компоненты, добавлялись новые возможности,
большое внимание уделялось программированию баз данных и
программ для глобальной сети Internet.
В 2004 г. вышла версия Delphi 8.0 с официальным названием Borland Delphi for the Microsoft .NET Framework, поддерживающая новую технологию разработки программного
обеспечения Microsoft .NET. В этой версии расширение языка
Паскаль (Object Pascal) стали называть Delphi for .NET
Language.
В 2005 и следующем годах выходят соответственно Delphi
2005 и Delphi 2006. Они уже представляют из себя многоязы27
ковую среду объектно-ориентированного программирования,
так как в среде Delphi 2005 присутствует два языка: расширения Object Pascal и С#, в Delphi 2006 к ним добавляется еще
С++.
Язык, оболочка и интегрированная среда
разработки
После того, как программа составлена, ее необходимо
ввести в компьютер. Здесь мы и сталкиваемся с инструментальным пакетом программ Турбо Паскаль. Этот пакет содержит:
1.
2.
3.
4.
5.
6.
7.
транслятор с языка Паскаль;
редактор текста;
инструментальную оболочку;
отладчик;
обширные библиотеки программ под DOS и Windows;
драйвера видеоадаптеров и памяти;
справочную систему
и многое другое, в частности примеры программ с использованием методов объектно-ориентированного программирования.
Для связи основных из этих программ в единое целое, создания удобного и наглядного интерфейса предназначена интегрированная инструментальная оболочка. Весь же набор
программ называется системой программирования, инструментальной системой, средой объектно-ориентированного программирования, или интегрированной средой разработки
(Integrated Development Environment, IDE).
Язык Паскаль, используемый в Турбо-среде, является
расширением стандартного языка программирования
Паскаль, а с версии 5.5 – объектного Паскаля (Object Pascal).
Поэтому все программы, написанные на языке Паскаль, будут
выполняться и в среде Турбо Паскаль, хотя обратное утверждение несправедливо. То есть соблюдается полная преемственность по принципу «сверху-вниз».
Для того чтобы отличить базовый язык программирования Паскаль от его расширения, последний называют расширением Паскаля в Турбо-среде или просто по названию
среды разработки Турбо-Паскалем, а в дальнейшем Delphi.
28
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
Перечислите этапы создания и эксплуатации программного обеспечения.
Какие документы необходимы для создания алгоритма?
Что включает в себя внешняя спецификация программы?
Что такое тестирование?
Что такое отладка?
Что включает в себя сопровождение программного продукта?
Что такое жизненный цикл программы?
Что такое алгоритм?
Какие возможности предоставляет алгоритм?
Какие области исследований связаны с алгоритмами?
Перечислите основные свойства алгоритма.
Поясните свойство конечности алгоритма.
Поясните свойство определенности алгоритма.
Поясните свойство алгоритма «Ввод».
Поясните свойство алгоритма «Вывод».
Поясните свойство эффективности алгоритма.
Перечислите дополнительные свойства алгоритма.
Перечислите основные формы записи алгоритмов.
Что такое словесное описание алгоритма?
Что такое графическое описание алгоритма?
Из каких групп символов состоит блок-схема программы?
Как изображается символ «процесс» и какова его функция?
Как изображается символ «линия потока» и какова его
функция?
Как изображается символ «решение» и какова его функция?
Как изображается символ «модификация» и какова его
функция?
Как изображается символ «граница цикла» и какова его
функция?
Как изображается символ «ввод-вывод» и какова его
функция?
Как изображается символ «ручной ввод» и какова его
функция?
Как изображается символ «документ» и какова его функция?
29
30. Как изображается символ «предопределенный процесс» и
какова его функция?
31. Как изображается символ «пуск-останов» и какова его
функция?
32. Как изображается символ «соединитель» и какова его
функция?
33. Как изображается символ «комментарий» и какова его
функция?
34. Каковы правила выполнения записей внутри символов?
35. Поясните понятие «язык» в контексте информации.
36. Перечислите характеристики языков программирования.
37. Перечислите типы языков программирования, связанные
с их историей и эволюцией.
38. Какие языки программирования используются наиболее
широко на ПК?
39. Кто автор языка программирования Паскаль?
40. Как называется система программирования, использующая язык Паскаль, под ОС DOS?
41. Как называется система программирования, использующая язык Паскаль, под ОС Windows?
42. Что такое «интегрированная среда разработки»?
30
Глава 2. Общие сведения о языке Паскаль
§2.1. Алфавит и служебные слова
Языки программирования, так же как и разговорные,
имеют свой алфавит. Алфавитом языка программирования
называют весь набор символов, с помощью которых составляется программа.
Алфавит языка Паскаль (Турбо-Паскаль) составляют:
1.
2.
3.
4.
Латинские строчные и прописные буквы, которые не
различаются. К буквам относится и символ подчеркивания.
Арабские цифры от 0 до 9.
Символы-разделители: пробел, переход на новую строку, табуляция. Используются для большей наглядности
текста программы.
Специальные символы:
+-*/=.,‘:;^@#${}[]()<>
Они выполняют определенные функции при построении
различных конструкций языка.
5.
Составные символы, то есть группа символов, воспринимаемых компилятором как единое целое:
<= => := (* *) (. .) ..
6.
Служебные слова. Так как языки программирования являются формальными, а не разговорными, то служебные
слова (ключевые слова английского языка) не подлежат
изменению или сокращению – это просто иная форма записи символов. Например: Program, Begin, For и т.д. Если начальная версия языка Паскаль насчитывала 35
служебных слов, то в Турбо-Паскале 7.0 их уже 59.
Описание общих конструкций языка
Для описания общих структур в программировании используются специальные символы – метасимволы, которые в
структурах не указываются, а предназначены лишь для пояснений. К ним относятся:
[a] – квадратные скобки указывают на необязательность элемента а;
31
[a1 |a2 |...an ] – свидетельствует о том, что может присутствовать только 1 элемент ai из указанного списка, или ни
одного;
a1 |a2 |...an или
a1
a2
...
– указывает на обязательность присут-
an
ствия одного и только одного элемента ai.
<a> – на место а должно быть подставлено конкретное значение;
... – многоточие свидетельствует о том, что предыдущий тип
элемента можно неоднократно повторять.
Например, запись [(<список параметров>)] эквивалентна
[(<параметр> [, ...])]. Конкретное же значение может либо вообще отсутствовать, либо выглядеть, например, так:
(А,В,С)
Или общая форма записи оператора цикла с параметром:
For <параметр цикла> := <начальное значение>
| DownTo <конечное значение> Do <внутренний
оператор – тело цикла>
To
А его конкретная реализация в программе:
For i:=1 To 100 Do s:=s+(i+1)/(i*i+2);
§2.2. Структура программы
Алгоритмический язык Паскаль является операторным
языком, то есть отдельными его предложениями являются операторы, с помощью которых задаются действия. Программа же
на этом языке представляет собой формальную запись некоторого алгоритма.
В соответствии с этим принципом программа на любом
алгоритмическом языке состоит из двух частей: описания последовательности действий, которые необходимо выполнить, и
описания данных, с которыми оперируют действия. Действия
представляются операторами языка, данные вводятся посредством описаний и определений. Описания данных по тексту
должны предшествовать описанию действий.
В оригинале синтаксически программа разделена на 2
части:
<заголовок программы>;
<блок>.
32
Наличие точки в конце является обязательным и служит
окончанием признака всего текста программы.
Заголовок начинается со служебного слова Program, далее
следует имя программы и список параметров. Но в ТурбоПаскале заголовок может отсутствовать, то есть программа
может состоять только из одного блока, хотя по правилам хорошего стиля программирования заголовок должен присутствовать.
Совокупность описаний и определений и следующая за
ним последовательность операторов называется блоком.
Объекты, вводимые посредством описаний и определений, имеют различную природу и делятся на 5 классов, описываемых каждый в своем разделе:
1.
2.
3.
4.
5.
раздел
раздел
раздел
раздел
раздел
описания
описания
описания
описания
описания
меток;
констант;
типов;
переменных;
процедур и функций.
В Турбо-Паскале соблюдение такой последовательности не
обязательно, и добавлен еще один раздел подключаемых модулей. За разделами описаний следует раздел операторов, заключенный между словами Begin и End.
При составлении программы следует придерживаться
определенных правил, основные из которых следующие.
1.
2.
Все описания и операторы отделяются друг от друга точкой с запятой. В принципе всю программу можно записать в одну строку, но правила хорошего стиля
программирования требуют записи каждого оператора на
отдельной строке для удобства чтения текста программы.
Для пояснения отдельных фрагментов программы используются комментарии. Для их записи могут использоваться
любые символы клавиатуры, они могут стоять в любом
месте, где может ставиться пробел, и для них в ТурбоПаскале введено 2 типа ограничителей:
{<комментарий>}
(*<комментарий>*)
3.
Для обозначения переменных, констант, заголовков и т.д.
используются идентификаторы или имена. Они могут состоять из прописных и строчных латинских букв, счита33
4.
5.
ющихся эквивалентными (символ подчеркивания относится к буквам и служит для разделения слов) и цифр,
причем начинаться имя должно с буквы. В Турбо-Паскале
длина имени не ограничена, но распознаются они по первым 63 символам.
Перед любым оператором может быть поставлена метка
(допускается и несколько), за которой следует двоеточие.
В стандартном Паскале меткой является целое число от 1
до 9999. В Турбо-Паскале меткой может быть как число
(для обеспечения совместимости), так и имя.
Правилом хорошего стиля программирования является
выделение различных конструкций программы: внутренних, составных операторов и др. отступами различной
ширины от начала строки.
В качестве примера приведем программу для вычисления
суммы
n

1
2
i 0 i  3i  5
при значении n = 20.
Program Summa;
Const n=20;
Var S:real;
i:integer;
Begin
S:=0;
For i:=0 to n do
S:=S+1/(i*i+3*i+5);
Writeln('Сумма=',S:6:4)
End.
§2.3. Разделы описаний
Описание меток
Все метки, которые используются в программе, должны
быть описаны в этом разделе. Раздел начинается со служебного
слова Label, за которым следуют через запятую все используемые метки:
Label <метка1>[,<метка2>...];
Хотя метки и могут стоять перед любым оператором, причем даже несколько сразу, но используются они в единственном операторе безусловного перехода GoTo, который не
рекомендуется к использованию, поэтому на практике данный
раздел встречается редко. Например:
34
Label
M1,Metka,L38;
Определение констант
Все значения, которые используются в программах на
любых языках программирования, делятся на 2 больших класса: константы и переменные. При трансляции программы
константы располагаются непосредственно в области кода программы, переменные же хранятся отдельно в сегменте данных.
Так как при выполнении программы она не должна модифицировать сама себя (свойство реентерабельности), то константы изменять нельзя. Переменные же могут изменяться в
процессе выполнения программы сколько угодно раз. Так, при
трансляции оператора и дальнейшем его выполнении
А:=В+С
используются 3 ячейки памяти, а оператора
А:=8+9
только одна. Переменные обозначаются именами, а константы
числами, строками и т.д.
Паскаль допускает введение в программу объектов,
внешне похожих на переменные, но которые на самом деле являются константами, то есть не подлежат изменению при выполнении программы. Константе можно присваивать свое имя,
которое просто является синонимом фиксированного числа.
Использование в программе имен констант вместо записи конкретных значений является правилом хорошего стиля программирования, так как делает программу более наглядной и
способствует лучшему ее пониманию без снижения эффективности. Кроме того, если конкретные значения обозначены именами, например, границы массивов, показатели точности
вычислений, то при необходимости их легко изменить, не выискивая по всему тексту программы.
Описание констант начинается со служебного слова
Const, за которым идет последовательность описаний констант:
Const <имя>=<значение>;
[<имя>=<значение>;...]
Здесь тип значения не указывается, так как считается,
что значение является единственно возможным с однозначно
определяемым типом по значению константы. В ТурбоПаскале допускается использовать в качестве значения константное выражение, строящееся по тем же правилам, что и
35
обычное. Его операндами могут быть константы, ранее описанные имена констант и некоторые стандартные функции.
Например:
Const
Max = 100;
{константа целого типа}
Min = 0;
Center =(Max-Min) div 2;
{выражение и
результат тоже целого типа}
m_e = 9.10955854e-31;
{масса покоя
электрона – число вещественное}
LiteraA ='A';
{символьная константа}
В Турбо-Паскале существует понятие типизированных
констант. Но на самом деле они являются переменными,
которым в разделе описаний присваиваются начальные значения. Типизированные константы, как и обычные переменные,
можно изменять в процессе вычислений, то есть они – переменные, но с начальными значениями. То есть их можно было
бы описать в разделе описания переменных, а затем первыми
операторами присвоить им начальное значение. Но в некоторых случаях, например при задании начальных значений массивам, их использование удобно. Для типизированных
констант уже необходимо задавать конкретный тип, например:
Const
max:integer = 9999;
MyArray: array[1..3] of string =
('Red','Blue','Green');
Определение типов
При составлении программ могут использоваться данные
самых разнообразных типов, включая и достаточно сложные.
От типа переменной зависит выделяемая для нее память и интерпретация данных в выделенной памяти.
В простейшем случае указывается стандартный тип при
описании переменной, например, как в примере вычисления
суммы. Однако в Паскале допускается определение своего типа
отдельно, например определение комплексного типа:
Type Complex = Record
Re,Im:real;
end;
Начинается этот раздел с служебного слова Type и широко используется при работе с динамическими переменными и
при объектном программировании.
36
Описание переменных
Перед началом выполнения программы под все переменные (за исключением динамических) должно быть выделено
место в памяти ЭВМ. То есть все переменные, используемые в
программе, должны быть описаны и транслятор «должен знать»
о них. В некоторых языках программирования используются
правила умолчания, так в Фортране тип и размер переменной
определяется по первой букве имени, и часто этот раздел отсутствует: при встрече новой переменной по тексту программы
для нее автоматически определяется размер и выделяется место (она заносится в таблицу). Но язык Паскаль разрабатывался
как «надежный» язык программирования, с максимально возможным поиском формальных ошибок. Поэтому в нем все переменные, участвующие в программе, должны быть описаны в
данном разделе.
Общий вид описания переменных выглядит следующим
образом:
Var <имя1>[,<имя2>...] :<тип>; ...
Например:
Var
x:real;
i,j,k:integer;
Так как по свойству массовости алгоритма переменные
присутствуют в каждой программе, то в реальных программах
всегда в наличии и этот раздел.
Раздел описания процедур и функций будет рассмотрен
далее.
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
Какие группы символов входят в алфавит языка Паскаль?
Что такое служебные слова?
Приведите примеры метасимволов для описания общих
конструкций языка программирования.
Из каких элементов состоит программа на языке Паскаль?
Что такое в структуре программы блок?
Какие бывают разделы описаний?
Как отделяются операторы друг от друга?
Как записываются комментарии?
Для чего используются идентификаторы (имена)?
37
10. Какие есть правила образования идентификаторов
(имен)?
11. Что такое метка и для чего они используются?
12. Каким образом рекомендуется располагать конструкции
программы для ее большей наглядности?
13. Каким образом описываются метки?
14. Какие различия между константами и переменными?
15. Как описываются константы?
16. Как определяется тип описываемых констант?
17. Что такое типизированные константы?
18. Как описываются типы?
19. Как описываются переменные?
20. Какой раздел описаний чаще всего присутствует в простых программах?
38
Глава 3. Простые типы данных
Иерархия типов
В Паскале любая переменная характеризуется своим типом. Под типом здесь понимается множество значений, которые может принимать переменная, и множеством операций,
допустимых над данной переменной.
Паскаль является типизированным, или статическим
языком, то есть тип переменной определяется при ее описании и не может быть изменен в процессе выполнения программы. Транслятором контролируется допустимость операций
с величинами данного типа, чем повышается надежность программы.
Паскаль имеет развитую систему типов. На основе стандартных типов можно конструировать данные произвольной
структуры и сложности, адекватно отражающие информационную природу задачи, используя раздел описания типов.
Турбо-Паскаль наследует систему типов стандартного
языка, но несколько расширен, что представлено на схеме
рис. 3.1.
Базовыми являются стандартные типы, на основе которых по определенным правилам образуются все остальные. Новые
типы,
появившиеся
в
Турбо-Паскале,
отмечены
звездочкой, в языке Delphi введены и еще дополнительные типы. Каждому типу соответствует свое имя, указываемое при
описании переменных или введении новых типов.
39
Типы языка Паскаль
Ссылочные типы
Простые типы
Процедурные
типы*
Составные или
структурированные типы
Объектные типы*
Стандартные
простые
Определяемые
пользователем
Массивы
Записи
Множества
Целые
Перечисляемый
Файлы
Вещественные
Интервальный
или диапазон
Строки*
Логический
Литерный или
символьный
Рис. 3.1. Иерархия стандартных типов языка Паскаль.
§3.1. Целые типы
В Паскале есть только один целый тип – Integer. В ТурбоПаскале это группа из пяти типов, обозначающих множества
целых чисел в разных диапазонах. Их характеристики приведены в табл. 3.1.
Таблица 3.1. Целые типы данных
Обозначение
целого типа
Shortint
Integer
Longint
Byte
Word
Диапазон значений
-128...127
-32768...32767
-2 147 483 648...2 147 483 647
0...255
0...65535
Размер памяти
в байтах
1
2
4
1
2
Целая константа – это последовательность цифр со знаком или без знака. Константы записываются в десятичной системе счисления, но в Турбо-Паскале может использоваться и
шестнадцатеричная. В этом случае перед числом ставится знак
$ и разрешается дополнительно к цифрам использовать буквы
40
от А до F ($f5). Обычно шестнадцатеричная система счисления
используется с целыми типа Byte и Word. Пример описания типа:
Const
Var
X:Word=$ff25;
i:byte;
l,m:integer;
Над целыми значениями допустимы следующие арифметические операции:
+
*
/
сложение,
вычитание,
умножение,
деление.
Если первые три операции дают целый точный результат,
то последняя – вещественный, который может быть приближенным, например 1/3=0.3333333. Для точного выполнения
деления используются операции целочисленной арифметики:
div – деление нацело;
mod – остаток от целочисленного деления. Например:
1 div 3 = 0
1 mod 3 = 1
Кроме этого, над целыми числами в Турбо-Паскале
допускается применять логические операции, обычно над
данными типа Byte и Word. Так же над всеми простыми
типами данных, включая целые, допустимо использование
операций отношения (сравнения). Эти два класса операций
будут рассмотрены с логическим типом данных.
§3.2. Вещественные типы
В Паскале есть только один вещественный тип – Real.
В Турбо-Паскале это группа из пяти типов, обозначающих
множества вещественных чисел в разных диапазонах и с различной точностью, ориентированных на представление информации в конкретных процессорах – фирмы Intel. Их
характеристики приведены в табл. 3.2.
Таблица 3.2. Вещественные типы данных
Обозначение типа
Real
Single
Double
Диапазон значений
2.9*10-39 ... 1.7*10+38
1.5*10-45 ... 3.4*10+38
5.0*10-324 ... 1.7*10+308
41
Число десятичных цифр мантиссы
11-12
7-8
15-16
Размер
памяти в
байтах
6
4
8
Extended
Comp
3.4*10-4932 ... 1.1*10+4932 19-20
-9.2*10+18 ...9.2*10+18
19-20
10
8
Использование последних четырех типов возможно только
с использованием математического сопроцессора (должна быть
включена соответствующая опция транслятора). Тип Comp хотя и считается вещественным, но на самом деле содержит целые числа из весьма большого диапазона.
Вещественные константы имеют две формы представления: с фиксированной и плавающей точкой. В первом случае
число представляется как целая и дробная часть:
[<знак>]<целая часть>.<дробная часть>
Наличие точки является признаком вещественного типа
числа.
В представлении с плавающей точкой, или экспоненциальном, число условно разбивается на две части: мантиссу и
порядок (иначе называемый характеристикой), поэтому в общем виде число выглядит как:
[<знак мантиссы>]<целая часть мантиссы> [.<дробная
часть мантиссы>] E [<знак порядка>]<порядок>
Здесь буква Е является разделителем, отделяющим мантиссу от порядка или характеристики. Числа представляются
только в десятичной системе счисления, например
1234.5610 = 1.23456E3 ( 1,23456103) = 1234.56E0 ,
то есть положение десятичной точки зависит от значения порядка и может изменяться.
Таким образом, признаком вещественного числа является
наличие точки или разделителя. Так, число «единица» может
быть представлено в трех разных форматах:
1Е0 – вещественное число с плавающей точкой;
1.0 – вещественное число с плавающей точкой;
1 – целое число.
Над вещественными значениями допустимы следующие
операции:
+
*
/
сложение;
вычитание;
умножение;
деление.
42
Все они дают вещественный результат, если хотя бы один
операнд вещественный.
Применение операций отношения дает логический результат.
§3.3. Логический тип
Логический тип основан на правилах Булевой алгебры,
широко используемой в цифровой электронике. Этот тип определяет всего два значения True (истина) и False (ложь), которые и являются константами. Описывается этот тип словом
Boolean, определенным как
Type Boolean = (False,True);
Над значениями логического типа определены следующие
операции:
NOT – «НЕ», логическое отрицание или инверсия, в отличие
от остальных выполняется над одним операндом (унарная
операция);
OR – «ИЛИ», логическое сложение или дизъюнкция;
AND – «И», логическое умножение или конъюнкция;
XOR – «исключающее ИЛИ», сложение по модулю 2 (в стандартном Паскале отсутствует).
Пусть А и В – логические значения, тогда результат операций будет определяться по табл. 3.3:
Таблица 3.3. Логические операции
А
В
not A
A or B
A and B
A xor B
True
True
False
True
True
False
True
False
False
True
False
True
False
True
True
True
False
True
False
False
True
False
False
False
В Паскале, как и в цифровой электронике, True кодируется единицей, а False – нулем (0 – низкое напряжение, 1 – высокое; при использовании обратной логики – наоборот).
Поэтому логические операции разрешены не только над логическими значениями, но и над целыми, представляемыми в
двоичной системе счисления. Например, если А=21 (101012), а
В=9 (10012), и они описаны как байты, то:
not A = 111010102 = 23410
A or B = 000111012 = 2910
43
A and B = 00000001
Обычно их используют над беззнаковыми целыми типа
Byte или Word.
Шесть операций отношения или сравнения:
> < =    ,
применимые ко всем простым типам и многим структурированным, всегда дают логический результат. Более того, и над
логическими значениями допускается выполнение операций
отношения:
True > False  True,
True < False  False.
Возможны даже такие записи:
Var i:boolean;
...
For i:=False to True do
<оператор>;
Здесь оператор выполнится два раза, сначала со значением i = False , затем i = True.
§3.4. Литерный тип
В качестве данных в Паскале могут выступать отдельные
символы или литеры. Этот тип данных описывается словом
Char и занимает 1 байт. Значениями здесь являются 256 символов кодировочной таблицы ASCII (American Standard Code
for Information Interchange – Стандартный американский код
обмена информацией), используемой в ОС DOS.
При записи констант, если символьное значение имеет
графическое представление, то оно изображается символьным
знаком, заключенным в апострофы, например:
'*'
'A'
'!'
Если символ нельзя отобразить в тексте программы (служебные и неотображаемые символы), то можно воспользоваться эквивалентной формой записи
#<целочисленный код символа>.
Например:
#$7F – Delete, стереть последний символ. Символом DEL,
состоящим в двоичном коде из всех единиц, можно было забить любой символ. Устройства и программы игнорировали
44
DEL так же, как NUL. Код этого символа происходит из первых
текстовых процессоров с памятью на перфоленте: в них удаление символа происходило забиванием его кода дырочками
(обозначавшими логические единицы).
#7 – Bell, звонок, звуковой сигнал. Имеет отношение не к
монитору, а ко встроенному динамику.
Если используются специальные символы, называемые
управляющими кодами (то есть с использованием префиксной
клавиши Ctrl), то можно использовать запись, например вида
^C (символ с кодом 3: ext – конец текста). Все коды с номерами
0-31 являются управляющими, и их можно представить в таком виде. Например, BEL можно записать как ^G.
Над значениями литерного типа можно выполнять только
операции отношения.
§3.5. Перечисляемый тип
Часто в программах число используется не в качестве
арифметической величины, а как указатель некоторого элемента множества. В Паскале предусмотрена возможность
называть элементы множества своими именами, то есть можно
определить новый тип путем явного перечисления всех его
возможных значений, причем каждое такое значение будет
определяться только именем. Считается, что значения перечисляемого типа указаны в порядке возрастания, начиная с
нулевого, поэтому над этими элементами можно выполнять
операции отношения. Причем наиболее часто используются
операции = и .
При описании этого типа используется список, заключенный в круглые скобки, а его значения (константы) являются
именами:
(<имя1>[,<имя2>...])
Например:
Type
Color = (Red,Yellow,Green);
{Светофор}
Day = (Mon,Tue,Wed,Thu,Fri,Sat,Sun);
Var c:Color;
d:day;
...
If d>Fri Then Writeln ('Выходной день');
Для любого перечисляемого типа Т, определенного записью
45
T = (w0,w1,...,wn) ,
справедливы свойства:
1. Различимости
wi  wj , если i  j.
2. Упорядоченности
wi < wj , если i < j.
Причем значениями типа Т могут быть только w0, w1, ...,
wn. Более того, эти имена должны быть уникальны, то есть не
должны повторяться ни в других типах, ни при описании переменных.
§3.6. Диапазоны (интервальный тип)
Часто при описании переменных известно, что они будут
принимать ограниченное множество значений, например часы, дни недели и др. Для облегчения восприятия программы и
введения дополнительного контроля используются диапазоны,
иначе интервальный тип.
Диапазоны можно задавать для любого простого, но дискретного типа. Дискретными или счетными типами являются такие, для которых можно указать два соседних
значения, между которыми нельзя добавить еще одно. Например, для целого 1 и 2, для перечисляемого Sat и Sun и т. д.
Единственным исключением здесь являются вещественные
числа. В дальнейшем будет показано, что вещественные числа
не могут использоваться в некоторых операторах и конструкциях языка Паскаль, в том числе и в диапазонах.
Для указания диапазона используются 2 точки:
Type C = '0'..'9';
Var Min,Sec:0..59;
Hour:0..23;
Так как диапазоны есть подмножества других типов, то
для них определены и соответствующие операции.
Дополнительные типы в Delphi
В Delphi определен новый тип данных – денежный, описываемый словом Currency. Он аналогичен вещественному
формату Comp, не имеющему дробной части. Здесь же добавлено 4 значащих цифры после запятой, то есть точность вычислений составляет 0.0001.
К логическому типу добавлено еще 3 разновидности для
совместимости со стандартами Windows: ByteBool, WordBool,
46
LongBool, с занимаемой памятью соответственно 1, 2 и 4 байта.
К символьному типу добавлены типы AnsiChar и
WideChar. Первый полностью аналогичен Char, хотя в Windows
и используется кодировка ANSI вместо АSСII. Второй же используется для хранения символов в кодировке UNICODE с
размером таблицы 65 536 символов, поэтому под каждый символ выделяется 2 байта.
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
Что такое тип переменной?
Что означает выражение «типизированный язык программирования»?
Перечислите служебные слова, которыми в Турбо-Паскале
обозначаются целые типы со знаком, каков их размер?
Каков диапазон короткого целого со знаком?
Каков приблизительно диапазон стандартного целого со
знаком?
Каков приблизительно диапазон длинного целого со знаком?
Перечислите служебные слова, которыми в Турбо-Паскале
обозначаются целые типы без знака, каков их размер?
Каков диапазон беззнакового однобайтного целого?
Каков приблизительно диапазон беззнакового двухбайтного целого?
Что такое целая константа?
Какие системы счисления используются и как обозначаются в Турбо-Паскале?
Какие операции используются над целыми значениями?
Какие две операции предназначены только для целого
типа данных?
Кроме арифметических, какие еще группы операций
можно использовать над целыми данными?
Какова точность представления (в количестве цифр мантиссы) и занимаемый объем памяти для вещественных
чисел типа Real?
Какова точность представления (в количестве цифр мантиссы) и занимаемый объем памяти для вещественных
чисел типа Single?
47
17. Какова точность представления (в количестве цифр мантиссы) и занимаемый объем памяти для вещественных
чисел типа Double?
18. Какова точность представления (в количестве цифр мантиссы) и занимаемый объем памяти для вещественных
чисел типа Extended?
19. Какова точность представления (в количестве цифр мантиссы) и занимаемый объем памяти для вещественных
чисел типа Comp?
20. Как представляются константы с фиксированной точкой?
21. Как представляются константы с плавающей точкой?
22. Запишите три формы представления числа «двойка».
23. Какие операции используются над вещественными значениями?
24. Какое служебное слово используется для описания логических переменных?
25. Как записываются логические константы?
26. Перечислите четыре логические операции, используемые
в Турбо-Паскале.
27. Как выполняются логические операции над целыми данными?
28. Перечислите операции отношения (сравнения).
29. Какой тип результата дают операции отношения?
30. Какое служебное слово используется для описания литерных (символьных) переменных?
31. Какая кодировочная таблица используется в ТурбоПаскале?
32. Как обычно записываются символьные константы, отображаемые на экране монитора?
33. Как записываются любые символьные константы?
34. Как записываются управляющие коды в виде литерных
констант?
35. Какие операции можно выполнять над литерными данными?
36. В каких целях используется перечисляемый тип?
37. Как описываются значения перечисляемого типа?
38. Что такое свойство различимости для перечисляемого типа?
39. Что такое свойство упорядоченности для перечисляемого
типа?
40. В каких целях используются диапазоны?
48
41. Что такое дискретный тип?
42. Какой простой тип нельзя использовать как базовый в
диапазонах?
43. Какие операции допускается использовать для интервальных типов?
49
Глава 4. Операторы языка Паскаль
Выражения
В операторах широко используются выражения.
Выражение – это совокупность переменных, констант и
функций, соединенных знаками операций.
Так как операций в выражении может быть несколько, то
все эти операции разбиты по приоритетам (табл. 4.1), причем
операции, имеющие более высокий приоритет, выполняются в
первую очередь.
Таблица 4.1. Распределение операций по приоритетам
Приоритет
1 (высший)
2
3
4
5 (низший)
Операция
( ) – выражение в скобках
Унарные операции:
NOT – логическая инверсия,
- «минус» – смена знака,
@ – взятие адреса.
* – арифметическое умножение,
/ – арифметическое деление,
DIV – целочисленное деление,
MOD – остаток от целочисленного деления,
AND – логическое умножение.
+ – арифметическое сложение,
- – арифметическое вычитание,
OR – логическое сложение,
XOR – логическое сложение по модулю 2.
IN – проверка принадлежности множеству.
Операции отношения:
> – больше,
< – меньше,
= – равно,
<> – не равно,
>= – больше или равно,
<= – меньше или равно.
То есть, если необходимо, например, записать выражение
ab
, то запись вида a+b/c+d будет неправильна. С помощью
cd
скобок необходимо расставить приоритеты выполнения операций: (a+b)/(c+d).
50
При вычислении выражений часто приходится использовать алгебраические, тригонометрические и другие функции.
Язык Паскаль содержит относительно небольшое число стандартных функций, делящихся по типам используемых операндов и получаемых результатов, а так же функции
преобразования.
Следующие математические функции могут работать как
с целым, так и с вещественным аргументом, тип получаемого
результата соответствует типу аргумента:
Abs(x) – модуль числа х;
Sqr(x) – квадрат.
Функции с вещественным результатом:
Sin(x) – синус;
Cos(x) – косинус;
Arctan(x) – арктангенс;
Exp(x) – экспонента;
Ln(x) – натуральный логарифм;
Sqrt(x) – корень квадратный;
Frac(x) – дробная часть вещественного числа;
Int(x) – целая часть вещественного числа.
Функции с целым результатом:
Random(x) – случайное число от 0 до х;
Trunc(x) – округление с отбрасыванием дробной части;
Round(x) – округление по правилам арифметики.
Odd(x) с целым аргументом дает логический результат:
True, если х нечетно.
Для работы с символьными значениями используются
функции:
Chr(x) – возвращает символ, соответствующий ASCII-коду
числа х;
Ord(x) – возвращает число, соответствующее символу х в
ASCII-таблице;
UpCase(x) – преобразует латинские строчные буквы в
прописные.
Функции для дискретных типов:
Ord(x) – порядковый номер числа х;
Pred(x) – предыдущий элемент;
Succ(x) – следующий элемент.
51
§4.1. Оператор присваивания
Этот оператор является одним из простейших, и наиболее
используемым оператором языка. Он предназначен для определения нового значения переменной или значения, возвращаемого функцией. Общий вид оператора:
<имя> := <выражение>
Частным случаем выражения может быть константа или
переменная.
Тип имени и выражения должны совпадать. Допускается
только одно исключение: выражение целого типа, а переменная – вещественного.
При выполнении оператора вычисляется выражение, и
полученный результат записывается в ячейку памяти, выделенную под переменную. Присваивание допускается для всех
простых типов, для структурированных типов присваивание
выполняется поэлементно. Нельзя использовать присваивание
для файловых типов.
§4.2. Ввод-вывод данных
Для ввода информации с клавиатуры и вывода ее на
экран (стандартные устройства ввода-вывода) используются 4
оператора обращения к процедурам. Для ввода используются:
Read (<список параметров ввода>);
ReadLn (<список параметров ввода>);
В списке параметров указываются вводимые переменные. Во втором случае после ввода указанного количества параметров происходит переход на новую строку. Но, так как
ввод с клавиатуры всегда должен заканчиваться переходом на
новую строку, то именно для устройства ввода – клавиатуры, –
эти операторы можно считать равнозначными. При вводе числовых значений их необходимо разделять хотя бы одним
пробелом или переходом на новую строку.
Например, если в программе встретится оператор
Read (x,y,z);
то выполнение программы приостановится (будет индицироваться экран данных, стандартно черного цвета), пока не будет
введена строка
0.28
32.5
1.0 <Enter>
Для вывода так же используются два оператора
52
Write (<список параметров вывода>);
WriteLn (<список параметров вывода>);
В списке параметров указываются выражения, в частности переменные и константы, например строковые:
WriteLn ('Сумма=',S,' Произведение=',a*b);
Аналогично с вводом, после выполнения второго оператора осуществляется переход на новую строку. Оператор Write
обычно используется при необходимости формирования одной
строки с помощью нескольких операторов.
При выводе вещественные числа имеют 15 цифр мантиссы и 4 цифры порядка, а целые занимают место, равное количеству значащих цифр. Так, при выполнении операторов
Read (i,j,k,x);
Writeln (i,j,k);
Writeln (x);
на экране данных появится информация:
35 128 15 0.75
3512815
7.50000000000000Е-0001
Первая строка – это введенные с клавиатуры данные. Гораздо нагляднее использовать форматированный вывод.
Здесь параметр выглядит следующим образом:
<выражение>:<количество выделяемых позиций>
При этом данное прижимается к правому краю. Вещественные числа в таком формате все равно будут выводится в
экспоненциальной форме (с плавающей точкой). Только для
вещественных чисел, чтобы использовать формат с фиксированной точкой, используется следующий общий вид:
< выражение>:<общее количество выделяемых
позиций>:<количество позиций дробной части>
Внимание! Не путать общее количество выделяемых позиций с количеством позиций целой части вещественного числа!
Аналогично предыдущему примеру:
Read (i,j,k,x);
Writeln (i:4,j:4,k:4);
Writeln (x:7:3);
на экран будет выведена информация в более удобном для
восприятия виде:
53
35
128 15 0.75
35 128 15
0.750
Необходимо обратить внимание: первая цифра при задании формата числа является общим количеством выделяемых позиций, в которых необходимо учитывать место под знак
числа и десятичную точку.
Формат с двумя числами предназначен только для вещественных чисел в форме с фиксированной точкой.
§4.3. Составной оператор
Все операторы Паскаля условно можно разбить на простые и структурные. Структурные включают в свой состав другие операторы. В частности, в условном операторе, операторе
выбора и двух операторах цикла может быть записан только
один внутренний оператор. Это ограничение можно обойти,
если использовать операторные скобки или составной оператор. Он имеет следующий вид:
Begin
<оператор1>;
<оператор2>;
...
<операторn>
end
Пример: вся программа состоит из одного составного
оператора.
§4.4. Пустой оператор
Пустой оператор никак не обозначается и не вызывает
действий – это просто лишняя точка с запятой в программе.
Предназначен он в качестве носителя метки, если ее необходимо поставить перед словом END в конце составного оператора или программы. Поэтому любой оператор может
заканчиваться одной или несколькими точками с запятой.
§4.5. Безусловный оператор перехода
Это единственный оператор, в котором используется метка. Он имеет вид:
Go To <метка>;
После выполнения этого оператора будет осуществлен переход на оператор, перед которым стоит указанная метка.
Использование безусловных передач управления в программе считается теоретически избыточным для многих язы54
ков высокого уровня и способствует созданию малопонятных и
трудно модифицируемых программ, которые вызывают сложности при отладке и сопровождении. Если же программист
считает, что без условных переходов не обойтись, то необходимо выполнять следующие условия:
1.
2.
Не допускаются переходы внутрь подпрограмм (процедур
и функций) или из них.
Переход внутрь структурного оператора может вызвать
непредсказуемые эффекты, хотя компилятор не выдает
сообщение об ошибке.
Примером на условный и безусловный операторы можно
считать алгоритм вычисления кубического корня, хотя реально
для этих целей удобнее и нагляднее использовать один из операторов цикла без параметра.
Program S;
Label L;
Const Eps = 1E-6; {определим точность как
константу}
Var a,x,y:real;
Begin
WriteLn (‘Введите число для определения
кубического корня’);
Read (a);
x := a;
L: y := x;
{«старим» предыдущее значение}
x := 1/3*(a/sqr(y)+2*y); {вычисляем новое
значение}
If Abs(x-y)>Eps Then
GoTo L; {повторим, если разность старого и
нового значений не достигла заданной точности}
WriteLn (‘Значение корня:’, x:10:6);
End.
§4.6. Условный оператор
При составлении по алгоритму программы на месте блока
«Решение» записывается условный оператор, который может
иметь 2 формы. В общем виде полная форма записывается
как:
If <логическое выражение> Then <оператор1> Else
< оператор2>;
а сокращенная:
If < логическое выражение > Then < оператор1>;
55
В первом случае, если логическое выражение истинно
(True), то выполняется оператор1, а если ложно, то оператор2.
Во втором случае при ложности логического выражения просто
осуществляется переход к следующему оператору.
Наиболее простой пример – вычисление значения кусочно-непрерывной функции:

 х 2 , если x  0
F x   

 x , если x  0
Program Function;
Var
f,x:real;
Begin
Read (x);
If x>=0 Then F := x*x
{первый вариант}
Else F := sqrt (x); {второй вариант}
WriteLn ('Значение функции =',F:8:3);
end.
Заметим, что значение функции используется как имя
переменной. Оно не должно содержать скобок, поэтому здесь
вместо F(x) используется просто F (можно функцию назвать и
по-другому, например Fx).
§4.7. Оператор цикла с параметром FOR
Операторы цикла заставляют выполняться входящие в их
состав внутренние операторы несколько раз. В языке Паскаль
существует три вида операторов цикла.
Если число повторений цикла известно перед его началом, то используют наиболее наглядный, но все же имеющий
ограниченную область применения оператор FOR:
For <параметр цикла> := <начальное значение> To |
DownTo <конечное значение> Do <внутренний
оператор – тело цикла>
Внутренний оператор будет выполняться один раз для
каждого значения параметра цикла из указанного диапазона.
Если используется служебное слово То, то при каждом новом
выполнении берется следующее значение, при DownTo –
предыдущее (используются функции Succ и Pred), то есть тип
параметра цикла должен быть дискретным.
При использовании оператора FOR необходимо соблюдать
следующие правила:
56
1.
2.
3.
Начальное и конечное значение являются выражениями
(константа – вырожденное выражение), а параметр цикла – имя переменной (может быть и структурированной,
например, элемент массива).
Параметр цикла, начальное и конечное значение должны
быть одного и того же дискретного типа (не допускается
использование типа Real) и не должны изменяться
внутри оператора FOR.
Если используется служебное слово То, а начальное значение больше конечного (или меньше в случае DownTo),
то внутренний оператор не выполнится ни разу.
Пример на вычисление суммы с оператором FOR приводился ранее. Если количество повторений заранее известно, но
параметр цикла – вещественный, то в явном виде этот оператор использовать нельзя. Ограничение на дискретный тип
можно обойти, если ввести дополнительную целую переменную. Например, необходимо вычислить таблицу синуса для
значений аргумента от 0.5 до 0.7 с шагом 0.01. Введем дополнительную переменную i, изменяющуюся от 0 до 20 (всего надо
вычислить 21 точку). Если учесть, что внутри цикла надо использовать не только оператор вычисления синуса, но и оператор вывода, то в операторе цикла используем операторные
скобки.
Программа будет выглядеть следующим образом:
Program Tabl;
Var
i:integer;
x:real;
Begin
WriteLn ('
Таблица синуса');
For i:=0 To 20 Do
Begin
x := i/100+0.5;
WriteLn ('x=', x:4:1,'sin(x)=', sin(x):8:5);
end
end.
Если же количество повторений заранее неизвестно, как,
например, при вычислении кубического корня, то используют
другие операторы цикла.
§4.8. Оператор цикла с предусловием WHILE
Общий вид оператора:
WHILE <логическое выражение> DO <оператор>;
57
Этот оператор заменяет в блок-схеме цикла только блок
«Решение», если он стоит в самом начале цикла. Если используется параметр цикла, то он должен явно изменяться в цикле.
Здесь истинность выражения проверяется каждый раз
перед выполнением цикла, и внутренний оператор выполняется, пока выражение истинно. Так как проверка осуществляется в начале, то при начальном ложном логическом выражении
внутренний оператор не выполнится ни разу.
Параметр цикла может отсутствовать в самых разнообразных задачах, часто носящих итерационный характер. Но
иногда количество повторений неизвестно и в циклах с параметром, например вычисление бесконечных сумм. В численных
методах значения вычисляются с конечной точностью, поэтому
и количество вычислений конечно: суммирование заканчивается, когда выражение под знаком суммы будет меньше наперед заданного числа, которое условно можно назвать
точностью вычислений.
В качестве примера составим программу для вычисления
суммы


i 1
3
i 1 i  8
при заданной точности ε = 10-3:
Program Sum2;
Const Eps=1e-3;
Var
i:integer;
S,v:real;
Begin
S:=0;
i:=1;
v:=1;
{любое число больше Eps}
While v>Eps do
Begin
v:=(i+1)/(i*i*i+8);
S:=S+v;
i:=i+1
end;
WriteLn (‘Сумма=’,S:6:3)
end.
Заметим, что вывод суммы выполняется в виде числа с
фиксированной точкой (для большей наглядности) с тремя знаками в дробной части. Если выводить больше значений, то они
недействительные, – числовой мусор; если выводить меньше,
то тогда зачем считать с такой точностью?
58
§4.9. Оператор цикла с постусловием REPEAT
Общий вид оператора:
Repeat
<оператор>;
...
Until <логическое выражение>;
Этот оператор, как и предыдущий, заменяет блок «Решение», но если он стоит не в начале, а в конце цикла. Поэтому
входящие в состав оператора REPEAT внутренние операторы
выполнятся хотя бы один раз. Роль операторных скобок
здесь играют служебные слова, поэтому использовать составной оператор не требуется.
Существенным отличием от оператора While является то,
что внутренние операторы выполняются, пока выражение
ложно.
Примеры вычисления кубического корня (уже рассматривался с использованием метки) и бесконечной суммы:
Program S2;
Label L;
Const Eps = 1E-6;
Var a,x,y:real;
Begin
WriteLn ('Введите число для определения
кубического корня');
Read (a);
x := a;
Repeat
y := x;
x := 1/3*(a/sqr(y)+2*y);
Until Abs(x-y)<Eps;
WriteLn ('Значение корня:', x:10:6);
End.
Program Sum3;
Const Eps=1e-3;
Var
i:integer;
S,v:real;
Begin
S:=0;
i:=1;
Repeat
v:=(i+1)/(i*i*i+8);
S:=S+v;
i:=i+1;
59
Until v<Eps;
WriteLn ('Сумма=',S:6:3)
end.
§4.10. Оператор выбора CASE
Для облегчения восприятия программы не рекомендуется
использовать многократно вложенные друг в друга условные
операторы. Если в зависимости от какого-либо выражения
необходимо производить несколько различных групп операций
(более двух), то используется оператор выбора:
CASE <выражение> OF
k1,k2... : <оператор1>;
...
l1,l2... : <операторn>
[ Else
<операторm> ]
End;
Часть Else является расширением Турбо-Паскаля.
Здесь выражение может быть любого дискретного типа,
то есть вещественный результат не допускается.
ki, ..., li – константы выбора. Они должны быть различными и принимать значения выражения. Можно использовать
одну или несколько констант, а так же диапазоны.
Если после вычисления выражения результат совпадает с
одной из констант выбора, то выполняется соответствующий
оператор. Если же соответствующая константа отсутствует, то
выполняется оператор по ветви Else.
Рассмотрим пример. В промышленности для измерения
высоких температур широко используются термопары из соединений различных сплавов. С них снимают напряжение, являющееся функцией температуры, но значение напряжения
существенно нелинейно и его приходится линеаризировать.
Напряжение преобразуется в цифровой код с помощью АЦП,
то есть входное значение является целой величиной.
У разных термопар характеристики различаются очень
сильно. Рассмотрим термопару, позволяющую измерять температуру до 1500 градусов. Допустим, ее график как принятый с
12-разрядного АЦП код от температуры (212 = 4096 отсчетов)
выглядит в соответствии с рис. 4.1.
60
N
4000
3000
2000
1000
0
250
500
750
1000
1250
1500
t, град
Рис. 4.1. Зависимость кода, принятого с АЦП, от температуры,
измеряемой термопарой
Кривая заменяется отрезками прямых с координатами и
углами наклона, которые определяются заранее. В зависимости
от диапазона кода, принятого с АЦП, используются данные соответствующей прямой.
Хотя обычно данные читаются в цикле из файла, но в
данной программе для простоты осуществляется ввод одной
точки с клавиатуры.
Program Linear;
Var n:word;
t:real;
Begin
WriteLn ('Введите значение кода');
Read(n);
Case n of
0..950: t:=n*0.43;
951..1500: t:=400+n*0.85;
1501..2100: t:=600+n*0.38;
2101..3100: t:=1100+n*0.79;
3101..4095: t:=1250+n*0.53
end;
WriteLn ('Значение температуры =',t:8:2)
end.
61
Последний оператор With для обслуживания записей
рассматривается при описании соответствующей структуры
данных.
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
Поясните понятие «выражение».
Какая операция обладает высшим приоритетом?
Какие операции обладают вторым после высшего приоритетом?
Какие операции обладают третьим после высшего приоритетом?
Какие операции обладают четвертым после высшего приоритетом?
Какие операции обладают низшим приоритетом?
С какой целью часть выражения заключается в круглые
скобки?
Как записываются аргументы у стандартных (и нестандартных) функций?
Каков общий вид оператора присваивания?
Для чего предназначен оператор присваивания?
Какое исключение допускается по совпадению типов
имени и выражения в операторе присваивания?
Каков общий вид операторов ввода?
Чем отличается оператор Read от ReadLn?
Каков общий вид операторов вывода?
Чем отличается оператор Write от WriteLn?
Чем отличается список параметров ввода от списка параметров вывода?
Что происходит с выполнением программы, когда встречается оператор Read или ReadLn?
Что содержит список параметров ввода?
Что содержит список параметров вывода?
Как можно отформатировать вывод для любого типа данных
Как можно отформатировать вывод для вещественного
типа данных
Каков общий вид составного оператора?
Где используется составной оператор?
Как выглядит пустой оператор?
В каких случаях применяется пустой оператор?
Как выглядит безусловный оператор перехода?
62
27. Почему не рекомендуется использовать оператор безусловного перехода?
28. В какие области программы запрещены переходы?
29. Каков общий вид полной формы условного оператора?
30. Как работает условный оператор в полной форме записи?
31. Каков общий вид сокращенной формы условного оператора?
32. Что произойдет, если при использовании сокращенной
формы условного оператора логическое выражение примет ложное значение?
33. В каких случаях используют оператор цикла FOR?
34. Какова общая форма записи оператора цикла FOR?
35. Что является параметром цикла в операторе FOR?
36. Как происходит изменение параметра цикла в операторе
FOR?
37. Что является начальным и конечным значениями параметра цикла в операторе FOR?
38. Могут ли изменятся параметр цикла, начальное или конечное его значения внутри цикла в операторе FOR?
39. Каково минимальное количество выполнений оператора
внутри цикла FOR?
40. Какова общая форма записи оператора цикла WHILE?
41. При каком условии будет выполняться внутренний оператор в операторе цикла WHILE?
42. Что необходимо предпринять, чтобы внутри цикла WHILE
выполнялось несколько операторов?
43. При наличии параметра цикла, как он должен изменяться
в операторе WHILE?
44. Каково минимальное количество выполнений оператора
внутри цикла WHILE?
45. Какова общая форма записи оператора цикла REPEAT?
46. Что необходимо предпринять, чтобы внутри цикла
REPEAT выполнялось несколько операторов?
47. При каком условии будет выполняться внутренний оператор в операторе цикла REPEAT?
48. При наличии параметра цикла, как он должен изменяться
в операторе REPEAT?
49. Каково минимальное количество выполнений оператора
внутри цикла REPEAT?
50. В каком случае используется оператор выбора CASE?
51. Какова общая форма записи оператора выбора CASE?
63
52. Какая часть оператора выбора CASE добавлена в ТурбоПаскале?
53. Что такое «константы выбора» в операторе CASE?
54. Каким образом работает оператор выбора CASE?
55. Каково должно быть соответствие между выражением и
константами выбора в операторе CASE?
64
Глава 5. Процедурное программирование
§5.1. Функции
Язык Паскаль включает понятие подпрограмм-функций и
подпрограмм-процедур.
Представление программы как совокупности или иерархии относительно обособленных фрагментов со строго определенными интерфейсами способствует большей читаемости и
простоте составления и отладки программы. Понятие подпрограммы как обособленной именованной части программы со
своим собственным локальным контекстом имен является в
большинстве языков программирования основным средством
структурирования программ.
Таким образом, подпрограмма – это часть программы,
оформленная в виде отдельной синтаксической конструкции и
снабженная именем.
Стандартные подпрограммы-функции уже приводились
при описании выражений, но пользователь может составлять
подпрограммы и самостоятельно, помещая их в раздел описаний.
Структура программы практически повторяет структуру
всей Паскаль-программы, что подчеркивает регулярный характер языка. Структура подпрограммы-функции следующая.
Function <имя>[(<список формальных параметров>)]:<тип>;
[<описания>;]
<операторы>
End;
Здесь <имя> – имя функции, по которому происходит обращение к ней при вычислении выражения, с ним связан результат вычислений. Имя функции должно встречаться хотя бы
один раз в разделе операторов функции слева в операторе
присваивания, то есть результат функции должен быть определен.
<тип> – тип результата, соответствующего имени функции. Специфика функции заключается в том, что после обращения к ней должен быть определен результат, который
участвует в выражении и связан с именем функции.
<список формальных параметров> может отсутствовать.
Формальным параметрам, в отличие от фактических, не соответствуют никакие значения, – они лишь ставят в соответствие
65
фактические значения и порядок вычислений по подпрограмме, то есть в этом списке должны быть только имена. Каждый
параметр, заданный в заголовке, считается локальным в данной подпрограмме так же, как и переменные, описанные в
блоке этой подпрограммы.
Допускается три способа задания формальных параметров подпрограммы (в заголовке в круглых скобках). Они могут
быть определены следующим образом.
Параметры – значения
Это наиболее распространенный и самый простой способ
передачи параметров, который имеет следующий вид:
<имя1>, <имя2>, …, <имяn>:<имя типа>
Здесь параметр считается локальной переменной в пределах подпрограммы, по нему передается значение данных, то
есть он является входным. Изменение этого значения не приводит к изменениям в вызывающем модуле, поэтому здесь
фактическим параметром является выражение и как частный
случай – константа или переменная. Имя типа должно быть
простым или описанным в разделе TYPE во внешнем модуле,
поэтому следующая запись неверна:
Function Z(A:array[1..20] of real):real;
Пример: функция вычисления суммы квадратов.
Function SumSqr(x,y:real):real;
Begin
SumSqr:=x*x+y*y
End;
Параметры – переменные
Параметры, иначе называемые передаваемыми по ссылке, имеют следующий вид.
Var <имя1>, <имя2>, …, <имяn>:<имя типа>;
По этому параметру передается адрес данных, то есть он
является как входным, так и выходным. Изменение этого
значения вызывает изменение фактического параметра, поэтому фактическим параметром может быть только переменная.
Так как при использовании функций выходным значением является значение самой функции, то этот тип параметров
наиболее часто используется в процедурах.
66
Бестиповые параметры
Кроме двух обычных способов передачи параметров в
подпрограммы при их вызове Турбо-Паскаль допускает третий
способ передачи, при котором тип параметра не фиксируется.
В стандартном Паскале этот тип параметра отсутствует, но там
параметрами могут быть функции и процедуры (здесь наблюдается несовместимость с Турбо-Паскалем). Описание параметра имеет следующий вид.
Var <имя1>, …
Так как тип формального параметра не задан, то, строго
говоря, он не совместим ни с какой другой переменной, то есть
не может входить ни в какие конструкции. Поэтому единственным способом использования таких параметров является
определение их типа в теле подпрограммы. Этого можно добиться, применив к ним операцию приведения типа или описав в подпрограмме локальную переменную определенного
типа с совмещением ее в памяти с нетипизированным параметром (в этом курсе не рассматриваются).
Обращение к функциям пользователя происходит в выражениях, точно так же, как к стандартным функциям: указанием
имени
и
фактических
параметров.
Пример
использования описанной ранее функции вычисления суммы
квадратов:
A := x+SumSqr(5.3,c+d);
§5.2. Процедуры
Описание процедур похоже на описание функций. Более
того, функции есть разновидность процедур с возвращаемым результатом, который используется в выражении.
Функции просто применяют для упрощения составления программ, в машинном языке присутствуют только процедуры.
Описание процедуры имеет вид:
Procedure <имя>[(<список параметров>)];
[<описания>;]
<операторы>
End;
Имя и параметры здесь те же, что и в подпрограммахфункциях. Но есть и отличия процедур от функций:
67
1.
2.
С именем процедуры, в отличие от функции, не связано
никакое значение, поэтому имени в теле процедуры значение не присваивается и, соответственно, не указывается тип возвращаемого результата.
Обращение осуществляется не в выражении, а отдельной
строкой, – оператором обращения к процедуре, – с указанием имени и списка фактических параметров. Например, операторы ввода-вывода имеют полное название
«операторы обращения к процедурам ввода или вывода».
Правда, они составлены с использованием объектноориентированного программирования, поэтому первый
пункт правил использования подпрограмм к ним не относится.
Правила использования подпрограмм
1.
2.
3.
4.
В заголовке подпрограммы и при обращении к ней аргументы должны совпадать по типу, количеству и порядку
следования.
В подпрограммах допускается наличие вложенных подпрограмм, количество вложений не ограничено.
Переменные, являющиеся формальными параметрами, а
так же метки, типы, константы и переменные, описанные
внутри подпрограммы, являются локальными, то есть
область их действия ограничена только данной и вложенными подпрограммами. Если же объекты описаны только
в основной программе или вызывающей подпрограмме,
то они являются глобальными. Из-за возможности ошибочного изменения глобальной переменной в какой-либо
подпрограмме, правилом хорошего стиля является локализация всех объектов, используемых в подпрограмме.
Работа процедуры или функции завершается после выполнения последнего оператора ее тела. Язык ТурбоПаскаль содержит дополнительное средство прерывания
выполнения подпрограммы или программы в целом, это
системная процедура Exit. Она возвращает управление в
точку вызова. Данная процедура является избыточной и
предназначена только для устранения безусловного перехода на конец подпрограммы или программы в целом, поэтому, при наличии нескольких вариантов, лучше
68
обходиться без нее. Так, использование второго варианта
подпрограммы более предпочтительно.
Procedure P(x,y:real; Var Res:real);
Begin
If x<y Then Exit;
Res:=Sqrt(x*x-y*y)
End;
Procedure P(x,y:real; Var Res:real);
Begin
If x>=y Then Res:=Sqrt(x*x-y*y)
End;
Рекурсия
В языке Паскаль допустимо обращение подпрограммы
самой к себе, то есть рекурсивные вычисления.
Решение некоторой задачи рекурсивно, если его можно
выразить в терминах решения более простой версии этой же
задачи. Природа многих математических алгоритмов рекурсивна, поэтому рекурсия достаточно широко применяется в
программировании.
Большинство циклических алгоритмов с известным количеством повторений можно записать с помощью рекурсии,
например, вычисление факториала или суммы:
Program SumRec;
Var S:real;
Procedure Sum(Var S:real;N:integer);
Begin
If N=1 then
S:=1
Else
Begin
Sum(S,N-1);
S:=S+1/N
end
end;
Begin
sum(S,100);
Writeln('Сумма=',S:10:4)
end.
При использовании рекурсивных вычислений необходимо
внимательно следить за параметром выхода из рекурсии, так
как эта конструкция является причиной трудно обнаруживаемых программных ошибок. Рекурсия применяется только при
принципиальном упрощении программы при вычислении,
69
например, кратных интегралов, дифференциальных уравнений
высоких порядков, решении комбинаторных задач.
Директивы
Директивы используются для задания параметров трансляции, а так же при описании программ. Единственной стандартной директивой языка Паскаль является директива
опережающего описания Forward.
Так как имена в Паскале просматриваются последовательно, то при обращении одной программы к другой последняя должна быть описана ранее вызывающей подпрограммы.
Если этого сделать нельзя, например, при перекрестных ссылках, то используется директива Forward.
При этом в начале раздела описывается только заголовок
подпрограммы, а сама подпрограмма записывается далее через несколько блоков, но только с указанием в заголовке его
имени, например:
Procedure Q(x:T); Forward;
Procedure P(y:T);
…
Begin
…
Q(A);
…
end;
Procedure Q;
…
Begin
…
P(B);
…
end;
Хотя по прямому назначению эта директива используется
очень редко, но для больших программ, составляющих сотни
страниц, удобно в самом начале компактно собрать все названия подпрограмм с помощью директивы опережающего описания.
В Турбо-Паскале есть еще несколько директив, относящимся к подпрограммам. Если подпрограмма или группа подпрограмм разработана вне системы Турбо-Паскаль, например,
на языке Ассемблер или Си, а их объектный код находится в
объектном файле, то такую подпрограмму можно подключить с
помощью директивы External, дополнительно задав директиву
компилятора $L с именем OBJ-файла:
70
Procedure SqRoots(A,B,C:word); external;
…
{$L ROOTS.OBJ}
Для обеспечения корректности такого подключения необходимо соблюдать определенные межъязыковые соглашения о
связях.
Другим вариантом создания машинного кода подпрограмм с непосредственным указанием машинных команд является директива Inline. Так же можно составлять программы
с использованием мнемонических или ассемблерных обозначений команд, используя директиву Assembler.
Для подпрограммы допускается указание типа вызова
процедуры: дальний (межсегментный) или ближний (внутри
одного сегмента), задаваемые соответственно словами Far и
Near. Они эквивалентны директивам компилятора {$F+} и
{$F-}.
Подпрограмма может вызываться не только явно в тексте
программы, но и по определенному событию, – прерыванию. В
этом случае используется директива Interrupt, служащая для
определения процедур прерываний. Такие процедуры должны
использовать только дальний вызов.
Процедурные типы
До сих пор процедуры и функции рассматривались как
текстовые фрагменты программы, определяемые именами.
Турбо-Паскаль позволяет вводить переменные специального
вида, значениями которых могут служить подпрограммы. Таким образом, Турбо-Паскаль позволяет интерпретировать процедуры и функции как значения, которые можно присваивать
переменным и передавать в качестве параметров, причем речь
идет именно о последовательности вычислений, а не о значениях, полученных после выполнения подпрограмм.
Общее описание процедурного типа следующее:
Type <имя типа>=Function (<список параметров>):<тип>;
Type <имя типа>=Procedure (<список параметров>);
Механизм процедурных типов предоставляет удобные
возможности при разработке программ, но необходимо соблюдать следующие правила.
1.
Подпрограмма, присваиваемая процедурной переменной,
должна быть оттранслирована в режиме дальнего типа
вызова (см. пример ниже).
71
2.
3.
4.
Подпрограмма не должна быть стандартной процедурой
или функцией. Это ограничение легко обойти, заключив
стандартную подпрограмму в оболочку подпрограммы
пользователя.
Данные подпрограммы не могут быть вложенными в другие подпрограммы.
Такие подпрограммы не могут быть процедурами прерываний.
Пример использования подпрограмм
Поскольку процедурные типы можно использовать в любом контексте, то можно описывать процедуры и функции, параметрами которых являются процедурные переменные. В
качестве примера рассмотрим вычисление суммы двух интегралов, причем интеграл вычисляется с использованием подпрограммы по методу трапеций:
2
e
x
x
2

1


 7 x  9 dx   sin x x 3  3 dx .
0
0
Вычисление производится по формуле
n1

h 
  f a   f b   2  f  xi  ,
2 
i 1

где xi  a  ih , h 
ba
,
n
количество отрезков разбиения площади интегрирования
определяется по заданной точности  = 10-3. Сначала интеграл
представляется в виде одной трапеции, потом двух, потом четырех и т.д., то есть шаг интегрирования в каждом цикле
уменьшается в два раза.
Program SumInt;
{ Демонстрационная программа на использование
функций: вычисление суммы двух интегралов методом
трапеций с заданной точностью }
Const EPS=1E-3;
{ Точность зададим константой }
Type Func=Function(x:real):real; { процедурный тип
}
Var SI:Real;
{ Результат: сумма интегралов }
{ Сначало опишем подпрограммы с использованием
дальнего вызова, которые будут передаваться в
подпрограмму как параметры }
72
{$f+}
Function F1(x:real):real;
Begin
F1:=exp(-x)*(x*x+7*x+9)
End;
Function F2(x:real):real;
Begin
F2:=sin(x)*(x*x*x+3)
End;
{$f-}
{ В функцию передаем границы (вещественные) и
последовательность вычислений – процедурный тип }
Function IntTrap(A,B:Real;F:Func):real;
Var IntAlt,IntNew,
интеграла }
H,S,x,
v:real;
{
Begin
H:=B-A;
v:=F(A)+F(B); {
IntNew:=H/2*v; {
трапеции }
{ старое и новое значение
вспомогательная переменная }
для сокращения вычислений }
Первый интеграл из одной
Repeat
IntAlt:=IntNew; { Старим предыдущее значение }
H:=H/2;
{ Уменьшаем шаг в 2 раза }
S:=0; x:=A;
Repeat
x:=x+H;
S:=S+F(x)
Until x>=B;
IntNew:=H/2*(v+2*S);
Until Abs(IntAlt-IntNew)<EPS; { Проверка,
насколько мала разность интегралов, вычисленных с
разным шагом }
IntTrap:=IntNew;
End;
{ Раздел операторов – непосредственно сама
программа }
Begin
{ Очень важно соблюдать типы и
последовательность параметров при обращении к
функциям}
SI:=IntTrap(0.0,2.0,F1)+IntTrap(0.0,2.0,F2);
Writeln('Сумма интегралов=',SI:7:3)
End.
73
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
Для каких целей применяют подпрограммы?
Что такое подпрограмма?
Какова общая структура подпрограммы-функции?
Что такое формальные параметры, и где они используются?
Зачем в заголовке функции указывать ее тип?
Когда применяются и как описываются параметрызначения?
Когда применяются и как описываются параметрыпеременные?
Какова общая структура подпрограммы-процедуры?
Каковы отличия процедур от функций?
Каково должно быть соответствие формальных и практических параметров (аргументов)?
Что такое вложенные подпрограммы?
Что такое локальные переменные?
Что такое глобальные переменные?
Какая процедура используется для завершения подпрограммы или всей программы?
Что такое «рекурсия»?
Где используется рекурсия?
Какая директива присутствует в стандартном Паскале?
Когда применяется директива external?
Когда применяется директива inline?
Когда применяется директива assembler?
Когда применяется директива interrupt?
Когда применяется процедурный тип?
Как описывается процедурный тип?
В каком режиме выполняется трансляция подпрограмм,
присваиваемых процедурной переменной?
74
Глава 6. Структурированные типы данных
Простые типы определяют различные множества неделимых значений. Структурированные (составные, структурные) типы данных задают множества значений, каждое из
которых является совокупностью значений другого или других
типов. Можно сказать, что составные типы определяют некоторый способ образования новых типов из уже имеющихся.
Таким образом, допускается образование структур данных
произвольной сложности.
§6.1. Массивы
Массив – это упорядоченная совокупность однотипных
элементов, названных одним именем и различающихся индексами. Таким образом, доступ к элементам производится указанием общего имени и порядкового номера, то есть индекса или
индексов, необходимого элемента.
Определение массива имеет вид:
Type <имя> = array [T1] of [T2];
Var <имя>[, …] : array [T1] of [T2];
Здесь Т1 обозначает тип индексов массива, Т2 – тип элементов массива. В качестве типа индекса можно использовать
любой дискретный тип, кроме Longint, в основном используются диапазоны. Элементами массива могут быть значения любого типа, кроме файлового.
Пример:
Type
M2 = array [1..100] of real;
Z = array [boolean] of integer;
Количество индексов у элемента массива может быть и
более одного, то есть могут использоваться не только вектора,
но и матрицы, кубические матрицы и т.д. Таким образом, количество индексов у элемента массива определяет его размерность, а количество элементов, то есть произведение
элементов в каждой размерности, определяет размер массива.
Так как в качестве элементов массива могут выступать
значения любого типа, например другие массивы, то в общем
случае типы индексов у элемента могут не совпадать:
Var Mass: array [1..10] of array [char] of array
[boolean] of byte;
75
Соответственно, пример обращения к элементу массива,
где в качестве индексов используются выражения соответствующего типа:
Mass[i+5]['A'][True]
Так как индексы могут вычисляться, то при обращении к
элементам массива они являются арифметическими выражениями. Этот пример является полной формой записи, в основном же используют краткую форму:
Var
Mass: array [1..10,char,boolean] of byte
например, с таким обращением
Mass[x+1,gamm,bool2]
В конкретной реализации Турбо-Паскаль, работающей в
ОС MS DOS, есть ограничение на общее количество элементов
одного массива, связанное с сегментной организацией памяти:
максимальный размер 216 = 64Кбайт.
§6.2. Строки
Массивы, содержащие в качестве элементов символы, являются особенными. Если с обычными массивами обработка
идет поэлементно, с использованием операций, разрешенных
для элементов, то элементы текста взаимосвязаны, и, соответственно, используются специальные процедуры и функции.
В стандартном Паскале для строковых массивов были
введены некоторые дополнительные правила, в Турбо-Паскале
такие массивы выделены в отдельный тип – строки.
Строки описываются следующим образом:
Type <имя> = string [[<длина>]];
Var <имя>[, ...] : string [[<длина>]];
Здесь необязательный параметр <длина> указывает максимальное количество символов в строке. Если длина не задана,
то
по
умолчанию
(автоматически)
принимается
максимально возможная длина 255. Это связано с тем, что
один байт как раз и указывает на длину строки, но в сам текст
строки не включается.
Строка в Турбо Паскале трактуется как цепочка символов. К любому символу в строке можно обратиться точно так
же, как к элементу одномерного массива ARRAY [0..N] OF
CHAR: указывается имя и в квадратных скобках позиция символа. Например:
var
st: String;
76
begin
…
if st[5] = 'A' then…
…
В дополнение к операциям, применимым к переменным
типа Char, введены групповые операции сцепления (конкатенации, объединения) нескольких строк с использованием знака
«+». При этом длина итоговой строки увеличивается, и это
главное отличие строк от массивов. Элементы строки нумеруются, начиная с единицы, отсутствие элементов – строка нулевой длины. Запись константы имеет вид строки, в следующем
примере объединяются три константы:
Writeln(' Это'+' – '+'строка');
Результат: вывод строки «Это – строка»
Для работы со строками используются следующие стандартные процедуры и функции.
Функцию Concat (<строка> [, …]) можно использовать
вместо операции сцепления. Тогда пример сцепления строк будет выглядеть как:
Writeln(Concat(' Это',' – ','строка');
Функция Length (<строка>) позволяет определить не предельную, а фактическую длину строки. Результат – целое число.
Например:
If Length(st)<>0 then …
Функция UpCase (<символ>) позволяет преобразовать
символ любой латинской буквы из строчной в прописную.
Функция Copy (<строка>, <номер первого символа>,
<количество символов>) позволяет копировать или выделять
фрагмент строки, например:
Writeln(Copy('Выводятся символы этой строки',
11,7));
Результат: вывод строки «символы».
Функция Pos (<искомая подстрока>, <строка>) позволяет произвести поиск определенного фрагмента в некоторой
строке и определить номер символа, с которого начинается
вхождение подстроки. Например:
Writeln(Pos('сл','Поиск слова');
Результат: вывод числа 7.
77
Процедура Insert (<вставляемая подстрока>, <строка>,
<номер символа в строке>) вставляет в исходную строку,
начиная с указанной позиции, другую строку. Например:
Insert('важного ','Поиск слова',7);
Результат – строка (второй аргумент) изменится на «Поиск
важного слова».
Процедура Delete (<строка>, <номер первого удаляемого
символа>, <количество символов>) удаляет в исходной строке
фрагмент определенной длины:
Delete('Это – строка',4,2);
Результат – строка (первый аргумент) изменится на «Это
строка». Удаляются 2 символа, так как пробел – тоже символ.
Процедура Val (<строка>, <переменная>, <код ошибки>) преобразует строку символов во внутреннее представление
целой
или
вещественной
переменной,
которое
определяется типом этой переменной. Третий параметр содержит ноль, если преобразование прошло успешно, и тогда переменной присваивается результат преобразований, в противном
случае код ошибки содержит номер позиции в строке, где обнаружен ошибочный символ, и в этом случае содержимое переменной не меняется. Например:
VAL ('135.67',x,cod)
Результат: значение переменных х=135.67 (1.3567102),
cod=0.
§6.3. Записи
Запись – это структура данных, состоящая из фиксированного числа элементов, называемых полями. При этом поля
могут быть различных типов.
При выборе элементов применяется конкретное имя, которое дается каждому элементу и не может вычисляться, в отличие от массивов.
Общий вид описания типа «запись»:
Type T = Record
<список полей>
end;
Например:
78
Type Complex = Record
Re: real;
Im: real;
End;
Здесь Re и Im – имена полей. При наличии этого фрагмента программы в дальнейшем можно вводить переменные
комплексного типа, например:
Var X, Y, Z: Complex;
При обращении к компонентам указывается имя переменной – записи, и через сочленяющую точку – имя поля:
X.Re:=5.2; X.Im:=3.1;
Пример подпрограмм арифметики комплексных чисел:
{ Функция сложения двух комплексных чисел }
Function CADD(A,B:Complex):Complex;
Begin
CADD.Re:=A.Re+B.Re;
CADD.Im:=A.Im+B.Im;
End;
{ Функция умножения }
Function CMUL(A,B:Complex):Complex;
Begin
CMUL.Re:=A.Re*B.Re-A.Im*B.Im;
CMUL.Im:=A.Re*B.Im+A.Im*B.Re;
End;
{ Функция деления }
Function CDIV(A,B:Complex):Complex;
Var x:real;
Begin
x:=sqr(B.Re)+sqr(B.Im);
CDIV.Re:=(A.Re*B.Re+A.Im*B.Im)/x;
CDIV.Im:=(B.Re*A.Im-A.Re*B.Im)/x;
End;
Пример вычисления выражения XY+XZ+YZ:
CADD(CMUL(X,Y),CADD(CMUL(X,Z),CMUL(Y,Z)))
В языке Паскаль разрешается использовать тип Record
при описании других структур данных и наоборот, например:
{ Формирование одной строки таблицы дней
рождения из максимально 100 строк }
Type
Data = Record
D:1..31;
M:string[10];
79
G:word;
End;
Rec = Record
FIO:string;
Dr:Data;
End;
Var List: array [1..100] of rec;
Begin
Z[1].FIO:=’Иванов Иван Иванович';
Z[1].Dr.D:=5;
Z[1].Dr.M:=’Май’;
Z[1].Dr.G:=’1975’;
…
В записях может использоваться не только фиксированная, но и вариантная часть, следующая за фиксированной
(здесь не рассматривается). В итоге полная структура записи
следующая.
Type T=Record
<список полей>:<тип>;
…
[Case <признак варианта> of
<список констант выбора>:
(<список полей>:<тип>;
…);]
…
end;
При обращении к некоторым компонентам записей при
большом количестве вложений могут получаться весьма длинные имена. Для сокращенной записи составных имен используется оператор With, имеющий следующую структуру:
With <список переменных записи> Do <оператор>;
В качестве переменных записи используются имена полей, определенных при описании данной записи, например:
Begin
…
Z[1].FIO:='Иванов Иван Иванович';
With Z[1].Dr do
Begin
D:=5;
M:=’Май’;
G:=’1975’;
End;
…
Оператор With R1,R2,…,Rn do S; эквивалентен записи
80
With R1 do
With R2 do
…
With Rn S;
§6.4. Множества
Множество в математике – это произвольный набор объектов, понимаемый как единое целое. Два множества, отличающиеся только порядком следования элементов, считаются
одинаковыми, например:
{A, B, C}, {A, C, B}, {B, C, A}
и т.д., то есть элементы множества не упорядочены.
На вид объектов и их число не накладываются никакие
ограничения, но в языке Турбо-Паскаль это понятие существенно уже: в качестве базовых типов допускаются дискретные типы не более чем с 256 различными значениями, то есть
типы byte, char, boolean, перечисляемый и диапазон.
Описание типа:
Туре <тип_множество> = SET OF <базовый_тип>
Var <список_переменных>: SET OF <базовый_тип>
В качестве констант в этом типе используется изображение или конструктор множества, который строится из списка элементов, заключенных в квадратные скобки. В качестве
элементов могут выступать и диапазоны, например:
[Blue, Yellow, Red] или [1,4..8,12,15].
Существует единственное пустое множество, которое
принадлежит всем типам множеств и обозначается как [ ].
Пусть есть описание:
Var A, B, C: Set of 0..9;
D, E: Set of '0'..'9';
F, G: Set of Boolean;
Тогда:
A:=[1, 3, 5];
D:=['3', '6'..'9'];
F:=[5, 7]; {неверная строка – «не то» множество}
При использовании диапазонов, если значение первой
константы меньше значения второй константы диапазона, то
задается пустое множество. Запись [5..3] эквивалентна [ ].
Операции над множествами – обычные из теории множеств и математической логики. Пусть S1 и S2 – однотипные
81
множества, тогда над ними можно выполнять следующие операции.
1. Объединение множеств.
S1+S2 содержит элементы, которые принадлежат либо S1,
либо S2, либо и тому и другому:
A:=[1, 2, 3];
B:=[1, 5, 9];
C:=A+B;
что эквивалентно
C:=[1, 2, 3, 5, 9];
2. Пересечение множеств.
S1*S2 содержит элементы, которые принадлежат как S1,
так и S2:
A:=[1, 2, 3];
B:=[1, 5, 9];
C:=A*B;
что эквивалентно
C:=[1];
Или выражение
C:=A*[8];
соответствует пустому множеству:
C:=[ ];
3. Относительное дополнение или разность множеств.
S1-S2 содержит элементы из S1, которые не принадлежат
S2:
A:=[1, 2, 3];
B:=[1, 5, 9];
C:=A-B;
что эквивалентно
C:=[2, 3];
Или выражение
C:=A-[3..9];
соответствует:
C:=[1, 2 ];
82
4. Проверка на равенство, неравенство и включение
множеств.
S1=S2 тогда и только тогда, когда S1 и S2 содержат одни
и те же элементы. В противном случае истинно выражение
S1<>S2. S1<=S2 принимает истинное значение, когда все элементы S1 являются элементами S2 (множество S2 включает в
себя множество S1). Пусть
A:=[1..5];
B:=[1..5];
C:=[1..6];
Тогда логические выражения примут значения:
A=B — истинно;
А=С — ложно;
B<>C — истинно;
A<=C — истинно (С включает А).
5. Проверка принадлежности множеству (включение во
множество).
Эта логическая операция обозначается служебным словом
in. Выражение X in S1 истинно, если элемент Х принадлежит
множеству S1 и ложно в противном случае:
2 in A
9 in A
— истинно;
— ложно.
Этот тип данных используется довольно редко, за исключением операции проверки принадлежности к множеству.
Например, нажата ли клавиша «p» в любом регистре и алфавите:
If CH in ['p', 'P', 'з', 'З'] then ... {нажата
клавиша «р»}
Пример использования множеств
В качестве примера используется программа вычисления
нескольких первых простых чисел методом «решета Эратосфена», выполняемого по следующему алгоритму.
1.
2.
3.
4.
5.
Поместим все числа между 2 и n в «решето».
Выберем и вынем из «решета» наименьшее из оставшихся
в нем чисел.
Поместим это число среди «простых» чисел.
Переберем и вынем из решета все числа, кратные данному.
Если решето не пустое, повторим шаги 2-5.
83
Program Eratosfen;
Const N=255; { Максимально возможное значение }
Var
R,
{ исходное решето }
Pr
{ решето с простыми числами - итог }
: Set of 2..N;
i,
{ счетчик чисел }
j
{ очередное простое число }
: integer;
Begin
R:=[2..N];
Pr:=[]; { пока в решете с простыми числами
ничего нет}
j:=2;
{ первое простое число }
{ цикл по шагам 2-5}
Repeat
{ поиск очередного простого числа
(первый раз не выполняется ни разу) }
While not (j in R) do
j:=j+1;
Pr:=Pr+[j]; { включение в итог – шаг 3}
i:=j;
Writeln(j:8);
While i<=N do
Begin
R:=R-[i]; { вынимание из решета – шаг 4}
i:=i+j
end;
Until R=[ ]; { повторяем, если решето не пустое
}
end.
§6.5. Файлы
Любой язык программирования высокого уровня должен
содержать средства для организации хранения информации на
внешних запоминающих устройствах и доступа к этой информации. Особенность этих средств заключается в существенно
разных способах организации работы на различных вычислительных системах. Поэтому обычно в языках высокого уровня
описываются только базовые понятия, а подробности работы
детализируются в конкретных версиях языка.
В стандартном Паскале под файлом подразумевается заранее не определенное количество компонентов одинакового
типа с общим именем. Порядок компонентов определяется самой последовательностью. Это – основное отличие файлов от
предыдущих структур данных.
84
В любой момент времени доступен только один компонент файла, доступ же к другим производится последовательным продвижением по файлу. То, как выбираются
компоненты, зависит от организации внешнего устройства,
свойств операционной системы, и некоторых других причин,
однако считается, что только некоторые компоненты присутствуют в оперативной памяти в определенный момент времени, причем только компонент, на который указывает буферная
переменная, доступен непосредственно.
Таким образом, с каждым описанием новой файловой переменной (непосредственно файла) автоматически вводится
дополнительная скрытая переменная с таким же типом, как
тип компонент, называемая буферной переменной файла
или текущим указателем файла.
Описание файловой переменной производится следующим образом.
TYPE <имя> = FILE OF <тип_компонент>
VAR <список_имен>: FILE OF <тип_компонент>
Имя файловой переменной назначается независимо от
имен файлов, используемых операционной системой, по правилу образования имен Паскаля. Компоненты могут быть любого типа, кроме файлового, или комбинированного типа, если
одним из его полей является файл. Например:
Var Fil: File of real;
Авторский вариант языка содержит минимальный набор
допустимых действий с файлами, который лишь частично совпадает с операциями, реализованными в Турбо Паскале. Здесь,
в частности, отсутствуют явные операции над буферной переменной (в этом пункте есть несовместимость стандартного и
Турбо-Паскаля). Все операции с файлами реализованы в виде
процедур и функций, что является общепринятым подходом в
большинстве языков программирования.
Все операции с файлами можно условно разбить на 4
группы.
 установочные и завершающие операции;
 собственно ввод-вывод;
 перемещения по файлу;
 специальные операции.
85
Установочные и завершающие операции
Так как имя файловой переменной назначается произвольно по правилам языка, то, чтобы связать его с конкретным
физическим файлом на внешнем носителе информации, используется процедура
Assign (<имя_файловой_переменной>,<строка>);
Здесь имя задается в разделе описания переменных, а
строкой является внешнее имя файла с возможным указанием
пути, например:
Assign (Fil, 'c:\document\myfile.txt');
Строка может быть и переменной, например, можно вводить имя файла с клавиатуры. В качестве строки могут выступать и имена внешних устройств, принятые в MS-DOS: PRN,
CON и другие.
В общем случае над файлами можно производить только
две операции – просмотр файла и создание файла, все остальные являются их производными. Поэтому существуют две различные процедуры
Reset (< имя_файловой_переменной >);
Rewrite (< имя_файловой_переменной >);
Первая предназначена для открытия файла для просмотра. То есть на внешнем устройстве ищется файл, образуется
специальный системный буфер для обмена, буферная переменная устанавливается на начало файла, то есть на его нулевой элемент. При этом предполагается, что открываемый файл
уже существует, в противном случае возникает ошибка.
Вторая выполняет аналогичные действия, но создает не
существовавший ранее файл. Если же такой файл уже существует, то при выполнении этой процедуры файл без предупреждения очищается.
После завершения работы с файлом его необходимо закрыть, то есть выполнить операции, обратные выполняемым
двумя предыдущими процедурами. Для этого предназначена
процедура
Close (< имя_файловой_переменной >);
Хотя при завершении работы всей программы происходит автоматическое закрытие всех файлов, но использование
этой процедуры является правилом хорошего тона. Более того,
86
в некоторых случаях без нее не обойтись, например, при создании файла, а затем использовании из него данных.
Операции ввода-вывода
Это две процедуры, которые и реализуют действия по
чтению информации из файла и записи ее в файл.
Read (<имя_файловой_переменной >,<список_ввода>);
Write (<имя_файловой_переменной >,<список_вывода>);
В отличие от других процедур, они могут вызываться с
различным числом параметров, и эти параметры могут иметь
различные типы (объектно-ориентированные процедуры).
Выполнение процедуры Read происходит, начиная с текущей позиции указателя файла. При этом последовательно
читаются значения, содержащиеся в файле, и присваиваются
очередной переменной из списка ввода. После каждого чтения
указатель файла смещается на следующую позицию. Если указатель файла будет установлен на позицию, не содержащую
информации, то есть будет достигнут конец файла, то возникает ситуация «конец файла», обнаруживаемая с помощью специальной функции EOF.
Так как файл может быть пустым, то есть не содержать
ни одного компонента, то при циклическом чтении информации из файла необходимо использовать оператор цикла
While.
Процедура Write имеет обратный смысл, позволяя записывать в файл информацию из программы. Первым параметром должна быть файловая переменная, открытая процедурой
Rewrite. Далее должен идти список выражений, тип которых
совпадает с базовым типом файла.
Перемещения по файлу
Это группа дополнительных процедур и функций, позволяющая изменять последовательный порядок операций чтения
и записи. Единственным исключением является базовая логическая функция EOF, указывающая на конец файла:
EOF (< имя_файловой_переменной >)
При чтении, если все данные прочитаны, возникает ситуация «конец файла», и эта функция принимает значение True,
иначе она равна False. Так как при записи данные всегда до-
87
бавляются в конец файла, то функция имеет постоянное значение True.
Процедура
SEEK (< имя_файловой_переменной >,<выражение>);
позволяет явно изменять значение указателя файла, устанавливая его на компонент файла с номером, заданным выражением. Здесь выражение должно быть целого типа Longint.
Эту процедуру можно применять не только для произвольного чтения элементов файла, но и для его усечения. Если
затем использовать процедуру
TRUNCATE (< имя_файловой_переменной >);
то компоненты файла, следующие за текущем указателем
включительно, будут удалены.
При использовании процедуры Seek может возникнуть
ситуация, когда компонентов в файле меньше, чем указанное
значение, и может возникнуть аварийная ситуация. Чтобы этого избежать, используют функции
FileSize (< имя_файловой_переменной >)
FilePos (< имя_файловой_переменной >)
Они позволяют получить дополнительную информацию о
файле и возвращают: первая – общее число элементов в файле,
вторая – номер элемента, на который установлен указатель
файла.
Пример работы с файлом
В качестве примера приведем статистическую обработку
информации, находящейся в файле из текущего раздела, имя
которого вводится с клавиатуры, а расширение .DAT назначено по умолчанию. В программе вычисляются математическое
ожидание и дисперсия вещественных чисел соответственно по
формулам:
N
N
 xi2
 xi
M  i
N
и D
i
N
M2 .
Program Statistic;
Var
Fil:File of real; { входной файл }
x,
{ очередное вводимое число }
M,D: real;
{ матожидание и дисперсия }
N: integer;
{ количество введенных значений }
88
Name: string; { вводимое имя файла
расширения) }
Begin
N:=0; M:=0; D:=0;
Read(Name);
{ вводим имя файла }
{ ставим в соответствие внутреннее
имена }
Assign(Fil,Name+'.dat');
Reset(Fil);
{ открываем файл для
While not Eof(Fil) do Begin
N:=N+1;
Read(Fil,x); { в цикле вводим все
M:=M+x;
{ и суммируем их }
D:=D+x*x;
end;
M:=M/N;
D:=sqrt(D/N-M*M);
Writeln(' Матожидание= ',M:10:5,
' Дисперсия= ', D:10:5);
end.
(без
и внешнее
чтения}
значения }
При попытке открыть несуществующий файл может возникнуть аварийная ситуация, поэтому необходимо проверять
корректность операций.
Обработка ошибок ввода-вывода
При выполнении программы, написанной на языке Турбо
Паскаль, установлены следующие правила обработки ошибочных ситуаций, связанных с вводом и выводом. По умолчанию
при выполнении любой такой операции автоматически производится проверка на возникновение ошибки. При обнаружении ошибки выполнение программы прекращается, на экран
выводится диагностическое сообщение с условным номером
ошибки.
Можно предусмотреть в самой программе реакцию на
ошибочные ситуации. Для этого используется директива компилятора {$I-}. В этом случае возникновение ошибки не будет
приводить к немедленному завершению программы, а код этой
ошибки будет запомнен в качестве значения стандартной
функции IOResult без параметров. При отсутствии ошибок
значение этой функции равно нулю. Например:
...
Writeln (' Введите имя файла');
Read (NameFil);
Assign (Fil,NameFil);
{$I-}
{ автоматический контроль отключен }
Reset (Fil);
89
Code:=IOResult;
If Code <> 0 Then Begin
Write ('Ошибка при открытии файла ',
NameFil,': ');
Case Code of
2: Writeln ('файл не найден');
3: Writeln ('путь к файлу не найден');
4: Writeln ('слишком много открытых файлов');
5: Writeln ('доступ закрыт');
6: Writeln ('нарушена информация в полях файла
или системных областях');
8: Writeln ('недостаточно памяти');
10: Writeln ('несовместимые параметры
окружения');
11: Writeln ('нераспознанный формат диска');
else
Writeln ('нераспознана');
end;
Exit;
end;
{$I+}
{ автоматический контроль включен }
...
При использовании функции IOResult, если отключен режим автоматического контроля, то после возникновения ошибки все последующие операции с любым файлом будут
игнорироваться, пока не произойдет обращение к функции
IOResult. Поэтому рекомендуется вызывать эту функцию сразу
после выполнения операции, связанной с файлом. Кроме этого,
обращение к функции обнуляет код ошибки, поэтому повторное обращение будет давать нулевой результат.
Специальные операции
Эта группа операций предназначена для действий с элементами файловой системы ОС – каталогами и файлами. К ней
относятся следующие процедуры:
 удаление файла на диске
Erase (< имя_файловой_переменной >);
 переименование файла
Rename (< имя_файловой_переменной >, <строка>);
 установка текущего каталога
ChDir;
 создание нового каталога
MkDir (<имя_подкаталога>);
90
 удаление пустого подкаталога
RmDir (<имя_подкаталога>);
Перечисленные средства работы с файлами являются
стандартными, при использовании модуля DOS возможно применение дополнительных процедур и функций.
Пример объединения двух файлов
Из двух отсортированных в убывающем порядке файлов
целых чисел IN1.DAT и IN2.DAT получается новый файл
OUT.DAT, отсортированный в том же порядке.
Program Merge;
{ Учебный пример:
Создание файла OUT.DAT, отсортированного в
убывающем порядке, объединенного из файлов
IN1.DAT и IN2.DAT, так же отсортированных }
Var In1,In2,Out: File of integer;
x1,x2: integer; { вспомогательные переменные элементы файлов }
Begin
{ Ставим в соответствие внутреннее и внешнее имя
файлов }
Assign(In1,'IN1.DAT');
Assign(In2,'IN2.DAT');
Assign(Out,'OUT.DAT');
{$I-}
{ Запрет контроля ошибок в среде ОС }
{ Открываем и контролируем открытие файлов }
Reset(In1);
If IOResult <>0 Then Begin
Writeln ('Ошибка файла IN1.DAT');
Exit; End;
Reset(In2);
If IOResult <>0 Then Begin
Writeln ('Ошибка файла IN2.DAT');
Exit; End;
Rewrite(Out);
If IOResult <>0 Then Begin
Writeln ('Ошибка создания файла OUT.DAT');
Exit; End;
{$I+}
{ Обязательно восстанавливаем контроль
за ошибками }
{ в цикле While обрабатываются элементы файлов }
While not (EOF(In1) or EOF(In2)) do
Begin
91
{ Читаем элемент файла, но указатель не
перемещаем, иначе будет пропущен последний
элемент }
Read(In1,x1); Seek(In1,FilePos(In1)-1);
Read(In2,x2); Seek(In2,FilePos(In2)-1);
If x1<x2 Then Begin
Write(Out,x2);
Seek(In2,FilePos(In2)+1)
end
else Begin
Write(Out,x1);
Seek(In1,FilePos(In1)+1)
end;
end;
{ Чтение одного из входных файлов закончено –
переписываем конец незаконченного файла в
выходной }
While not EOF(In1) do
Begin
Read(In1,x1);
Write(Out,x1);
end;
While not EOF(In2) do
Begin
Read(In2,x2);
Write(Out,x2);
end;
{ Закрытие использованных файлов – хороший стиль
программирования}
Close (In1);
Close (In2);
Close (Out);
end.
Текстовые файлы
На персональных компьютерах значительное количество
информации обрабатывается в виде текстов, хранящихся в
текстовых файлах. Их структура отличается от структуры
обычных файлов тем, что содержимое текстового файла рассматривается как последовательность символьных строк переменной
длины,
разделенных
комбинацией
символов,
называемой «конец строки». Эти файлы завершаются специальным кодом «конец файла».
Знание конкретной кодировки управляющих символов не
обязательно, так как они автоматически учитываются при вы-
92
полнении операций над текстовыми файлами, но это и обеспечивает специфику данного вида файлов.
Файлы, имеющие такую структуру, имеют стандартный
тип:
Type <имя_типа> = text;
Var < имя_файловой_переменной >: text;
и имеют в своем составе элементы литерного типа, которые
включают управляющие символы.
Набор операций, применимых к текстовым файлам, содержит, кроме операций для обычных файлов, следующие. К
начальным операциям добавлена процедура
Append (< имя_файловой_переменной >);
предназначенная для записи информации в файл. Ее действие аналогично Rewrite, но при наличии файла она не очищает файл, а ставит указатель файла на его конец. Таким
образом, процедура Append используется, когда необходимо
добавить новые строки в конец уже существующего файла.
Так же используется процедура SetTextBuf, определяющая буфер для обмена с текстовым файлом, здесь не рассматривается.
В операциях, связанных с вводом-выводом, помимо процедур Read и Write, имеются две их модификации:
ReadLn (< имя_файловой_переменной >[,<список_ввода>]);
WriteLn (< имя_файловой_переменной >[,<список_ввода>]);
Они выполняют аналогичные действия, но после операций чтения или записи производят переход к следующей строке текстового файла. Хотя операция Read и переходит
автоматически к следующей строке после окончания текущей,
но процедура ReadLn выполняет это принудительно, не дожидаясь окончания строки, то есть возможен пропуск данных.
При посимвольной обработке, чтобы проверить, достигнут
ли конец текущей строки, используется функция
EOLn (< имя_файловой_переменной>).
Таким образом, типовая схема обработки текстового файла включает в себя двойной цикл. При чтении файла:
...
Assign (< имя_файловой_переменной >,
93
<внешнее_имя_файла >);
Reset (< имя_файловой_переменной >);
{ Возможна обработка особых ситуаций }
While not EOF (<имя_файловой_переменной >) do
Begin
While not EOLn (<имя_файловой_переменной >)
do
Begin
Read (< имя_файловой_переменной >,
< символьная переменная >);
< операторы обработки символа >;
end;
ReadLn (<имя_файловой_переменной >);
end;
Close (<имя_файловой_переменной >);
...
При создании файла:
...
Assign (<имя_файловой_переменной >,
<внешнее_имя_файла >);
Rewrite (<имя_файловой_переменной >);
{ Возможна обработка особых ситуаций }
While < признак_окончания_файла > do
Begin
While <признак_окончания_строки> do
Begin
Write (<имя_файловой_переменной >,
< символьная переменная >);
< операторы формирования символа >;
end;
WriteLn (<имя_файловой_переменной >);
end;
Close (<имя_файловой_переменной >);
...
Стандартные текстовые файлы
В любой программе считаются уже описанными и открытыми два текстовых файла, предназначенные для обмена со
стандартными устройствами ввода-вывода. При вводе это
файл Input, связанный с клавиатурой, при выводе – Output, –
монитор.
Эти файлы рассматриваются как параметры по умолчанию в операциях работы с текстовыми файлами, когда файл
явно не указан. Таким образом:
Write (CH)
Read (CH)
соответствует
соответствует
94
Write (Output,CH);
Read (Input,CH);
WriteLn
ReadLn
EOF
EOLn
соответствует
соответствует
соответствует
соответствует
WriteLn (Output);
ReadLn (Input);
EOF (Input);
EOLn (Input).
В соответствии с общими правилами MS-DOS стандартные файлы могут быть переназначены, то есть связаны с другими устройствами или дисковыми файлами. Для этой цели
можно использовать процедуру Assign, например:
Assign (Output, ‘myfile.out’);
После этого все операции вывода, неявно использующие
этот файл, будут выводить информацию на диск в текущий каталог и файл myfile.out.
При вводе и выводе числовых данных (как и других констант простых типов) в текстовый файл они представляются в
литерном виде и разделяются либо произвольным количеством
пробелов, либо признаком окончания строки. Перевод чисел из
машинного представления в литерное и наоборот осуществляется автоматически. При чтении только числовых данных
можно не анализировать конец строки, процедура Read сама
осуществляет переход к другой строке. Например, вводятся исходные данные
3.51
5.6
-16 15
14
-10.2Е-3
для фрагмента программы
Var i,j,k: Integer;
r,s,t: Real;
...
Begin
Read (r,i,j,k,s,t);
...
Более сложная организация ввода потребуется, если исходные данные содержат смесь литерных и числовых данных.
Например, вводится календарная дата, состоящая из дня (целое), месяца (три символа) и года (целое). Между этими тремя
данными может быть произвольное количество пробелов, а
может и не быть ни одного. Хотя после первого числа обязательно должен быть пробел, — как разделитель, иначе произойдет ошибка ввода.
Program Input_Date;
Var i,j: integer;
ch: char;
95
mch: array [1..3] of char;
Begin
Read(i);
Repeat
Read(ch);
Until ch <> ' ';
mch[1]:=ch;
Read (mch[2]);
Read (mch[3]);
Read (j);
{ Writeln (i,' ',mch,' ',j); }
end.
В данном случае нельзя целиком ввести массив mch, так
как количество пробелов неопределенно.
Файлы без типа
Этот тип данных используется только в Турбо-Паскале и
описывается следующим образом:
Var <имя>: File;
Понятие нетипизированных файлов используется для организации доступа к любым дисковым файлам независимо от
их структуры. Файл представляется как последовательность
компонентов произвольного типа, но необходимо определить
размер этих компонентов.
Открываются эти файлы теми же процедурами, что и
обычные, но вторым параметром должен быть задан размер
компонента в байтах, например:
Assign (Fil,’Data.dat’);
Reset (Fil,512);
Если параметр отсутствует, то по умолчанию размер
предполагается равным 128 байт. Для обеспечения максимальной скорости обмена размер компонента рекомендуется выбирать кратным размеру физического сектора диска, например
512 байт. С другой стороны, размер файла может быть не кратен выбранному размеру, поэтому, чтобы гарантированно
обеспечить полное чтение всего файла, нужно использовать
размер компонента 1.
Для организации обмена с нетипизированными файлами
используются специальные процедуры, здесь не рассматриваемые.
96
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
Что такое массив?
Как описывается массив?
Что такое размер и размерность массива?
Чем отличается краткая от полной формы записи индексов массива?
Каково максимальное количество памяти, которое может
занимать один массив в ОС MS DOS?
Как описываются строки определенной длины?
Какова максимальная длина строки?
Как наиболее просто обратиться к отдельному символу в
строке?
Что такое «конкатенация»?
С помощью какой операции можно объединить две или
более строки?
Какую функцию используют для сцепления строк, какова
форма ее записи?
Какую функцию используют для определения длины
строки, какова форма ее записи?
Какую функцию используют для копирования фрагмента
строки, какова форма ее записи?
Какую функцию используют для определения номера
символа в строке, с которого начинается определенная
подстрока, какова форма ее записи?
Какую процедуру используют для вставки в исходную
строку другой подстроки, какова форма ее записи?
Какую процедуру используют для удаления из строки ее
фрагмента, какова форма ее записи?
Какую процедуру используют для преобразования строки
символов в целое или вещественное значение, какова
форма ее записи?
Что такое «запись»?
Как описывается тип «запись»?
Как обращаться к элементам записи?
Что такое «сочленяющая точка»?
Из каких двух частей может состоять запись?
Для какой цели используется оператор With?
Что такое «множество»?
Как описывается тип «множество»?
Какие простые типы в Турбо-Паскале можно использовать
для создания множеств?
97
27. Как называются константы для множеств?
28. Как обозначается пустое множество?
29. Что произойдет, если при задании в конструкторе множества диапазона, первый элемент будет больше второго?
30. Что такое «объединение множеств»?
31. Что такое «пересечение множеств»?
32. Что такое «разность множеств»?
33. Что такое «включение множеств»?
34. Как обозначается и какой дает результат операция проверки принадлежности множеству?
35. Что в Паскале подразумевается пол файлом?
36. Что такое текущий указатель файла?
37. Почему в общем случае нельзя использовать имена файлов в программе такие же, как и задаваемые в операционной системе?
38. На какие 4 группы можно условно разбить все операции
над файлами?
39. Как записывается процедура, ставящая в соответствие
имя файловой переменной и конкретный файл?
40. Как записывается процедура, открывающая файл для
просмотра?
41. Какие действия выполняются при открытии файла?
42. Как записывается процедура, открывающая файл для записи?
43. Как записывается процедура, завершающая работу с
файлом?
44. Как записывается процедура, выполняющая чтение данных из файла?
45. Как записывается процедура, выполняющая запись данных в файл?
46. Как записывается и как используется функция, указывающая на конец файла?
47. Какой процедурой можно явно изменить значение указателя файла?
48. С помощью какой процедуры можно удалить все элементы файла, следующие за указателем файла?
49. С помощью какой функции можно определить количество
элементов в файле?
50. С помощью какой функции можно определить значение
файловой переменной?
98
51. С помощью какой функции можно определить результат
выполнения операции ввода-вывода в файл?
52. Какое значение принимает функция IOResult при
корректном выполнении операции ввода-вывода?
53. Каковы особенности текстового файла?
54. Как описываются текстовые файлы?
55. С помощью какой процедуры можно добавлять строки в
конец файла?
56. С помощью какой процедуры можно читать из файла
текст с начала строки?
57. С помощью какой процедуры можно записывать в файл
текст с начала строки?
58. Как проверить, достигнут ли в файле конец строки?
59. Каково имя файла для стандартного устройства ввода?
60. Каково имя файла для стандартного устройства вывода?
61. Как разделяются числа в текстовом файле?
62. Зачем используются и как описываются файлы без типа?
99
Глава 7. Динамические структуры данных
§7.1. Динамические переменные
Все рассмотренные ранее структуры данных являются
статическими. Память под переменные выделяется на этапе
трансляции, ее объем и распределение не может изменяться во
время решения задачи в зависимости от вводимых условий.
Исключение составляют файлы. Однако файл не рассматривается как единое целое. Его обработка – это, по сути дела, обработка отдельных компонентов. Здесь постоянно может
требоваться «подкачка» компонент в буферную область оперативной памяти. Работа с компонентами файла – очень медленная операция с точки зрения процессора.
Все предыдущие структуры объединяет следующее. К переменной любого типа можно обратиться с помощью имени:
указывается или просто имя, или имя с индексами, или составное имя и т.д. Любой объект может быть явно обозначен, для
его хранения выделяются конкретные ячейки памяти еще до
выполнения программы, которые связываются с его именем.
Транслятор может обработать и проконтролировать все статические переменные без выполнения программы, на основании
лишь ее статического текста.
В языке Паскаль, как практически во всех современных
языках высокого уровня, есть переменные, которые создаются
и уничтожаются в процессе выполнения программы. Они не
входят в явные описания программы и, следовательно, к ним
нельзя обращаться с помощью имен. Память для них выделяется только динамически в ходе работы программы.
Доступ к динамическим переменным осуществляется с
помощью указателей (или ссылок), которые становятся определенными после создания динамического объекта.
Динамические переменные очень широко используется в
программировании, более того, современные операционные
системы имеют функции поддержки динамических областей
памяти.
Динамические переменные используются при заранее
неизвестном количестве элементов обрабатываемой информации: строк текста, таблиц, больших матриц данных и т.п. Естественно, можно было бы использовать и другие структуры
данных, например массивы, но память тогда будет использоваться неэкономно и, самое важное, придется указывать максимальные размеры данных, что крайне нежелательно. Более
100
того, существуют ситуации, когда без динамических переменных просто не обойтись.
1.
2.
3.
В IBM-совместимых компьютерах используется сегментная организация памяти. Для 16-разрядных ОС, которой
является и MS-DOS, максимальный размер сегмента данных не может превышать 64 Кбайт. Поэтому, если общее
количество описанных статических переменных выходит
за эту границу, возникает сообщение об ошибке «Выход за
пределы памяти». Динамические переменные могут находиться в разных сегментах, поэтому их размер ограничен
только размером оперативной памяти. Начиная с Delphi
2.0 (предназначенной для работы в Windows 95 OCR2, –
32-разрядной операционной системы, и выше) это ограничение снимается. Но здесь широко используются объекты, которые и являются динамическими.
Если в программе используются буфера памяти для временного хранения данных заранее неизвестного размера,
обычно хранящихся в файлах. Это относится, например,
к созданию текстовых редакторов.
Если в программе используются структуры данных, по
своей природе являющиеся динамическими, или наиболее
просто описываемые с помощью динамических структур.
Например, используются объекты, связанные списки или
деревья.
§7.2. Указатели
Тип указателя сам по себе не является динамической
структурой данных, часто его называют ссылочным типом.
Это обычное четырехбайтное число, содержащее адрес, с которого начинается динамическая переменная. Адрес в архитектуре процессоров фирмы Intel – два целых беззнаковых числа,
определяющие номер сегмента памяти и смещение внутри этого сегмента. Указатели используются для установления отношений или связей между динамическими структурами данных.
Эти связи могут быть весьма сложными.
Обозначим динамическую структуру, называемую узлом,
как:
101
Данные
Указатель(и)
При наличии одного указателя, показывающего на следующие данные, структура называется списком (в математике
это является связанным списком). В общем случае в узле может
содержаться несколько указателей, тогда структуру называют
деревом (от генеалогического дерева). Список же является вырожденным деревом. Кроме того, на каждый узел может указывать произвольное число указателей.
В стандартном языке Паскаль указатели должны ссылаться на однотипные элементы данных, то есть являются типизированными. Существует специальное значение указателя NIL
(пустой указатель), принадлежащее всем типам указателей. В
этом случае указатель не указывает ни на какой элемент. Это
значение применяется для обозначения конца списка или ветви дерева (по аналогии в функцией EOF).
Пример общего случая использования динамической
структуры: ввод данных, располагаемых в порядке возрастания: 50, 40, 10, 20, 30.
102
С использованием массива
С использованием указателей
Начало
списка
max
1. 50
X
X
X
X
...
50
X
NIL
max
2. 40 50
X
X
X
...
50
X
40
NIL
max
3. 10 40 50
X
X
...
50
X
40
10
NIL
max
4. 10 20 40 50
X
...
50
X
40
10
20
NIL
max
5. 10 20 30 40 50
...
50
X
40
10
20
30
NIL
Описание указателей:
Туре <имя_указателя>=^<базовый_тип>
Например:
Var
z:^real;
Type poin=^T;
T=Record
str:string;
pnt:poin;
end;
Var p,q:Poin;
Здесь p и q – указатели на переменную типа Т (запись),
чтобы обратиться к самой переменной, записывается p^, q^
(см. далее).
Замечание 1. Правило языка Паскаль: имя любого типа сначала должно быть определено, и лишь затем использовано.
Единственное исключение – при определении ссылочного
типа можно использовать имя типа, который определяется
далее в тексте программы.
103
Замечание 2. В программах, использующих указатели,
обычно нельзя обойтись без раздела описания типов, поскольку при определении ссылочного типа должно использоваться имя базового типа, как правило сложного,
например записи.
Если необходимы указатели на различные типы элементов, то должны быть и различные типы указателей, то есть
каждый указатель может ссылаться только на элементы своего
типа.
Для того чтобы присвоить переменной ссылочного типа
определенное значение, необходимо воспользоваться операцией взятия адреса (указателя), – символа амперсанд «@» и переменной базового типа, например:
z:=@x;
Ссылочные типы можно образовывать от любых других
типов, поэтому допустимо определение вида «указатель на указатель». Например, фрагмент программы:
Type p1=^integer;
Var pp1:^p1;
i:integer;
Begin
p1:=@i;
pp1:=@p1;
приводит к следующим связям:
pp1
Указатель
p1
Указатель
i
Целое
Над значениями ссылочных типов допускаются только
две операции сравнения на равенство и неравенство. Они
проверяют, ссылаются ли два указателя на одно и то же место
в памяти, или нет, например:
Sign:=P1=P2;
If P1<>nil then
...
Работа с динамическими переменными
Доступ, то есть обращение к динамическим переменным,
возможен по двум схемам. Рассмотрим ситуацию, возникшую
после присваивания:
p1:=@i;
Первый вариант очевиден:
104
i:=i+2;
Но, хотя на i ссылается указатель, переменная описана
как статическая.
Для реализации косвенного доступа к переменной через
указатель используется разыменование. То есть, чтобы по
указателю получить доступ к переменной, необходимо после
указателя поставить знак «^». Так, запись р1^ означает: «переменная, на которую ссылается р1». Поэтому операторы
i:=i+2
и
p1^:=p1^+2
полностью эквивалентны. Разыменование имеет тип, совпадающий с базовым типом, в частности p1^ является переменной
целого типа (по описанию выше).
Разыменование допустимо для любых ссылочных типов,
например, возможны следующие конструкции:
j:=pp1^^;
f:=h.l^.g^.p;
При разыменовании может возникнуть некорректная ситуация, хотя и не приводящая к аварийному завершению, но
дающая обычно неверное значение результата (это трудно обнаруживаемая ошибка). Это разыменование указателя со
значением nil. За этим необходимо внимательно следить при
составлении и отладке программы.
Основные действия над динамическими переменными —
это их создание и уничтожение. Первое реализуется стандартной процедурой
New (<указатель>)
При выполнении этой процедуры происходят следующие
действия:
1.
2.
В динамической области памяти отводится место для переменной по размеру базового типа указателя.
Указателю присваивается адрес этой переменной.
Пример.
Var I,J: ^integer; { определяем два указателя на
целую переменную }
NEW (J); { выделяем память для целой переменной и
присваиваем ее адрес указателю }
I:=J;
{ I и J указывают на одну и ту же
переменную (J:=I —запрещено) }
J^:=5;
{ запись числа в выделенную память }
I^:=6;
{ стало и J^=6 }
105
I:=NIL;
{ I ни на что не указывает }
J:=NIL;
{ доступа к переменной больше нет, она
не нужна }
При использовании динамических переменных происходит постоянный процесс изменения указателей. При этом некоторые переменные могут оказаться ненужными, ссылки на
них уничтожаются. Но они занимают память, которую ни для
каких других целей использовать нельзя, то есть образуют «мусор». Таким образом может возникнуть ситуация, что памяти
более чем достаточно, но для размещения новой переменной в
динамической области памяти, называемой «кучей», нет места.
В этом случае значение указателя из параметра не изменится,
но никаких сообщений выдано не будет, что может привести к
непредсказуемым последствиям. Для повышения надежности
программы следует проверять текущее состояние «кучи» перед
каждым созданием новой переменной. Это можно сделать с
помощью стандартной функции MaxAvail без параметров, возвращающей максимальный размер непрерывного участка свободной памяти, пригодного для размещения переменной.
Например, для 4-байтовой переменной типа longint:
Var p:longint;
Begin
...
If MaxAvail >=4 then
New(p)
else
Writeln (‘Переполнение памяти’);
В общем случае для структурированных типов при определении размера выделяемой памяти можно использовать
функцию SizeOf (определение размера переменной):
If
MaxAvail >= SizeOf (TypeDat) then ...
Для освобождения памяти, выделенной под динамическую переменную, используется процедура, обратная по действию процедуре New:
Dispose (<указатель>);
Работа с динамическими переменными требует большой
аккуратности, иначе «засорение» памяти ненужными переменными может привести к быстрому ее исчерпанию. Трудно обнаруживаемые ошибки могут возникать при использовании
динамических переменных в подпрограммах: при выходе из
них локальные ссылки на вновь созданные переменные теряются. Поэтому нужно придерживаться правила: при выходе из
106
подпрограммы или освобождается память от вновь созданных
в ней переменных, или ссылки сохраняются через параметры.
Пример двухсвязанного циклического списка
ми,
0
1
2
3
4
Определим функции при работе со списком с поясненияпредставленными на рис. 7.1:
- просмотр влево (подпрограмма PRL);
- просмотр вправо (подпрограмма PRR);
- удаление (подпрограмма DEL);
- вставка (подпрограмма BCT);
- конец работы.
При проходе первого элемента переводится строка.
A
Начало
B
D
LEF
INF
Выводится
при
просмотре
C
L
Вставка
LEF
INF
Последний
выведенный
узел
RIG
INF
R
L
R
Удаление
RIG
INF
INF
N
INF
LEF
INF
RIG
INF
Виден
удаляемый
узел
Рис. 7.1. Операции с узлами двухсвязанного циклического списка
Program SW_Spisok2;
{ Демонстрационный пример использования
динамических переменных: двухсвязанный
циклический список }
Uses crt;
Type P=^dat;
dat=Record
INF:char; { любой нужный тип элемента списка }
L,R:P;
end;
Var Point,
{ указатель на начало кольца }
lef,rig,n:P;
ch:char;
Procedure PRL; { процедура сдвига влево }
Begin
107
rig:=lef;
lef:=rig^.l; { сдвиг указателей }
Write(lef^.inf);{ вывод информационного поля }
If lef=Point then
Writeln;
{ начало кольца }
end;
Procedure PRR; { процедура сдвига вправо }
Begin
lef:=rig;
rig:=lef^.r;
Write(lef^.inf);
If lef=Point then
Writeln;
end;
Procedure DEL; { процедура удаления }
Begin
If lef^.l=lef^.r then{ не менее 2х элементов }
Writeln('Осталось два элемента')
else
If lef=Point then
Writeln('Начало списка')
else
Begin
rig^.l:=lef^.l; { изменение указателей }
lef^.l^.R:=lef^.r;
Dispose(lef);
{ возвращение памяти }
lef:=Rig^.l;
end;
end;
Procedure BCT; { процедура вставки }
Begin
ch:=ReadKey; Write(ch);
NEW(n);
n^.inf:=ch;
n^.l:=lef;
{ связи из нового узла }
n^.r:=rig;
rig^.l:=n;
{ указание на новый узел }
lef^.r:=n;
rig:=n;
writeln;
end;
Begin
{ непосредственно сама программа }
ch:=ReadKey; Write(ch);
NEW(rig);
rig^.inf:=ch;
{ первый правый узел }
ch:=ReadKey; Write(ch);
108
NEW(lef);
lef^.inf:=ch;
{ первый левый узел }
rig^.l:=lef;
rig^.r:=lef;
lef^.l:=rig;
lef^.r:=rig;
Point:=lef;
{ начало кольца }
Repeat
Ch:=ReadKey;
Case ch of { анализируем введенную команду }
'0': PRL;
'1': PRR;
'2': DEL;
'3': BCT;
'4': Writeln('Конец')
else
Writeln('Неверный ввод')
end;
Until ch='4'; { пока не встретим команду
«конец» }
end.
Указатели без типа
Турбо Паскаль содержит стандартный ссылочный тип, который позволяет не конкретизировать базового типа и считается совместимым со всеми ссылочными типами.
Var <имя_указателя> : pointer;
Работа с ними подразумевает их преобразование в указатели, ссылающиеся на значения конкретного типа, например,
при необходимости организовать список, состоящий из записей различных типов.
Для создания и удаления переменных с такими указателями соответственно используются процедуры:
GetMem (<указатель>,<размер>);
FreeMem (<указатель>,<размер>);
Размер участка памяти указывается в байтах, обычно с
применением функции SizeOf:
GetMem (Ptr,SizeOf(R));
Для работы с нетипизированными указателями используются дополнительные функции, здесь не рассматриваемые.
Контрольные вопросы
1.
Что означает термин «статические переменные»?
109
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
Как производится обращение к статическим переменным?
Что означает термин «динамические переменные»?
Как производится обращение к динамическим переменным?
В каких случаях используются динамические переменные?
Что такое «указатели»?
Для каких целей используются указатели?
Что такое «пустой указатель», и как он обозначается?
Для чего используется операция взятия адреса?
Что такое «разыменование»?
При каком значении указателя его нельзя разыменовывать?
Как выглядит процедура создания динамической переменной?
Что происходит при выполнении процедуры создания динамической переменной?
Что можно определить с помощью функции MaxAvail?
Как выглядит процедура уничтожения динамической переменной?
Что происходит при выполнении процедуры уничтожения
динамической переменной?
С помощью какой функции можно определить максимальный размер непрерывного участка свободной памяти?
Какой тип совместим со всеми ссылочными типами?
110
Глава 8. Низкоуровневые возможности
Паскаля
§8.1. Язык Ассемблер
Специфика системных программ, ориентированных на
непосредственное взаимодействие с физическими устройствами компьютера, а так же необходимость доступа ко всем его
аппаратным возможностям, часто не позволяют создавать элементы программ средствами языков высокого уровня. Более
того, исполняемый код, порождаемый компиляторами, менее
эффективен, чем код, созданный вручную. Поэтому повышенные требования к быстродействию и объему программ вызывают необходимость использования машинных команд или
языка ассемблера.
Ассемблер (assembler, assembly language – монтаж, сборка) – язык программирования, понятия которого отражают архитектуру ЭВМ. Обеспечивает доступ к регистрам, указание
методов адресации и описание операций в терминах команд
конкретного процессора. Ассемблер, содержащий средства более высокого уровня: встроенные и определяемые макрокоманды, соответствующие нескольким машинным командам,
автоматический выбор команды в зависимости от типов операндов, средства описания структур данных и т.д., является
макроассемблером.
Язык ассемблера используется при:
1.
2.
3.
Создании драйверов низшего уровня и некоторых других
программ, требующих прямого доступа к потенциальным
возможностям компьютера.
Достижении наибольшей эффективности использования
вычислительных ресурсов при вычислениях с большим
количеством вложенных циклов. Самые внутренние циклы часто программируются на языке ассемблера.
Решении задач в реальном масштабе времени, то есть
управляющих программ, которые требуют быстрой реакции вычислительной системы на внешние воздействия, и
процессы, строго привязанные к определенным моментам
времени. Более того, управляющие однокристальные ЭВМ
обычно имеют маленький объем памяти.
111
4.
Для управляющих ЭВМ набор языков программирования
очень ограничен. Обычно это ассемблер и Си, причем последний часто отсутствует.
Изучение языка ассемблера дает понимание организации
вычислительных процессов в процессоре, принципов его работы и позволяет создавать оптимальные программы и на языках
высокого уровня.
Язык Турбо Паскаль допускает три основных вида доступа ко всем аппаратным возможностям ЭВМ:
1.
2.
3.
Можно запрограммировать подпрограммы вне среды
Паскаль на языке ассемблера или любом другом языке
программирования и подключить полученный после ассемблирования или трансляции объектный модуль к
Паскаль-программе.
Начиная с шестой версии в Паскале появились средства
включения в текст программ фрагментов на языке ассемблера. Эти средства называются «встроенный ассемблер».
Можно непосредственно включать в текст программы
фрагменты, написанные в машинном коде. Эти средства
оставлены для совместимости с предыдущими версиями
Турбо Паскаля и сейчас практически не используются и
здесь не рассматриваются.
После трансляции с большинства языков программирования получается промежуточный объектный файл (OBJ-файл).
Кроме непосредственно кодов он содержит некоторую дополнительную информацию.
Для подключения внешних подпрограмм в Паскале используется директива external, следующая за заголовком подпрограммы. Кроме этого, где-либо в тексте программы надо
задать директиву компилятора $L, аргументом которой является имя OBJ-файла, содержащего код подключаемой подпрограммы, например:
Procedure SRoots(A,B,C:real); external;
Procedure CRoots(B,C,D.F:real); external;
...
{$L ROOTS.OBJ}
Для обеспечения корректности такого подключения необходимо соблюдать определенные межъязыковые соглашения о
связях, принятые в системе Турбо Паскаль.
112
Средства встроенного ассемблера позволяют подключать
ассемблерный текст непосредственно в Паскаль-программы,
что гораздо удобнее и гибче, чем техника подключения независимо разработанных ассемблерных программ с помощью аппарата внешних подпрограмм.
Встроенный ассемблер реализует подмножество языка
ассемблера, совместимого с языками Turbo Assembler фирмы
Borland (TASM) и макроассемблера фирмы Microsoft (MASM).
Хотя некоторые возможности по описанию структур не реализованы, но они во многом соответствуют описаниям Паскальобъектов – констант, переменных, подпрограмм.
Средства, образующие встроенный ассемблер, организованы в виде двух конструкций: ассемблерные операторы (asmоператоры) и ассемблерные подпрограммы.
Структура asm-оператора:
asm
<команды (операторы) ассемблера>
end
Например:
asm
mov ah, 12
{работа с клавиатурой}
{информация о статусе смены регистра}
int 16h
mov OsnFlag,al
mov DopFlag,ah
end;
Обычно комментарии на ассемблере следуют за точкой с
запятой. Но в asm-операторах используются ограничители,
принятые для Паскаля: { } и (* *). Точкой с запятой могут разделяться команды ассемблера, но обычно используется общепринятый разделитель – переход на новую строку.
Поэтому возможна следующая запись:
asm
mov
end;
ax,Left;
xchg
ax,Right;
mov Left,ax;
Синтаксис команды:
[метка:][префикс] инструкция [операнд[,операнд]]
Инструкцией является мнемоническое обозначение действия, выполняемого командой.
Особенности использования asm-операторов.
113
1.
2.
3.
4.
Использование регистров. В связи с тем, что Паскаль
использует некоторые регистры в своих целях, не допускается изменять содержимое регистров BP, SP, SS и DS.
Метки. Образование меток такое же, как и в Паскале.
Они могут быть глобальными или локальными в asmоператоре. Но в последнем случае они не описываются и
должны начинаться с символа «@».
Коды инструкций. Поддерживаются только все коды
инструкций процессора i8086. Для использования расширения этих кодов для процессора i80286 и арифметических
сопроцессоров
необходимо
использовать
специальные директивы компилятора.
Зарезервированные слова. Для совместимости с ассемблером используется 41 зарезервированное слово, имеющее область действия только в asm-операторах. Эти слова
имеют приоритет над именами пользователя, например:
Var Ch:char;
...
asm
mov Ch,1
end;
Здесь единица загружается в регистр процессора Ch, а не
в ячейку памяти с именем Ch. Для указания описанного имени, совпадающего с зарезервированным словом, надо использовать перед именем символ «&»:
asm
mov
end;
5.
&Ch,1
Выражения. Использование выражений существенно
отличается в Паскале и ассемблере. В Паскале упоминание переменной понимается как ее содержимое, а в asmоператорах обозначает адрес или константу. Поэтому
оператор для х,
asm
mov
end;
AX,x+4
описанного как переменная, не помещает значение х+4 в АХ, а
загружает в регистр значение слова, хранящегося по адресу,
114
на 4 байта большему х. Для увеличения х на 4 выполняются
действия:
asm
mov AX,x
add AX,4
end;
Но записывать константы возможно следующим образом:
Const x=10;
y=20;
Var z:integer;
...
asm
mov z,x+y
end;
6.
Есть 3 специальных переменных, одна из которых,
@Result, обозначает переменную с результатом функции.
Например, функция
Function Sum(x,y:integer):integer;
Begin
Sum:=x+y;
end;
эквивалентна
Function Sum(x,y:integer):integer;
Begin
asm
mov ax,x
add ax,y
mov @Result,ax
end;
end;
Для чисто ассемблерных подпрограмм может использоваться директива assembler. Это позволяет записывать подпрограммы полностью на языке ассемблера, с отсутствием
операторных скобок и единственным asm-оператором:
Function LongMul(x,y:integer):longint;
asm
mov ax,x
imul y
end;
assembler;
Использование @Result здесь не допускается, так как
компилятор не образует переменную для результата функции.
Обмен данными производится через регистры.
115
§8.2. Доступ к аппаратуре
Доступ к памяти
В Турбо Паскале можно напрямую обращаться к ячейкам
памяти, организованным в три типа предопределенных одномерных массивов и представляющих элементы различного
формата:
Mem – Byte,
MemW – Word,
MemL – Longint.
Для доступа к элементам массивов используется специальный синтаксис, состоящий из номера сегмента и номера
ячейки памяти, оба типа Word:
Mem[$0040:$0049]:=7;
Data := MemW[Seg(V):Ofs(V)];
Обращение к оперативной памяти по физическим адресам обычно используется для доступа к системной информации
DOS и BIOS и разрешено только в однопользовательских операционных системах, например MS-DOS.
Доступ к портам ввода-вывода
В процессорах фирмы Intel используется изолированный
ввод-вывод. То есть область адресов памяти отделена от области внешних устройств. Все подключаемые внешние устройства с точки зрения процессора выглядят одинаково и
являются портами ввода-вывода.
Для доступа к этим портам используются два предопределенных массива
Port, PortW
соответственно с типами byte и word.
Когда элементу массива Port или PortW присваивается
значение, это действие интерпретируется как вывод значения
в порт.
Когда элемент массива выступает как операнд в выражении, то доступ к порту понимается как ввод с порта. Например:
{ Разрешение дальнейшей работы контроллера
прерываний }
Port[$20]:=$20;
116
{ Установка в 1 заданных линий порта }
PortW[Base]:=PortW[Base] or Maska;
{ Ожидание установки в 0 старшей линии порта }
While (Port[$82] and $80)=0 do;
Пример доступа к порту клавиатуры для управления
движением курсора.
Program Kyrsor;
{ Демонстрационный пример доступа к порту
клавиатуры для управления движением курсора }
Uses Crt;
{ используется для создания задержки }
Var Mode, { записываемый в порт байт }
Ch:byte; { число, читаемое с клавиатуры }
Begin
Writeln(' Выберите режим ввода символов:');
Writeln(' Задержка перед первым повторением:');
Writeln(' 1 - 250 мс');
Writeln(' 2 - 500 мс');
Writeln(' 3 - 750 мс');
Writeln(' 4 - 1 с');
Repeat
Read(Ch);
Until Ch in [1..4];
Case Ch of
1: Mode:=0;
2: Mode:=$20;
3: Mode:=$40;
4: Mode:=$60;
end;
Writeln(' Частота перемещения, раз/сек:');
Writeln(' 1 - 30');
Writeln(' 2 - 20');
Writeln(' 3 - 10');
Writeln(' 4 - 5');
Writeln(' 5 - 2');
Repeat
Read(Ch);
Until Ch in [1..5];
Case Ch of
2: Mode:=Mode or 4;
3: Mode:=Mode or $0a;
4: Mode:=Mode or $14;
5: Mode:=Mode or $1f;
end;
{ Команда на установку режима }
Port[$60]:=$f3;
{ Задержка на восприятие }
Delay(10);
{ Код режима }
Port[$60]:=Mode;
117
end.
Работа по прерываниям
Программный интерфейс DOS и BIOS организован в виде
обращения к процедурам по прерываниям.
Прерыванием является указание процессору на то, что
произошло какое-либо событие и требуется его обработка,
называемая реакцией на прерывание.
Прерывания делятся на внешние по отношению к процессору, например от часов реального времени, и внутренние,
например деление на ноль или переполнение. Более того, любое
прерывание можно вызвать командой
INT n
где n - номер прерывания, обычно обозначается в шестнадцатеричной системе счисления (с буквой Н в конце числа).
Реакцией процессора на прерывание является выполнение подпрограммы, адрес начала которой, или вектор прерывания, находится в таблице, номер строки которой и есть
номер прерывания. Максимальное количество прерываний для
процессоров Intel – 256.
К подпрограммам DOS и BIOS доступ осуществляется по
прерываниям. Связано это с тем, что в разных версиях адреса
начала различных подпрограмм могут начинаться с разных адресов. А так как таблицу векторов прерываний формирует сама ОС, то достаточно только зафиксировать назначение строк
таблицы, а ее содержание может изменяться.
В IBM-совместимых компьютерах не все вектора прерываний распределены между событиями и устройствами. Неиспользуемые номера и предназначены для обращения к
подпрограммам ОС.
Так как свободных прерываний не много, то по одному
вектору может вызываться несколько подпрограмм. Номер
подпрограммы, называемый функцией или службой прерывания, передается в качестве параметра, записываемого в регистр АН. Например, наиболее часто используемое прерывание
DOS 21Н имеет 87 служб.
Подпрограммы DOS имеют более высокий уровень обработки информации и часто дополнительно используют прерывания BIOS.
Основные прерывания DOS:
118
21Н – общие функции DOS;
25Н – безусловное чтение с диска;
26Н – безусловная запись на диск.
Основные прерывания BIOS:
10Н – работа с видеосистемой;
12Н – получение размера основной памяти;
13Н – работа с дисками;
14Н – работа с последовательными портами;
15Н – разнообразные службы;
16Н – работа с клавиатурой;
17Н – работа с принтером;
19Н – перезагрузка компьютера;
1АН – работа с часами реального времени.
Иногда возникает ситуация, когда в программе необходимо определить собственные алгоритмы реакции на прерывания от внешних устройств или операционной системы,
отменив при этом стандартные реакции.
Процедура, которая будет обслуживать прерывание,
должна иметь директиву interrupt и строго фиксированный
список параметров:
Procedure <имя> (Flags,
CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP:Word);
interrupt;
Допускается отсутствие или задание отдельных параметров. Содержимое регистров процессора передается в процедуру в качестве параметров, поэтому их можно изменять.
Собственно переопределение некоторого прерывания
производится обращением к процедуре модуля DOS:
SetIntVec (IntNo:byte; Vector:Pointer);
IntNo – номер прерывания, которое необходимо переопределить;
Vector – адрес новой процедуры обработки прерывания,
которая будет запускаться автоматически.
При завершении программы желательно восстанавливать
все переопределения, которые делались программой, включая
и старые обработчики прерываний. Для сохранения предыдущей реакции, то есть адреса начала подпрограммы, используется процедура
119
GetIntVec (IntNo:byte; Var Vector:Pointer);
которая присваивает второму параметру адрес текущей реакции на прерывание с номером IntNo.
Таким образом, несколько программ могут работать с одним прерыванием: сначала срабатывает новый обработчик, затем управление передается следующему, расположенному по
адресу Vector.
Программы, обрабатывающие прерывания, являются
специфическими, так как обращение к ним происходит не из
сегмента программ, а из таблицы прерываний. Поэтому для
них необходимо указывать дальний (межсегментный) вызов
директивой компилятора {$F+}.
Пример. Обработка прерывания, возникающего при
нажатии клавиш Ctrl-Break.
Program Ctrl_Break;
{ Демонстрационный пример обработки прерывания,
возникающего при нажатии клавиш Ctrl-Break }
Uses Dos;
Const BreakFlag:boolean=False;
Var IntSt:pointer; { Адрес стандартного
обработчика }
{$F+}
Procedure CtBr; interrupt;
Begin
BreakFlag:=true;
end;
{$F-}
Begin
{ Запоминаем адрес стандартной реакции }
GetIntVec($1b,IntSt);
SetIntVec($1b,@CtBr);
Writeln('Для окончания нажмите Ctrl-Break');
Repeat
Until BreakFlag;
{ Восстанавливаем первоначальный адрес реакции }
SetIntVec($1b,IntSt)
end.
Контрольные вопросы
1.
2.
3.
4.
Что такое «ассемблер»?
Что такое «макроассемблер»?
В каких случаях используется язык Ассемблер?
Что такое «встроенный ассемблер»?
120
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
Что такое «OBJ-файл»?
Какова структура asm-оператора?
Как в asm-операторах записываются комментарии?
Каков общий вид команды ассемблера (asm-оператора)?
Все ли регистры можно изменять в asm-операторах?
Как записываются локальные и глобальные метки в asmоператорах?
Какие коды инструкций (команд) используются во встроенном ассемблере?
Как обозначаются имена, совпадающие с зарезервированными словами?
Как в asm-операторах Паскаля в выражениях трактуется
имя переменной?
Как в asm-операторах обозначается переменная с результатом функции?
С помощью какой директивы создается подпрограмма,
полностью написанная на языке ассемблер?
Как можно напрямую обратиться к ячейкам памяти?
К ячейкам какого размера можно обращаться напрямую?
Как записывается адрес при обращении к ячейкам массива?
Как выполнить доступ к портам ввода-вывода?
Какой командой можно вызвать любое прерывание?
Что является реакцией процессора на прерывание?
Какой номер имеет наиболее часто используемое прерывание DOS?
Какую директиву должна иметь подпрограмма, обслуживающая прерывание?
Какова форма записи процедуры переопределения прерывания?
Какова форма записи процедуры восстановления прерывания?
121
Глава 9. Модули
§9.1. Основные понятия
Понятие модульного программирования возникло при создании программ большого объема, их увеличивающейся внутренней сложности и коллективном характере разработок.
Сейчас понятие модуля включает независимо компилируемые
и тестируемые программные единицы со строго определенными интерфейсами, которые могут определяться в различных
сочетаниях.
Паскаль является алголоподобным языком, а в таких языках отсутствует модульность. В стандартном Паскале даже нет
возможности отдельно транслировать и тестировать подпрограммы. В Турбо Паскале с введением понятия «модуль» стал
возможным современный стиль реализации программных пакетов, легко подключаемых к любой программе, а так же
ослабли ограничения на суммарный объем готовых программ.
В отличие от подпрограммы модуль считается отдельной
программой, то есть представляет собой отдельно хранимую и
независимо компилируемую программную единицу.
Модуль – это совокупность или коллекция программных
ресурсов, предназначенных для использования другими программами или модулями. Ресурсы – это любые программные
объекты Паскаля, – константы, переменные, типы, подпрограммы.
Основное отличие модуля от программы заключается в
том, что его объекты только используются другими программами, но сам модуль выполнить нельзя.
Все программные ресурсы модуля делятся на две части:
1). Интерфейс. Здесь находятся видимые объекты, то есть
те, которые можно использовать в других программах, и предназначенные именно для этих целей.
2). Реализация. Сюда помещают рабочие объекты, называемые невидимыми или скрытыми. Например, если модуль
содержит подпрограмму универсального применения, то вызываемые этой подпрограммой процедуры и функции, содержащиеся в модуле, и используемые ею переменные могут иметь
чисто внутренний характер и не должны использоваться другими программами.
В разделе реализации так же может находиться секция
инициализации, содержащая любые операторы. Они будут вы122
полняться сразу после запуска программы до операторов основной программы. Используется обычно для установки различных начальных значений. Если в Турбо Паскале секция
инициализации начиналась со слова Begin, то в Delphi 1.0 для
этих целей введено новое служебное слово Initialization.
Начиная с Delphi 2.0, здесь может использоваться и секция завершения (finalization), в которой выполняются действия, противоположные секции инициализации. Например,
закрытие файлов, восстановление векторов прерываний и другие.
Таким образом, общая структура модуля следующая:
UNIT <имя модуля>
Interface
<описание видимых объектов>
Implementation
<описание скрытых объектов>
[ Begin {Initialization}
<операторы инициализации>
{ Finalization
<завершающие операторы> } ]
end.
Например, модуль, подключаемый при работе с комплексными числами:
Unit COMPLEX;
Interface
Type Complex = Record
Re,Im:Real;
end;
Function CADD (A,B:complex):complex;
Function CMUL (A,B:complex):complex;
Function CDIV (A,B:complex):complex;
... ...
Implementation
Function CADD;
...
{тело функции}
Function CDIV;
Var X:Real;
{скрытая переменная}
... ...
end.
При использовании процедур и функций есть некоторые
особенности. Заголовок подпрограммы содержит всю информацию, необходимую для ее вызова: имя, количество и тип параметров, и для функций тип результата. Тело же
подпрограммы содержит блок, раскрывающий его алгоритм.
123
Можно считать, что заголовок подпрограммы является ее
интерфейсом, а тело – реализацией. Поэтому в интерфейсной
части модуля должны быть представлены только заголовки
процедур и функций, доступные для других программ, по аналогии с опережающим описанием. Полные же описания с сокращенным заголовком помещают в раздел реализации.
Таким образом, программа может связываться с модулем
через описания видимых объектов, то есть через интерфейс
модуля. Поэтому, если необходимо расширить модуль или изменить реализацию какой-либо подпрограммы, и если интерфейс модуля при этом не меняется, то такое изменение никак
не отразится на использующих программах.
§9.2. Использование модулей
Модуль компилируется так же, как и обычные программы, но так как модуль сам по себе не выполняем, то в результате получается специальный файл с расширением TPU (Turbo
Pascal Unit).
Для того чтобы получить доступ к интерфейсным объектам модуля, необходимо указать в программе имя нужного
TPU-файла в разделе спецификаций используемых модулей,
идущего сразу за заголовком:
USES <список модулей>;
При ее наличии в программе считаются известными все
описания из интерфейсной части подключенного модуля, и к
ним можно обращаться так же, как если бы они были описаны
в самой этой программе:
Program Prim;
Uses Complex;
Var R,X,Y,Z: Complex;
Begin
Read (X.RE,X.IM,Y.RE,Y.IM,Z.RE,Z.IM);
R := CADD(CMUL(X,Y),CADD(CMUL(X,Z),CMUL(Y,Z)));
Writeln(R.RE,'+',R.IM,'i')
end.
Правила использования.
1. Иногда некоторые имена используемого модуля могут
совпадать с именами использующей его программы. Тогда интерфейсные имена модуля, указанного в списке первым, образуют самый внешний блок программы, имена второго модуля
образуют блок, вложенный в первый и т.д.
Например, в программе есть спецификация:
Uses A,B;
124
то вложенность блоков будет следующей:
Имена модуля А
Имена модуля В
Имена программы
То есть имена выполняемой программы «экранируют»
одинаковые имена модулей А и В. Получить доступ к одноименным переменным модуля можно, используя составное
имя.
Пусть есть модуль:
Unit A;
Interface
Var X:Real;
Procedure Pr(Y:integer);
implementation
... ...
end.
Пусть программа, использующая этот модуль, так же содержит переменную Х:
Program P;
Uses A;
Var X:Integer;
Begin
... ...
X:=8;
{ определение внутренней целой
переменной }
A.X:=2.5; { переменная модуля (составное имя) }
Pr(X);
{ X - фактический целый параметрзначение }
... ...
end.
2. Разрешены косвенные использования модулей. Причем
в спецификации использования указываются только модули,
непосредственно используемые в программе. Пример косвенного использования модуля А в программе Р:
Unit A;
Interfase
... ...
end.
Unit B;
Interfase
Uses A;
... ...
end.
125
Program P;
Uses B;
... ..
end.
3. Схема взаимного использования модулей может иметь
структуру любой сложности, но недопустимо явное или косвенное обращение модуля к самому себе.
Unit A;
Interfase
Uses B;
... ...
Unit B;
Interfase
Uses A;
... ...
<- недопустимо
4. При наличии раздела инициализации операторы модуля выполняются первыми в порядке описания:
Program P;
Uses A,B;
... ...
Выполняются:
1.
2.
3.
операторы модуля А;
операторы модуля В;
операторы программы Р.
5. Рекомендуется, чтобы имя модуля совпадало с именем
файла на внешнем носителе информации, например:
Unit Module1;
... ...
Текст программы необходимо поместить в файл Module1.pas, а оттранслированный модуль будет находиться в
файле Module1.tpu. При необходимости хранить код модуля в
файле с другим именем используют директиву компилятора
$U. Она имеет параметр дискового имени файла с данным модулем и должна находится непосредственно перед именем модуля в спецификации пользователя. Например, запись
Uses {$U MY} MyUnit;
приведет к тому, что компилятор будет искать код модуля
MyUnit в файле MY.TPU.
§9.3. Стандартные модули
Обычно все используемые модули находятся в текущем
каталоге или в системном библиотечном файле TURBO.TPL
(Turbo Pascal Library). В этот файл можно добавлять и свои
модули, но в стандартном варианте там находятся 5 модулей,
содержащих все системные константы, типы, процедуры и
функции:
SYSTEM,
DOS,
126
CRT,
PRINTER,
OVERLAY.
Остальные модули
GRAPH,
STRINGS,
WINDOS,
TURBO3 и GRAPH3
размещаются в отдельных файлах TPU.
В модуле System находятся все процедуры и функции
стандартного языка Паскаль и расширения для Турбо Паскаля,
относящиеся к типам и операторам. Этот модуль подключается
к любой программе автоматически, то есть считается, что в
каждой программе есть строка
Uses SYSTEM;
Модули Dos и WinDos содержат средства доступа к основным функциям операционной системы и обработки файлов.
Модуль Crt обеспечивает работу с экраном дисплея в текстовом режиме, работу с клавиатурой и простейшее управление звуком.
Модуль Printer содержит единственный интерфейсный
элемент – файловую переменную Lst стандартного типа Text,
системно связанную с печатающим устройством. Ее использование в стандартных процедурах приводит к выводу информации не на экран, а на печать.
Модуль Overlay предоставляет средства для организации
программ, размер которых превышает объем доступной оперативной памяти.
Модуль Graph объединяет многочисленные программные
средства управления графическим режимом работы дисплея.
Модуль Strings позволяет использовать строки с завершающим нулем, что вместе с расширенным синтаксисом позволяет создавать Windows-приложения.
Модули Turbo3 и Graph3 предназначены для обеспечения
совместимости программ, созданных на ранних версиях Турбо
Паскаля и сейчас не используются.
§9.4. Модуль Crt
Формирование изображения на экране монитора происходит с помощью дисплейного адаптера (видеокарты). В зави127
симости от него дисплей может работать в разных текстовых и
графических режимах.
Текстовый режим поддерживают все типы адаптеров. В
этом режиме каждый символ кодируется одним байтом и занимает от 16х16 до 8х8 точек или пикселов, чем существенно
экономится память. В зависимости от типа адаптера и включенного режима количество строк на экране и символов в
строке может быть разным: от 40х25 до 120х50, но стандартным является размер экрана в 25 строк по 80 символов.
Черно-белый текстовый режим является частным случаем
цветного. Для последнего совместно с кодом самого символа
задается байт атрибута цвета. Каждый символ имеет цвет
начертания, или цвет переднего плана и цвет фона. Кроме этого символ может мерцать. Эта информация кодируется в байте атрибута символа:
128
Цвет фона
Цвет символа
Биты
7
6
5
4
3
2
1
0
Бит мерцания
Красный
Зеленый
Голубой
Яркость
Красный
Зеленый
Голубой
Таким образом, цвет фона может задаваться восьмью
темными тонами, а цвет символа 16-ю, и каждому из них соответствует константа со стандартным именем.
Темные цвета:
0
1
2
3
4
5
6
7
(Black) – черный;
(Blue) – синий;
(Green) – зеленый;
(Cyan) – голубой;
(Red) – красный;
(Magenta) – фиолетовый;
(Brown) – коричневый;
(LightGrau) – светло-серый.
Светлые цвета:
8 (DarkGray) – темно-серый;
9 (LightBlue) – светло-синий;
10 (LightGreen) – светло-зеленый;
11 (LightCyan) – светло-голубой;
12 (LightRed) – светло-красный;
13 (LightMagenta) – светло-фиолетовый;
14 (Yellow) – желтый;
15 (White) – белый.
Начальная координата экрана находится в левом верхнем
углу, а увеличение идет по осям Х и Y аналогично записи на
бумаге по позициям и строкам. То есть экран в стандартном
текстовом режиме выглядит в соответствии с рис. 9.1.
129
Начальная
позиция
Х
1,1
80,1
Экран
в текстовом режиме
1,25
80,25
Y
Конечная
позиция
Рис. 9.1. Координаты знакомест в стандартном текстовом режиме
монитора
То есть увеличение Х соответствует перебору символов в
одной строке слева направо, а увеличение Y – перебору строк
сверху вниз.
Работа с экраном в текстовом режиме
Первоначально при запуске программы на экране остается информация от предыдущих действий, для очистки экрана
используется процедура
ClrScr;
При этом курсор помещается в верхний левый угол. Операторы Write и Writeln начинают вывод информации с текущей позиции курсора. Поэтому, если управлять положением
курсора, то нет необходимости несколько раз использовать
оператор Writeln или заключать в апострофы строки пробелов.
Для установки курсора в точку с координатами X, Y используется процедура
GoToXY (X,Y:byte);
Для того чтобы узнать, где в текущий момент находится
курсор, используются функции
WhereX: byte;
WhereY: byte;
Например:
Var A,B:byte;
Begin
... ...
A:=WhereX;
B:=WhereY;
Write('Курсор находится в столбце ',А,
130
' и строке ',B);
... ...
Чтобы установить цвет символов и фона, соответственно
используются процедуры
TextColor (Color:byte);
TextBackGround (Color:byte);
Они изменяют дальнейший выводимый цвет символов
и полей знакомест.
Первоначально ОС сама устанавливает цвета, обычно
черный фон и светло-серые символы, но их можно изменить
этими процедурами. Если сделать фон и символы одинакового
цвета, то они становятся невидимыми. Для закраски всего
экрана одним цветом после установки цвета очищается экран:
TextColor (White);
TextBackGround (Blue);
ClrScr;
GoToXY (35,13);
Write ('Середина экрана');
В модуле Crt существуют и другие способы и процедуры
для работы с режимами, цветами и курсором на экране.
Работа с клавиатурой
Кроме стандартных процедур Read и Readln в модуле Crt
есть две процедуры, расширяющие возможности работы с клавиатурой.
Функция
ReadKey:char;
считывает один символ с клавиатуры. Его чтение не сопровождается эхо-отображением на экране, что используется в
программах с меню и других, требующих постоянного управления с помощью клавиатуры. Более того, с ее помощью можно
читать коды управляющих клавиш, чего нельзя сделать с помощью команды Read.
Если нажата не символьная, а управляющая клавиша, то
первое считанное число – 0, а код клавиши надо ввести еще
раз.
Пример: программа проверки нажатия функциональных
клавиш F1-F10.
Program DemoCode;
{ Демонстрационная программа проверки нажатия
функциональных клавиш }
Uses Crt;
131
Const Esc=27;
F1=59;
F10=68;
Var
Ch:char; { символ, вводимый с клавиатуры }
Priznak:boolean;
Begin
TextColor(Yellow); { Буквы желтые }
TextBackGround(Blue); { Фон синий }
ClrScr;
Repeat
Ch:=ReadKey;
If Ord(Ch)=0 then
Begin
Ch:=ReadKey; { второй символ }
If Ord(Ch) in [F1..F10] then
Writeln(
'Нажата функциональная клавиша F',Ord(Ch)-58)
else
Writeln(
'Нажата управляющая клавиша с кодом ',Ord(Ch))
end else
Writeln(
'Нажата символьная клавиша с кодом ', Ord(Ch));
Until Ord(Ch)=Esc;
end.
При обращении к функции
KeyPressed:boolean;
ожидания нажатия клавиши нет, она просто опрашивает
буфер клавиатуры и выдает логический результат: True – если
в буфере есть какой-либо символ и False – в противном случае.
Работа со звуком
В ПЭВМ есть возможность генерировать звуковые сигналы частотой от 37 Гц до 20 КГц с помощью встроенного динамика. Из программы на Паскале воспроизводятся только
чистые тона без искажений. Сила, или громкость звука не регулируется.
Стандартный звуковой сигнал длительностью 0,25 сек
и частотой 800 Гц вызывается символом 7 кодовой таблицы,
например:
Writeln (‘Ошибка данных’,Chr(7));
Для управления частотой звука и его продолжительностью
в модуле Crt предусмотрены следующие процедуры. Включение
звука:
132
Sound(Freq:word);
Целый аргумент указывает частоту звучания в герцах.
Звук будет продолжаться, пока не будет выключен.
Отмена (выключение) звука:
NoSound;
Задержка по времени:
Delay (ms:word);
Целый аргумент указывает длительность в миллисекундах. Это универсальная процедура и может использоваться в
самых различных целях. Но она использует встроенную схему
таймера с интервалом 18,2 раз в секунду, поэтому для точного
вычисления времени эту процедуру использовать нельзя.
Таким образом, стандартный звуковой сигнал – это последовательность процедур, например:
Sound (800);
Delay (250);
NoSound;
С помощью этих процедур и операторов цикла можно создавать разнообразные звуковые эффекты, например, проиграть гамму:
Program Gamma;
{ Пример проигрывания гаммы }
Uses Crt;
Const
{Частоты нот гаммы от ДО октавы 1 до ДО октавы 2}
Freq: Array[1..8] of
word=(262,294,330,349,392,440,494,524);
VAR
i:integer;
Begin
Repeat
For i:=1 to 8 do Begin
Sound(Freq[i]); { Включение звука }
Delay(500);
NoSound;
{ Выключение звука }
end
Until KeyPressed;
end.
§9.5. Модуль DOS
В Паскале стандартные процедуры работы с файлами и
системной информацией весьма ограничены: нет прямой возможности управления ресурсами компьютера и их контроля,
133
например, получения значения текущего времени. Назначение
же операционной системы – это управление ресурсами и процессами, в частности работа с файловой системой.
В модуле Dos введены процедуры и функции, позволяющие получить доступ к некоторым ресурсам DOS.
Для поиска первого и следующего файла с заданным
именем используются соответственно процедуры:
FindFirst (Path:String; Attr:Word; Var S:SearchRec);
FindNext (Var S:SearchRec);
Здесь:
Path – путь и имя искомого файла. При отсутствующем пути
поиск производится в текущем каталоге, возможно использование шаблонов.
Attr – задаваемые атрибуты файла. В качестве Attr можно
использовать константу AnyFile, означающую любые атрибуты.
S – возвращаемая переменная со следующей структурой:
SearchRec=Record
Fill: Array [1..21] of byte;
Attr: byte;
Time: LongInt;
Size: LongInt;
Name: String [12];
end;
Здесь поле Fill содержит служебную информацию DOS.
Attr – реальные атрибуты файла.
Time – дата и время создания файла в упакованном виде.
Size – размер файла в байтах.
Name – имя найденного файла.
Искомый файл может отсутствовать, поэтому после использования этих и некоторых следующих процедур анализируют системную переменную DosError, описанную выше.
Для распаковки времени создания файла используется
процедура:
UnpackTime (Time:LongInt; Var DT:DateTime);
Выходным параметром для неё является запись DT, имеющая следующую структуру:
DateTime = Record
Year, Month, Day, Hour, Min, Sec: Word
end;
134
Пример: вывод списка файлов из текущего каталога, содержащих тексты программ на языке Паскаль.
Program FindPas;
{ Поиск файлов с Паскаль-программами }
Uses Dos;
Var
DT:DateTime;
S:SearchRec;
Begin
Writeln;
FindFirst ('*.pas',AnyFile,S);
While DosError=0 do
Begin
UnpackTime (S.Time,DT);
WriteLn(' Файл ',S.Name,' создан ',
DT.Day,'.',DT.Month,'.',DT.Year,
' время: ',DT.Hour,':',DT.Min,':',DT.Sec,
' размер ',S.Size,' байт');
FindNext (S);
end
end.
Если необходимо искать файлы не в одном, а в нескольких каталогах, то используют функцию
FSearch (Path:PathStr; DirList:String): PathStr;
Тип PathStr является предопределенным:
PathStr=String [79];
Здесь: Path – имя искомого файла, может быть составным.
DirList – список каталогов, в которых будет производиться поиск, каталоги разделяются точкой с запятой, например:
S:= FSearch ('EDIT.EXE','C:\DOS;C:\NC;C:\NU');
В текущем каталоге поиск производится автоматически.
В случае удачного поиска переменной присваивается строка,
содержащая составное имя файла, в случае неудачи – пустая
строка.
Список каталогов используется аналогично принятым
правилам в DOS. Обычно в файле автозапуска AUTOEXEC.BAT
содержится команда PATH, присваивающая этой переменной
окружения имена каталогов, в которых находятся наиболее часто используемые файлы. В этом случае поиск файлов из среды
DOS производится не только в текущем и корневом каталогах,
но и в каталогах, указанных в этой команде.
135
Чтобы воспользоваться этой переменной, в модуле DOS
введена функция
GetEnv (EnvVar:String):String;
где в качестве аргумента используется имя переменной окружения. Так, если в файле автозапуска указана команда
PATH
C:\DOS;C:\WINDOWS;C:\NC
,
то после выполнения
S := GetEnv ('PATH');
S примет значение
C:\DOS;C:\WINDOWS;C:\NC
,
что может являться аргументом функции FSearch.
Из составного имени файла можно отдельно выделить
полный путь, чисто имя и расширение процедурой
FSplit (Path:PathStr; Var Dir:DirStr; Var Name:NameStr;
Var Ext:ExtStr);
Типы определены как
DirStr = String [67];
NameStr = String [8];
ExtStr = String [4];
Пример поиска файла.
Program PoiskFile;
{ Пример программы поиска файла }
Uses Dos;
Var
NameIn,NameFul:PathStr; { входное и составное
имя файла }
Dir:DirStr;
Name:NameStr;
Ext:ExtStr;
Begin
Writeln(' Введите имя искомого файла: ');
Readln(NameIn);
NameFul:=FSearch(NameIn,GetEnv('PATH'));
If NameFul='' then
Writeln('Файл ',NameIn,' не найден')
else
Begin
FSplit(NameFul,Dir,Name,Ext);
Writeln('файл ',Name,' с расширением ',Ext,
' найден в каталоге ',Dir)
136
end
end.
При создании новых файлов часто необходимо знать, достаточно ли места на диске. Для этого можно воспользоваться
функциями
DiskSize (Drive:Byte):LongInt;
– определение общего объема указанного логического устройства (диска) в байтах;
DiskFree (Drive:Byte):LongInt;
– определение количества свободных байт на диске.
Номер диска задается как: 0 – текущий, 1 – А, 2 – С и т.д.
Для работы с текущей датой и временем используются
процедуры:
GetDate ( Var Year,Month,Day,Day_of_week: Word);
– определяет текущую системную дату. Здесь Day_of_week
определяет порядковый день недели от 0 до 6, причем 0 означает воскресенье.
Аналогично для установки новой даты:
SetDate (Year,Mount,Day:Word);
Процедура для определения системного времени:
GetTime ( Var Hour,Minute,Second,Hund:Word);
Здесь Hund — сотые доли секунды со значением от 0 до
99. Они определяются приблизительно, так как внутренний генератор переключается 18,2 раза в секунду.
Для установки системного времени применяется процедура
SetTime (Hour,Minute,Second,Hund:Word);
Но бо́льшие возможности дает работа непосредственно с
процедурами операционной системы и внешними устройствами.
Для работы с прерываниями в модуле DOS предусмотрены две процедуры вызова прерываний.
Intr (IntNo:Byte; Var Regs: Registers);
Здесь:
IntNo – номер прерывания;
Regs – содержимое регистров процессора следующего типа:
137
Registers = Record
Case integer of
0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags:Word);
1: (AL,AH,BL,BH,CL,CH,DL,DH:Byte);
end;
То есть поля записи соответствуют либо шестнадцатиразрядным, либо восьмиразрядным регистрам процессора.
Например, установка графического режима 640х480 точек, 16
цветов:
Var R:Registers;
... ...
R.AH:=0;
R.AL:=$12;
Intr ($10,R);
Так как очень часто используется вектор прерывания
DOS 21H, то добавлена специальная процедура для работы
только с этим прерыванием:
MSDos ( Var Regs:Registers);
полностью эквивалентная
Intr ($21,Regs);
Например, определение версии DOS:
Uses Dos;
Var R:Registers;
... ...
R.AH:=$30;
MSDos ( R );
Writeln ('MS-DOS ',R.AL,'.',R.AH);
... ...
§9.6. Графический режим монитора
Для создания графических изображений, то есть для работы монитора в графическом режиме, предназначен модуль
GRAPH, хранящийся в соответствующем файле.
В настоящее время используются самые разнообразные
видеоадаптеры, поддерживающие несколько режимов работы
монитора, стандартными из которых считаются:
VGA (Video Graphics Array) с максимальным разрешением
640х480 точек (пикселов);
SVGA (Super VGA) – 800x600;
XGA (eXtended Graphics Array) – 1024x768:
SXGA (Super XGA) – 1280x1024;
UXGA (Ultra XGA) – 1600x1200 и другие.
138
Более того, в каждом режиме можно отображать разное
количество цветов, что существенно зависит от объема видеопамяти. При работе с 16-ю цветами каждая точка занимает
полбайта, с 256 – 1 байт, в режиме High Color (более 65 тыс.
цветов) – 2 байта, в режиме True Color (более 16 млн. цветов) –
3 байта.
В модуле Graph поддерживается только режим VGA.
Здесь можно задавать всего 16 цветов, но три варианта точек
по вертикали: 200, 350 и 480, которые задаются режимами:
VGALo=0, VGAMed=1, VGAHi=2. Для управления режимом VGA
необходимо подключать специальный драйвер, находящийся в
файле EGAVGA.BGI (Borland Graphic Interface).
Если программа запускается из DOS, то графический режим надо инициализировать, задав драйвер и его режим в
процедуре инициализации:
InitGraph (< драйвер >,< режим >,< путь к драйверу >);
например:
D:=VGA;
R:=VGAHi;
InitGraph (D,R,’’);
Аналогично для восстановления текстового режима используется процедура
CloseGraph;
Удобнее автоматически
адаптер процедурой
распознавать
используемый
DetectGraph (<драйвер>,<режим>);
например
DetectGraph (D,R);
InitGraph (D,R,’’);
В графическом режиме курсор отсутствует. Его функции
выполняет невидимый текущий указатель CP (Current
Pointer), выполняющий аналогичные функции. Для его перемещения используется процедура
MoveTo (X,Y:Integer);
причем Х для режима Hi может изменяться в диапазоне от 1 до
640, а Y – от 1 до 480.
Для перемещения указателя относительно его последнего
положения используется процедура
MoveRel (dX,dY:Integer);
139
Аналогично текстовому режиму, здесь используется цвет
переднего плана и цвет фона, которые устанавливаются процедурами
SetColor (Color:Word);
SetBkColor (Color:Word);
Дополнительно используется процедура заливки
крашивания фигур и замкнутых областей
для за-
SetFillStyle (Pattern:Word; Color:Word);
где Pattern определяет стиль заполнения.
Модуль включает множество простейших процедур рисования: отображение точки, прямой, прямоугольников, окружностей и т.п. Для вывода текста используются специальные
процедуры.
Пример построения столбцовой диаграммы двух функций.
Program Diagram;
{ Демонстрационный пример на использование
модуля Graph: столбцовая диаграмма функций F1 и
F2 }
Uses Crt,Graph;
Const
xmin:real=0.05; { Начальные значения аргумента }
xmax:real=1;
step:real=0.05;
Var x,
{ текущее значение аргумента }
Fmin,Fmax:real;
stdiag, { ширина большого прямоугольника }
xdiag,
{ текущая точка диаграммы }
rezim,razm:integer; { режим и разрешение
монитора }
stng:string; { временная переменная для
преобразования числовых данных при выводе }
Function F1(x:real):real;
Begin
F1:=exp(-x)*sin(x)
end;
Function F2(x:real):real;
Begin
F2:=exp(-sqrt(x))*cos(x);
end;
Begin
{ Поиск экстремумов функции }
x:=xmin;
Fmin:=F1(x);
140
Fmax:=Fmin;
Repeat
if F1(x)>Fmax then Fmax:=F1(x);
if F2(x)>Fmax then Fmax:=F2(x);
if F1(x)<Fmin then Fmin:=F1(x);
if F2(x)<Fmin then Fmin:=F2(x);
x:=x+step;
until x>xmax;
{ Контроль за масштабом шкалы ординат }
Writeln('min=',Fmin:8:5,' max=',Fmax:8:5);
Read(Fmin,Fmax);
{ Установка графического режима и цветов }
DetectGraph(rezim,razm);
InitGraph(rezim,razm,'');
SetBkColor(Black);
SetColor(White);
{ Заголовок диаграммы }
OutTextXY(220,20,'Диаграмма двух функций');
{ Координатные линии }
Line(44,50,44,451);
Line(44,451,595,451);
{ Масштабы по X и Y - числовые значения }
Str(xmin:4:2,stng);
OutTextXY(44,460,stng);
Str(xmax:4:2,stng);
OutTextXY(570,460,stng);
Str(Fmax:4:2,stng);
OutTextXY(1,50,stng);
Str(Fmin:4:2,stng);
OutTextXY(1,445,stng);
{ Определение расстояния между столбцами }
stdiag:=round(500/((xmax-xmin)/step));
x:=xmin;
xdiag:=45;
{ Вывод в цикле по два столбца }
Repeat
{ F1 - широкий и зеленый столбец }
SetFillStyle(SolidFill,Green);
Bar(xdiag,450,xdiag+stdiag-2,round(450400*(F1(x)-Fmin)/(Fmax-Fmin)));
{ F2 - красный и узкий столбец }
SetFillStyle(SolidFill,Red);
Bar(xdiag+round(stdiag/4),450,
xdiag+round(stdiag/4*3)-2,round(450400*(F2(x)-Fmin)/(Fmax-Fmin)));
x:=x+step;
xdiag:=xdiag+stdiag;
until x>xmax;
{ Индицируем результат, пока не будет нажата
какая либо клавиша }
141
Repeat
until KeyPressed;
{ Выход из графического режима }
CloseGraph
end.
Контрольные вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
Что такое «модуль»?
Что такое «ресурсы модуля»?
Чем отличается программа от модуля?
Что находится в интерфейсе модуля?
Что находится в реализации модуля?
Для чего предназначен и когда выполняется раздел инициализации модуля?
Какова общая структура модуля?
Как выглядят заголовки подпрограмм в интерфейсной
части и в разделе реализации?
Как выполняется подключение модулей к программе?
Как получить доступ к именам модулей при их совпадении?
Что такое «косвенное использование модулей»?
Может ли модуль обращаться сам к себе (рекурсивное обращение)?
Что необходимо предпринять, если имя модуля и имя содержащего его файла не совпадают?
В каком файле находятся стандартные используемые модули?
Какие 5 модулей находятся в стандартном библиотечном
файле?
Что находится в модуле System, и как он подключается к
программе?
Что содержит модуль Crt?
Что содержит модуль Dos?
Какую информацию содержит байт атрибута символа в
текстовом режиме монитора?
Сколько цветов можно использовать для символа, и
сколько для фона при наличии одного байта атрибута
символа?
В каких местах экрана расположена начальная и конечная координата символов?
Какая процедура используется для очистки экрана?
142
23. Какая процедура используется для перемещения курсора
в заданную точку?
24. С помощью каких функций можно узнать местоположение курсора?
25. С помощью какой функции можно изменить цвет дальнейших выводимых символов?
26. С помощью какой функции можно изменить цвет дальнейших выводимых знакомест (фона символов)?
27. Какая функция предназначена для считывания одного
символа с клавиатуры?
28. Какая функция предназначена для определения нажатия
клавиши?
29. Что такое «стандартный звуковой сигнал»?
30. Какую процедуру используют для включения звука с заданной частотой?
31. Какую процедуру используют для выключения звука?
32. Какую процедуру используют для выполнения задержки
по времени?
33. В каких целях используются ресурсы модуля DOS?
34. Какая процедура используется для поиска первого файла
с заданным именем?
35. Какая процедура используется для поиска следующего
файла, после найденного первого?
36. Какая процедура используется для распаковки данных о
времени создания файла?
37. Какая процедура используется для поиска файла в разных каталогах?
38. Какой процедурой из составного имени файла выделяют
отдельно путь, имя и расширение файла?
39. С помощью какой процедуры можно определить размер
логического устройства (диска)?
40. С помощью какой процедуры можно определить количество свободного места (в байтах) на логическом устройстве (диске)?
41. С помощью какой процедуры можно определить системную дату?
42. С помощью какой процедуры можно определить системное время?
43. С помощью какой процедуры можно установить системную дату?
143
44. С помощью какой процедуры можно установить системное время?
45. Какая стандартная процедура модуля DOS используется
для общего вызова прерывания?
46. Какая стандартная процедура модуля DOS используется
для вызова часто используемого прерывания DOS 21H?
47. Какой стандартный режим монитора поддерживает модуль Graph?
48. Какой процедурой инициализируется графический режим
монитора?
49. Какой процедурой инициализируется текстовый режим
монитора?
50. Какая процедура используется для перемещения графического (текущего) указателя в абсолютных координатах?
51. Какая процедура используется для перемещения графического (текущего) указателя в относительных координатах?
52. Какая процедура используется для установки цвета переднего плана в графическом режиме?
53. Какая процедура используется для установки цвета фона
в графическом режиме?
54. Какой вид имеет процедура заливки для закрашивания
фигур и замкнутых областей?
144
Глава 10. Введение в объектноориентированное программирование
§10.1. История развития программирования
В общих чертах ее можно представить диаграммой:
Машинное программирование
↓
Процедурное программирование
↓
Объектно-ориентированное программирование
Первый объектно-ориентированный язык программирования Симула был создан в 1967 г. и являлся расширением Алгола. В 1970 г. был разработан язык Смолток, но они не стали
популярными из-за низкого быстродействия.
В современные прикладные языки объектно-ориентированное программирование пришло не сразу, так Турбо Паскаль
и Си изначально не были объектно-ориентированными. В 1983
г. появилась объектно-ориентированная версия Си++, а в 1989
г. были включены средства работы с объектами в версию
Турбо Паскаль 5.5.
Понятие объекта
Весь окружающий нас мир состоит из объектов, то есть
предметов живой и неживой природы, которые представляются как единое целое, а отдельные их части образуют сложное
взаимодействие друг с другом. Объект можно разделить на части, но тогда он перестает быть этим объектом.
Объекты являются высшим уровнем абстракции данных,
так как отношения частей к целому и взаимоотношения между
частями становятся понятнее и удобнее тогда, когда все содержится вместе как единое целое.
Обычно, классифицируя некоторый объект, возникают
вопросы: чем этот объект похож на другие объекты из общего
класса и чем он отличается от других. Каждый конкретный
класс имеет свои особенности поведения и характеристик,
определяющих этот класс. В свою очередь классы делятся на
подклассы и т.д., то есть образуют иерархическую структуру,
пример которой приведен на рис. 10.1.
145
Геометрический
объект
Плоский объект
Объемный объект
С вершинами
Без вершин
Окружность
Эллипс
Рис. 10.1. Пример фрагмента иерархии геометрических фигур
Наивысший уровень – самый общий, каждый последующий – более специфический, дополняемый деталями. На последнем уровне определяются цвет, стиль заполнения и т.п.
Более низкий уровень, называемый непосредственным
потомком или подклассом, наследует данные (поля) и программы обработки (методы) уровня, стоящего выше. Уровень,
стоящий выше подкласса, называют непосредственным предком или родителем. Объектный тип в Турбо Паскале может
иметь не более одного родителя, но неограниченное число порожденных типов.
Иерархия объекта – это структурированный в виде дерева набор связей родитель-потомок набора объектов.
Основное правило ООП: если характеристика определена
для какого-либо объекта, то все объекты, расположенные ниже
данного определения, содержат эту характеристику.
Объектные типы в языке Турбо Паскаль очень похожи на
комбинированный тип – записи, то есть объектный тип так же
является составным, элементы, то есть поля которого могут
иметь любой тип. Простейший пример объектного типа, – точка, – может выглядеть следующим образом.
Type
Point = object
X,Y: integer;
Visible: boolean
end;
Для формирования структуры типа объект используется
служебное слово object. В дальнейшем тип Point можно использовать в программе обычным образом: определять переменные
этого типа, как статически, в разделе Var, так и динамически,
создавая экземпляр переменной этого типа с помощью процедуры New; работать с полями и прочее.
146
Но важнейшим и радикальным отличием от обычных
комбинированных типов является возможность, наряду с полями, описывать в объектовом типе процедуры и функции,
описывающие действия, которые можно выполнять с данным
объектом. В этом и заключается одна из главных идей объектно-ориентированного подхода к программированию: предполагается, что объект содержит не только информацию о себе, но
и правила своего поведения, оформленные в виде выполняемых фрагментов, – подпрограмм. Подпрограммы, определенные в объектном типе, называются методами объекта.
§10.2. Свойства объектов
Любой язык объектно-ориентированного программирования характеризуется тремя основными свойствами.
1. Инкапсуляция – это определение записей с методами,
то есть процедурами и функциями, работающими с полями
этих записей, которое формирует новый тип данных – объект.
2. Наследование – определение объекта и дальнейшее
использование всех его свойств для построения иерархии порожденных объектов с возможностью для каждого порожденного объекта, относящегося к иерархии, доступа методам и
данным всех порождающих объектов.
3. Полиморфизм – присваивание определенному действию одного имени, которое затем совместно используется по
всей иерархии объектов, причем каждый объект иерархии выполняет это действие характерным именно для него способом.
Технически инкапсуляция выполняется следующим образом. Непосредственно в описании типа задаются только заголовки подпрограмм-методов, а описания подпрограмм
записываются отдельно, аналогично модулям. Имя, и соответственно обращение к методу, формируется аналогично обращению к полям записей: указывается имя объектного типа, а
через точку-разделитель – имя подпрограммы-метода. Например, точка на экране может быть описана следующим образом:
Type
Point = object
X, Y: integer;
Visible: boolean;
Procedure Create (a, b: integer);
Procedure SwitchOn;
Procedure SwitchOff;
Procedure Move (dx, dy: integer);
Function GetX: integer;
147
Function GetY: integer;
End;
Procedure Point.Create (a, b: integer);
Begin
X := a; Y := b;
Visible := False;
End;
Procedure Point.SwitchOn;
Begin
Visible := True;
PytPixel (X, Y, GetColor);
End;
…
Function Point.GetX: integer;
Begin
GetX := X;
End;
…
Такое разделение диктуется, во-первых, необходимостью
достижения большей наглядности определения объектов, вовторых, становится возможным определение такого типа как
интерфейсного элемента модуля. В этом случае реализация методов объекта будет размещена в соответствующем разделе.
Таким образом, объединение в одном имени информации
о некотором реальном объекте (в примере – точке на экране
монитора) и операциях над ним делает объектный тип замкнутым самодостаточным элементом, содержащим все требуемые
знания о конкретном объекте. Имея такое описание, можно
определять в программе экземпляры объектов, например:
Var
OnePoint: Point;
и в дальнейшем оперировать с этим экземпляром посредством
его методов:
OnePoint.Create (100, 200);
OnePoint.SwitchOn;
OnePoint.Move (20, -10);
Для объектов, как и для записей, можно использовать
оператор присоединения With, например:
With OnePoint do Begin
Create (100, 200);
SwitchOn;
Move (20, -10);
148
end;
Свойство наследования объектных типов позволяет при
построении нового типа использовать ранее определенный объектный тип, что существенно экономит объем текста программы. Например, пусть необходимо построить объектный тип для
круга на экране монитора. Структура информации для него
очень похожа на структуру для точки: здесь также необходимы
поля Х и Y для фиксации центра круга и логическое поле Visible для определения видимости круга в текущий момент. Кроме этого необходимо задавать радиус круга.
При традиционном процедурном стиле программирования можно или ввести для круга совершенно новую структуру,
или сконструировать структуру, используя в ней поля ранее
определенного типа, то есть сделать «структуру в структуре».
Хотя оба подхода приемлемы, но объектно-ориентированное
программирование предлагает иной, более предпочтительный
подход.
В Турбо Паскале при определении типа потомка после
служебного слова object должно быть указано имя родительского типа, например:
Type
Circle = object (Point)
Radius: integer
end;
Это означает, что в объектном типе Circle присутствует не
только поле Radius, но неявно и все поля из типа Point, включая и методы:
Var
OneCircle: Circle;
Begin
…
OneCircle.Create (100, 200);
OneCircle.Radius := 30;
…
Тип-потомок может, в свою очередь, выступать как предок по отношению к другому объектному типу, например,
определение фигуры «кольцо», состоящей из двух концентрических кругов:
Type
Ring = object (Circle)
Radius2: integer
end;
149
Здесь так же наследуются поля и методы типа Point, который считается косвенным предком для Ring. Длина такой
цепочки не ограничена.
В примере с объектным типом Circle он имеет в своем составе методы объекта-предка Point. Но если методы получения
координат центра GetX и GetY здесь можно использовать без
изменений, то для рисования круга методы SwitchOn и SwitchOff неприменимы. Наиболее простым решением было бы ввести в новый тип и новые методы с новыми именами. Но
объектно-ориентированный подход с использованием свойства полиморфизма позволяет определить новые методы со
старыми именами, переопределив тем самым методы типародителя:
Type
Circle = object (Point)
Radius: integer;
Procedure Create (a,b,R: integer);
Procedure SwitchOn;
Procedure SwitchOff;
Procedure Move (dx,dy: integer);
Function GetR: integer;
end;
…
Procedure Circle.Create (a,b,R: integer);
Begin
Point.Create (a,b);
Radius := R
end;
…
Procedure Circle.SwitchOn;
Begin
Visible := True;
Graph.Circle (X,Y,Radius)
end;
…
Function Circle.GetR: integer;
Begin
GetR := Radius
end;
…
Так как стандартная процедура для рисования круга из
модуля Graph так же имеет имя Circle, то для ее однозначной
идентификации необходимо использовать составное имя
Graph.Circle.
Это определение содержит следующие элементы:
150
1.
2.
3.
4.
5.
6.
Унаследованные поля X, Y, Visible;
Собственное поле Radius;
Унаследованные без изменения методы GetX, GetY;
Новый собственный метод GetR;
Полностью переопределенные методы Circle.SwitchOn,
Circle.SwitchOff и Circle.Move;
Частично переопределенный метод Circle.Create. Для
инициализации полей X, Y, Visible используется унаследованный метод Point.Create, но для инициализации поля
Radius метод расширен.
Замечание 1. К полям любого объекта можно обращаться и
напрямую, например, без использования процедуры Circle.Create, функции GetR и других. Но является правилом
хорошего стиля программирования обращение ко всем полям только с помощью методов данного объекта, то есть
поля считаются скрытыми.
Замечание 2. Переопределять можно только методы. Поля,
указанные в родительском типе, наследуются безусловно и
не могут быть переопределены, то есть имена полей типапотомка не должны совпадать с именами полей типапредка. Кроме того, переопределенный метод может иметь
совершенно другие параметры в отличие от методапредка.
Раннее и позднее связывание
Рассмотрим объектные типы Point и Circle. Они связаны
отношением наследования и содержат, наряду с другими, методы SwitchOn, SwithOff и Move. Первые два из них реализуют
алгоритмы рисования и удаления фигуры с экрана, они существенно различны для разных типов. Алгоритм же метода Move
практически одинаков для обоих типов: удаление фигуры со
старого места, изменение ее координат и рисование на новом
месте. Например:
Procedure Move (dx, dy: integer);
Begin
SwitchOff;
X:=X+dx;
Y:=Y+dy;
SwitchOn;
end;
151
Рассмотрим вариант унаследования этого метода без переопределения. Пусть имеются экземпляры двух разных объектов:
Var
OnePoint: Point;
OneCircle: Circle;
то вызовы методов
OnePoint.Move (10, -20);
OneCircle.Move (10, -20);
приведут к одному и тому же действию: перемещению точки.
Это связано с тем, что экземпляр типа-потомка вызывает унаследованный метод Move, который жестко связан с методами
Point.SwitchOn и Point.SwitchOff на ранней стадии, то есть
еще во время компиляции программы. В данном случае связь
методов является статической.
Для того, чтобы в полной мере использовать свойство полиморфизма и использовать одни и те же унаследованные методы,
по-разному
применяемые
к
разным
объектам,
необходимо, во-первых, разорвать связь метода Move с методами предка Point, во-вторых, обеспечить возможность вызывать разные методы SwitchOn и SwitchOff в зависимости от
того, какой объект вызывает Move.
Это можно реализовать только во время вызова метода,
то есть в процессе выполнения программы, поэтому такой механизм называется динамическим или поздним связыванием
и достигается введением виртуальных методов.
§10.3. Виртуальные методы
Метод становится виртуальным, если после его заголовка
стоит служебное слово Virtual. Необходимо помнить, что если
метод в родительском типе объявлен как виртуальный, то все
одноименные методы у потомков так же должны быть виртуальными. Кроме того, они все должны иметь одинаковый набор
формальных параметров, что и самый первый виртуальный
метод.
Если объекты являются динамическими, то, по аналогии с
динамическими переменными они должны создаваться и уничтожаться. Для этого введены понятия «Конструктор» и «Деструктор», которые обсуждаются далее. Так, типы Point и Circle
можно определить следующим образом:
Type
Point = object
…
152
Constructor Create (a, b: integer);
Destructor Done; VIRTUAL;
Procedure SwitchOn; VIRTUAL;
Procedure SwitchOff; VIRTUAL;
Procedure Move (dx, dy: integer);
end;
Circle = object (Point)
…
Constructor Create (a, b, R: integer);
Procedure SwitchOn; VIRTUAL;
Procedure SwitchOff; VIRTUAL;
end;
Полные описания методов остаются такими же, как и
раньше. Теперь обращения к методам
OnePoint.Move (10, -20);
OneCircle.Move (10, -20);
дадут разный результат, то есть определение виртуального метода можно представить как шаблон для всех родственных ему
методов.
Свойство полиморфизма предоставляет очень широкие
возможности при разработке программ. Типы объектов и методы, определенные в различных модулях, могут поставляться
пользователям в виде TPU-файлов без исходного кода. Для работы с объектами модулей только необходимо знать содержимое интерфейсной части. Зная его, можно создавать не только
компактные описания новых объектов, но и добавлять новые
методы к уже существующим. Возможность добавления новых
функциональных характеристик в программу без модификации ее исходного текста называется способностью к расширению. С другой стороны, работа с виртуальными методами
происходит немного медленнее и требует дополнительных затрат памяти, но это не должно служить препятствием к широкому использованию виртуальных методов.
Конструкторы и деструкторы
Корректная работа с виртуальными методами требует
определенных правил.
Если объектный тип содержит хотя бы один виртуальный
метод, то он должен иметь хотя бы один особый метод, называемый конструктором (этот метод может быть и унаследован).
Конструктор должен быть применен к экземпляру объекта до
первого вызова виртуального метода так же, как перед обращением к динамической переменной необходимо ее создать.
Кроме этого, каждый экземпляр объекта должен инициализи153
роваться отдельным вызовом конструктора. Другие экземпляры, даже если они содержат правильные данные, но не проинициализированы, приведут к ошибке выполнения при любых
вызовах их виртуальных методов. Например:
Var
OnePoint, TwoPoint: Point;
Begin
OnePoint.Create (50,100);
…
TwoPoint.Move (21, -20); { Неправильный вызов,
второй экземпляр не создан }
Каждый тип объекта, содержащий виртуальные методы,
имеет таблицу виртуальных методов (ТВМ), хранящуюся в области данных. ТВМ содержит размер типа объекта и для каждого виртуального метода указатель кода, исполняющий
данный метод. Конструктор устанавливает связь между вызывающим его экземпляром объекта и его ТВМ.
Так как имеется только одна ТВМ для любого типа объекта, то отдельные экземпляры объекта содержат только адрес
ТВМ, а конструктор устанавливает значение этого адреса.
С процедурой освобождения памяти связан особый вид
метода, называемый деструктором, и предназначенный для
выполнения действий завершающего характера. В отличие от
методов-конструкторов, деструкторы могут быть виртуальными
и могут наследоваться, в одном объектном типе может быть
определено несколько деструкторов.
В случае единственного деструктора в Турбо Паскале рекомендуется использовать название Done. Этот метод должен
инкапсулировать все детали очистки своего и вложенных объектов и структур данных. Основное преимущество использования деструктора заключается в удалении из памяти
полиморфных объектов, то есть таких, которые были созданы
во время вызова метода, а не во время компиляции.
Так как размеры типов объектов различны, то во время
компиляции из полиморфного объекта нельзя извлечь какуюлибо информацию о его размере. Эта информация становится
доступной для деструктора в момент удаления при обращении
к таблице виртуальных методов экземпляров объектов этого
типа.
Сам по себе метод деструктора может быть пустым и выполнять только функцию связи с процедурой высвобождения
памяти:
Destructor Point.Done;
154
Begin
End;
Деструктор дочернего типа последним действием должен
вызывать соответствующий деструктор своего непосредственного предка, чтобы освободить поля всех наследуемых указателей объекта. В Турбо Паскале можно использовать служебное
слово INHERITED (V7.0), с помощью которого можно вызывать
методы предка без указания его имени, например:
Destructor Circle.Done;
Begin
INHERITED Done;
End;
§10.4. Динамические объекты
Все экземпляры объектных типов, так же, как и значения
любых других типов, могут быть представлены в программе
либо посредством описаний с использованием служебного слова
Var, либо динамически, что используется гораздо чаще. Если
динамический объект содержит виртуальные методы, то он
должен инициализироваться с помощью вызова конструктора,
например следующим образом:
Var
OneP: ^Point;
…
New (OneP);
OneP^.Create (50, 100);
x1 := OneP^.CetX;
…
Затем вызовы методов могут происходить обычным образом с использованием указателя. Турбо Паскаль допускает использование расширенной процедуры New, совмещающей
создание объекта и его инициализацию, которая имеет два параметра: имя указателя и имя конструктора:
New (OneP, Create(50,100));
Здесь вызов конструктора не содержит составного имени,
так как первый параметр однозначно определяет, из какого
объектного типа берется конструктор.
Аналогично для освобождения памяти при позднем связывании используется процедура Dispose с расширенным синтаксисом:
Dispose (OneP, Done);
155
Самостоятельный вызов деструктора вне процедуры Dispose не приведет к освобождению памяти, занимаемой экземпляром объекта.
Скрытые поля и методы
Часть полей и методов объектных типов можно объявить
как скрытые. Смысл введения таких компонент заключается в
ограничении области видимости их имен, так же, как и в модулях. Для этих целей в Турбо Паскале дополнительно введены
два служебных слова PRIVATE и PUBLIC. В итоге описание
объекта в полной форме выглядит следующим образом:
Type
NewObject =
<поля>;
<методы>;
PRIVATE
<поля>;
<методы>;
PUBLIC
<поля>;
<методы>;
End;
OBJECT (<родитель>);
{ общедоступные }
{ общедоступные }
{ скрытые }
{ скрытые }
{ общедоступные }
{ общедоступные }
Объекты наиболее сильно подвергаются изменениям от
версии к версии, поэтому в Delphi использование объектов несколько отличается от использования в Турбо Паскале 7.0.
Контрольные вопросы
1.
Назовите первые объектно-ориентированные языки программирования.
2. Какую структуру имеет классификация различных объектов?
3. Как называется более низкий уровень иерархии относительно текущего?
4. Как называется более высокий уровень иерархии относительно текущего?
5. Поясните понятие «иерархия объекта».
6. Поясните понятие «методы объекта».
7. Какое служебное слово используется для описания структуры «объект»?
8. Поясните понятие «инкапсуляция».
9. Поясните понятие «наследование».
10. Поясните понятие «полиморфизм».
156
11.
12.
13.
14.
Поясните понятие «раннее связывание».
Поясните понятие «позднее связывание».
Как сделать метод виртуальным?
Поясните понятие «конструктор» в применении к объектам.
15. Поясните понятие «деструктор» в применении к объектам.
16. Поясните понятие «скрытые поля и методы».
157
Литература
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
Зелковиц М., Шоу А., Гэннон Дж. Принципы разработки
программного обеспечения. Пер. с англ. М.: Мир, 1982 –
368 с., ил.
Одинцов И.О. Профессиональное программирование. Системный подход. – 2-е изд. перераб. и доп. – СПб.: БХВПетербург, 2004. – 624 с.: ил.
Давыдов В.Г. Программирование и основы алгоритмизации: Учеб. Пособие. – М.: Высш. шк., 2003. – 447 с.: ил.
Кнут Д. Искусство программирования для ЭВМ. т.1. Основные алгоритмы. – М.: Мир, 1975. – 736 с., ил.
Белов В.С., Бруттан Ю.В., Мотайленко Л.В. и др. Основы
информатики и информационных технологий. Часть 1.
Основы информатики. Пособие для поступающих в вуз.
Под общ. ред. к.т.н., доцента В.С. Белова, — СПб/Псков,
Изд-во СПбГПУ, 2004 — 160 с.
Единая система программной документации. ГОСТ
19.701-90. Схемы алгоритмов, программ, данных и систем. Условные обозначения и правила выполнения.
Единая система программной документации. ГОСТ
19.002-80. Схемы алгоритмов и программ. Правила выполнения.
Единая система программной документации. ГОСТ
19.003-80. Схемы алгоритмов и программ. Обозначения
условные графические.
Турбо Паскаль 7.0. Самоучитель. – СПб.: Питер; К.: Издательская группа BHV, 2002. – 416 с.: ил.
Немюгин С.А. TURBO PASCAL – СПб, 2000.
Фаронов В.В. Турбо Паскаль 7.0. Начальный курс. Учебное
пособие. – М.: «Нолидж», 1999. – 616 с., ил.
Федоренко Ю. Алгоритмы и программы на Turbo Pascal.
Учеб. Курс. – СПб.: Питер, 2001.
Зуев Е.А. Язык программирования Turbo Pascal 6.0, 7.0. –
М.: Веста, Радио и связь, 1993.
Климова Л.М. PASCAL 7.0: Практическое программирование. Решение типовых задач.: Учебное пособие. – М.,:
2000.
158
Полетаев Игорь Алексеевич
Полетаев Дмитрий Игоревич
Полетаева Ольга Александровна
Программирование на языке высокого уровня
Паскаль
Учебное пособие
Для студентов технических специальностей всех форм обучения
Технический редактор: Полетаев И.А.
Компьютерная верстка: Полетаев И.А
Напечатано с готового оригинал-макета,
предоставленного авторами
________________________________________________________________
Подписано в печать _______________ Формат 60х90/16.
Гарнитура «Bookman Old Style». Усл. печ. л. 9,5
Тираж ________ экз. Заказ № ______
Адрес издательства:
Россия, 180000, Псков, ул. Л.Толстого, 4
Издательство ППИ
Download