1
ББК 32.973.2-018.2я7
УДК 004.43(075)
П12
Рецензенты:
Сергеев М. Б., завкафедрой вычислительных систем и сетей Санкт-Петербургского
государственного университета аэрокосмического приборостроения,
доктор технических наук, профессор;
Фомичев В. С., доктор технических наук, профессор кафедры вычислительной
техники СПбГЭТУ «ЛЭТИ».
Павловская Т. А.
П12 Паскаль. Программирование на языке высокого уровня: Учебник для вузов.
2-е изд. — СПб.: Питер, 2010. — 464 с.: ил.
ISBN 978-5-49807-772-7
В учебнике рассматриваются структурная и объектно-ориентированная технологии программи-
рования, методы проектирования и отладки программ и основные структуры данных. Книга со-
держит последовательное изложение основ программирования на примере языка Паскаль, практи-
кум по всем разделам курса, полную справочную информацию, 260 индивидуальных заданий для
лабораторных работ и полностью соответствует Государственному образовательному стандарту.
Проверить правильность выполнения основных лабораторных работ, изучить электронный кон-
спект лекций и пройти тесты на знание синтаксиса языка можно на сайте http://ips.ifmo.ru.
Допущено Министерством образования и науки Российской Федерации в качестве учебника для
студентов высших учебных заведений, обучающихся по направлению подготовки дипломирован-
ных специалистов «Информатика и вычислительная техника».
ББК 32.973.2-018.2я7
УДК 004.43(075)
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было фор-
ме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как
надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не
может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за
возможные ошибки, связанные с использованием книги.
2
Краткое оглавление
Предисловие ............................................................ 9
3
4 Краткое оглавление
Семинар 9. Объекты..................................................................................................................311
Семинар 10. Наследование .......................................................................................................334
Литература............................................................ 451
4
Оглавление
Предисловие ............................................................ 9
5
6 Оглавление
6
Оглавление 7
7
8 Оглавление
Литература............................................................ 451
8
Предисловие
Эта книга — для тех, кто по зову сердца или по необходимости изучает основы про-
граммирования. Но почему же Паскаль? Ведь серьезные программы пишут, как
правило, на других языках, так не лучше ли сразу изучать именно то, что пригодит-
ся в дальнейшем?
Сторонникам такой утилитарной точки зрения могу рекомендовать, например,
свои книги по С++ и C# [9, 12], по структуре аналогичные данной. Но следует
иметь в виду, что, начав обучение программированию с «промышленных» языков,
легко увязнуть в их хитросплетениях и так и не приобрести необходимый любому
программисту хороший стиль. Никлаус Вирт создал Паскаль именно для обучения;
язык получился настолько удачным и ясным, что и теперь, спустя десятки лет, и он,
и его потомки используются очень широко.
Язык Паскаль прост, но при этом обладает ключевыми свойствами более сложных
и современных языков высокого уровня. Строгий синтаксис обеспечивает хоро-
шую диагностику ошибок. Наиболее распространенные среды программирования
Borland Pascal with Objects и Turbo Pascal 7.0 при фантастической по современным
меркам компактности обладают достаточно удобными средствами написания и от-
ладки программ. Нельзя не упомянуть и о том, что в профессиональной среде про-
граммирования Delphi используется язык, базирующийся на Паскале.
Эта книга написана на основе учебника и практикума [10, 11], которые выпуска-
лись издательством «Питер» на протяжении ряда последних лет и имеют грифы
Министерства образования. В книге рассматриваются конструкции языка, базо-
вые алгоритмы, методы и приемы написания программ, основные структуры дан-
ных, основы объектно-ориентированного программирования, типичные ошибки,
9
10 Предисловие
Интернет-поддержка книги
Важная особенность этого учебника состоит в его интернет-поддержке: зарегис-
трировавшись на сайте http://ips.ifmo.ru, можно в интерактивном режиме пройти
тесты на знание синтаксиса языка, понимание алгоритмов, знание основных по-
ложений объектно-ориентированного программирования, а главное, проверить
правильность выполнения лабораторных работ, задания для которых приведены
в книге. Каждая программа, посылаемая на сайт, проходит полный набор эталон-
ных тестов. Кроме того, на сайте имеются аналогичные материалы по основам ал-
горитмов и по другим языкам и аспектам программирования.
Таким образом обеспечивается единый для всех высокий уровень качества обуче-
ния основам программирования в соответствии с государственным образователь-
ным стандартом: где бы вы ни обучались, с помощью первой части этого учебника
можно освоить теоретический материал, с помощью второй, практической, части
научиться создавать надежные и эффективные программы, а помогут в этом при-
веденная в третьей части справочная информация и поддерживающий сайт.
В основу книги положен курс, на протяжении многих лет читавшийся автором
в Санкт-Петербургском государственном университете информационных техно-
логий, механики и оптики (СПбГУ ИТМО). Как показала практика, первую часть
этого курса успешно усваивают и школьники старших классов.
Ваши замечания, пожелания, дополнения, а также замеченные ошибки и опечатки
не ленитесь присылать по адресу pta-ipm@yandex.ru — и тогда благодаря вам сле-
дующее издание этой книги станет еще лучше.
10
Часть I. Основы программирования
11
Глава 1. Основные понятия языка
В этой главе описано то, что необходимо для создания простейших программ: эле-
ментарные строительные блоки языка, стандартные типы данных, структура про-
граммы, переменные, операции, выражения и процедуры ввода-вывода.
Состав языка
Язык программирования можно уподобить очень примитивному иностранному
языку с жесткими правилами, не имеющими исключений. Изучение иностранного
языка обычно начинают с алфавита, затем переходят к простым словам, далее рас-
сматривают законы построения фраз, и только в результате длительной практики
становится возможным свободно выражать на этом языке свои мысли. Примерно
так же поступим и мы при изучении языка Паскаль, а сначала определим термино-
логию.
Для решения задачи на компьютере требуется написать программу. Программа
состоит из исполняемых операторов и операторов описания. Исполняемый опе-
ратор задает законченное действие, выполняемое над данными. Примеры испол-
няемых операторов: вывод на экран, занесение числа в память, выход из програм-
мы.
Оператор описания, как и следует из его названия, описывает данные, над которы-
ми в программе выполняются действия. Примером описания (конечно, не на Па-
скале, а на естественном языке) может служить предложение «В памяти следует
отвести место для хранения целого числа, и это место мы будем обозначать А».
Исполняемые операторы для краткости часто называют просто операторами, а опе-
раторы описания — описаниями. Описания должны предшествовать операторам,
в которых используются соответствующие данные. Операторы исполняются по-
следовательно, один за другим, если явным образом не задан иной порядок.
Рассмотрим простейшую программу на Паскале. Все, что она делает, — вычисляет
и выводит на экран сумму двух целых чисел, введенных с клавиатуры:
var a, b, sum : integer; { 1 }
begin { 2 }
readln(a, b); { 3 }
sum := a + b; { 4 }
writeln('Cумма чисел ', a, ' и ', b, ' равна ', sum); { 5 }
end. { 6 }
12
Глава 1. Основные понятия языка 13
В программе шесть строк, каждая из них для удобства рассмотрения помечена ком-
ментарием с номером (внутри фигурных скобок можно писать все, что угодно, но
правильнее, когда там находятся пояснения к программе).
В строке 1 расположен оператор описания используемых в программе величин.
Для каждой из них задается имя, по которому к ней будут обращаться, и ее тип.
«Волшебным словом» var обозначается тот факт, что a, b и sum — переменные, то
есть величины, которые во время работы программы могут менять свои значения.
Для всех переменных задан целый тип, он обозначается integer. Тип необходим для
того, чтобы переменным в памяти было отведено соответствующее место.
Исполняемые операторы программы располагаются между служебными словами
begin и end, которые предназначены для объединения операторов и сами оператора-
ми не являются. Операторы отделяются друг от друга точкой с запятой.
Ввод с клавиатуры выполняется в строке 3 с помощью стандартной процедуры
с именем readln. В скобках после имени указывается, каким именно переменным
будут присвоены значения. Для вывода результатов работы программы в строке 5
используется стандартная процедура writeln. В скобках через запятую перечисля-
ется все, что мы хотим вывести на экран, при этом пояснительный текст заключа-
ется в апострофы. Например, если ввести в программу числа 2 и 3, результат будет
выглядеть так:
Cумма чисел 2 и 3 равна 5
В строке 4 выполняется вычисление суммы и присваивание ее значения перемен-
ной sum. Справа от знака операции присваивания, обозначаемой символами :=, на-
ходится так называемое выражение — правило вычисления значения. Выражения
являются частью операторов.
Чтобы выполнить программу, требуется перевести ее на язык, понятный процес-
сору, — в машинные коды. Этим занимается компилятор. Каждый оператор языка
переводится в последовательность машинных команд, которая может быть весьма
длинной, поэтому Паскаль и называется языком высокого уровня. В языках низ-
кого уровня, например в ассемблере, каждая команда переводится в одну или не-
сколько машинных команд.
Компилятор планирует размещение данных в оперативной памяти в соответствии
с операторами описания. Попутно он ищет синтаксические ошибки, то есть ошибки
записи операторов. Кроме этого, в Паскале на компилятор возложена еще одна обя-
занность — подключение к программе стандартных подпрограмм (например, ввода
данных или вычисления синуса угла).
Алфавит и лексемы
Все тексты на языке пишутся с помощью его алфавита. Алфавит Паскаля вклю-
чает в себя:
прописные и строчные латинские1 буквы, знак подчеркивания _;
цифры от 0 до 9;
1
Заметьте: русских букв в алфавите языка Паскаль нет. К сожалению, хотя Никлаус Вирт
и знает русский язык, но в алфавит Паскаля русские буквы не включил.
13
14 Часть I. Основы программирования
Константы
Константа — величина, не изменяющая свое значение в процессе работы програм-
мы. Классификация констант Паскаля приведена в табл. 1.1. Две нижние строки
таблицы представляют собой примеры соответствующих констант.
14
Глава 1. Основные понятия языка 15
15
16 Часть I. Основы программирования
Типы данных
Данные, с которыми работает программа, хранятся в оперативной памяти. Есте-
ственно, что компилятору необходимо точно знать, сколько места они занимают, как
именно закодированы и какие действия с ними можно выполнять. Все это задается
при описании данных с помощью типа. Тип данных однозначно определяет:
внутреннее представление данных, а следовательно, и множество их возможных
значений;
допустимые действия над данными (операции и функции).
Например, целые и вещественные числа, даже если они занимают одинаковый объ-
ем памяти, имеют совершенно разные диапазоны возможных значений; целые чис-
ла можно умножать друг на друга, а, например, символы — нельзя.
Каждое выражение в программе имеет определенный тип. Компилятор использу-
ет информацию о типе при проверке допустимости описанных в программе дей-
ствий.
Классификация типов
Любая информация легче усваивается, если она «разложена по полочкам». Поэто-
му, прежде чем перейти к изучению конкретных типов языка Паскаль, давайте рас-
смотрим их классификацию в табл. 1.2.
Определяемые программистом1
Стандартные
Простые Составные
Логические Перечисляемый Массивы Файлы
Целые Интервальный Строки Процедурные типы
Вещественные Адресные Записи Объекты
Символьный Множества
Строковый
Адресный
Файловые
1
В литературе чаще встречается термин «типы, определяемые пользователем». При этом
имеется в виду пользователь языка, то есть программист.
16
Глава 1. Основные понятия языка 17
ПРИМЕЧАНИЕ
Типы, выделенные в табл. 1.2 полужирным шрифтом, объединяются термином «по-
рядковые». Этот термин рассмотрен на с. 22.
Логические типы
Внутреннее представление. Основной логический тип данных Паскаля называет-
ся boolean. Величины этого типа занимают в памяти 1 байт и могут принимать всего
два значения: true (истина) или false (ложь). Внутреннее представление значения
false — 0 (нуль), значения true — 1.
Для совместимости с другими языками в Паскале определены и другие логические
типы данных: ByteBool, WordBool и LongBool длиной 1, 2 и 4 байта соответственно. Ис-
тинным в них считается любое отличное от нуля значение.
Операции. К величинам логического типа применяются логические операции and,
or, xor и not (табл. 1.3). Для наглядности вместо значения false в таблице использу-
ется 0, а вместо true — 1.
17
18 Часть I. Основы программирования
Целые типы
Внутреннее представление. Целые числа представляются в компьютере в двоич-
ной системе счисления (отрицательные числа — в дополнительном коде, но для нас
это не принципиально). В Паскале определены несколько целых типов данных, раз-
личающихся длиной и наличием знака: старший двоичный разряд либо восприни-
мается как знаковый, либо является обычным разрядом числа (табл. 1.5). Внутрен-
нее представление определяет диапазоны допустимых значений величин (от нулей
до единиц во всех двоичных разрядах).
Первоначально в Паскале был всего один целый тип — integer, остальные добавле-
ны впоследствии для представления больших величин или для экономии памяти.
Например, нет смысла отводить 4 байта под величину, про которую известно, что
все ее значения находятся в диапазоне от 0 до 100.
Операции. С целыми величинами можно выполнять арифметические операции
(табл. 1.6). Результат их выполнения всегда целый (при делении дробная часть от-
брасывается).
18
Глава 1. Основные понятия языка 19
19
20 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Различие между функцией и процедурой проявляется в способе вызова. Процедура
вызывается отдельным оператором, а функция — в составе выражения. На с. 12 был
приведен пример вызова процедур readln и writeln.
Вещественные типы
Внутреннее представление. Вещественные типы данных хранятся в памяти ком-
пьютера иначе, чем целые. Внутреннее представление вещественного числа состоит
из двух частей — мантиссы и порядка, и каждая часть имеет знак. Например, число
0,087 представляется в виде 0,87×10–1, и в памяти хранится мантисса 87 и порядок
–1 (для наглядности мы пренебрегли тем, что данные на самом деле представляют-
ся в двоичной системе счисления и несколько сложнее).
Существует несколько вещественных типов, различающихся точностью и диапазо-
ном представления данных (табл. 1.8). Точность числа определяется длиной ман-
тиссы, а диапазон — длиной порядка.
ПРИМЕЧАНИЕ
Для первых четырех типов в табл. 1.8 приведены абсолютные величины минимальных
и максимальных значений в виде констант Паскаля.
Автор языка Никлаус Вирт определил всего один вещественный тип — real и отвел
под него разумное количество памяти. Однако аппаратно этот тип в компьютерах
20
Глава 1. Основные понятия языка 21
В общем случае при выполнении любой операции операнды должны быть одного
и того же типа, но целые и вещественные величины смешивать разрешается. Ины-
ми словами, один из операндов может быть целого типа.
ПРИМЕЧАНИЕ
Обратите внимание, что целочисленное и вещественное деление записываются с по-
мощью разных знаков операций. Если требуется получить вещественный результат
деления двух целых величин, нужно использовать операцию /, если целый — опера-
цию div.
21
22 Часть I. Основы программирования
Символьный тип
Этот тип данных, обозначаемый ключевым словом char, служит для представления
любого символа из набора допустимых символов. Под каждый символ отводится
1 байт. К символам можно применять операции отношения (<, <=, >, >=, =, <>), при
этом сравниваются коды символов. Меньшим окажется символ, код которого мень-
ше. Других операций с символами нет, да они и не имеют смысла. Стандартных
подпрограмм для работы с символами тоже немного (табл. 1.11).
Порядковые типы
В группу порядковых объединены целые, символьный, логический, перечисляемый
и интервальный типы. Сделано это потому, что они обладают следующими общими
чертами:
все возможные значения порядкового типа представляют собой ограниченное
упорядоченное множество;
22
Глава 1. Основные понятия языка 23
Приведение типов
Иногда при программировании требуется явным образом преобразовывать вели-
чину одного типа в величины другого. Для этого служит операция приведения типа,
которая записывается так:
имя_типа (преобразуемая_величина)1
Например:
integer ('A')
byte(500)
Размер преобразуемой величины должен быть равен числу байтов, отводимых под
величины типа, в который она преобразуется. Исключение составляют преобразо-
вания более длинных целых типов в более короткие: в этом случае лишние биты
просто отбрасываются. Приведение типа изменяет только точку зрения компиля-
тора на содержимое ячеек памяти, никакие преобразования внутреннего представ-
ления при этом не выполняются.
Линейные программы
Линейной называется программа, все операторы которой выполняются в том поряд-
ке, в котором они записаны. Это самый простой вид программ.
Переменные
Переменная — это величина, которая во время работы программы может менять
свое значение. Все переменные, используемые в программе, должны быть описа-
ны в разделе описания переменных, начинающемся со служебного слова var (от
слова variable — переменная). Для каждой переменной задается ее имя и тип, на-
пример:
var number : integer;
x, y : real;
option : char;
1
Здесь и далее русскими словами обозначены элементы, на место которых должны быть
подставлены конкретные значения. Символ подчеркивания между словами используется
для указания на тот факт, что на это место подставляется одно значениe, а не два.
23
24 Часть I. Основы программирования
СОВЕТ
Желательно, чтобы имя не содержало символов, которые можно перепутать друг
с другом, например: l (строчная L) или I (прописная i).
Абсолютные переменные
Обычно при описании переменных решение об их конкретном расположении в па-
мяти возлагается на компилятор. Однако в Паскале есть возможность явным об-
разом задавать адреса, по которым должны находиться переменные. Существует
два способа это сделать: либо записать адрес явным образом, либо дать указание
компилятору расположить переменную начиная с того же адреса, что и некоторая
другая переменная.
var имя : тип absolute адрес;
var имя : тип absolute имя_существующей_переменной;
Например:
var scr : byte absolute $B800:0;
x : integer;
b : word absolute x;
Здесь дается указание расположить переменную scr по адресу $B800:0. Адрес за-
дается в виде сегмента и смещения (см. с. 78). Этот способ может использоваться,
например, для непосредственного доступа к видеопамяти или порту и является не-
желательным, так как снижает переносимость программы.
Переменная b размещена «поверх» переменной х. Этот способ применяется для
того, чтобы иметь возможность рассматривать один и тот же участок памяти как
величины разных типов.
24
Глава 1. Основные понятия языка 25
Выражения
Выражение — это правило вычисления значения. В выражении участвуют опе-
ранды, объединенные знаками операций. Операндами выражения могут быть кон-
станты, переменные и вызовы функций. Операции выполняются в определенном
порядке в соответствии с приоритетами, как и в математике. Для изменения по-
рядка выполнения операций используются круглые скобки, уровень их вложенно-
сти практически не ограничен.
Результатом выражения всегда является значение определенного типа, который
определяется типами операндов. Величины, участвующие в выражении, должны
быть совместимых типов (см. с. 73). Например, допускается использовать в одном
выражении величины целых и вещественных типов. Результат такого выражения
будет вещественным.
Ниже приведены операции Паскаля, упорядоченные по убыванию приоритетов.
1. Унарная операция not, унарный минус –, взятие адреса @1.
2. Операции типа умножения: *, /, div, mod, and, shl, shr.
3. Операции типа сложения: +, –, or, xor.
4. Операции отношения: =, <, >, <>, <=, >=, in2.
Функции, используемые в выражении, вычисляются в первую очередь.
ВНИМАНИЕ
Константа и переменная являются частными случаями выражения.
Примеры выражений:
t + sin(x)/2 * x — результат имеет вещественный тип;
a <= b + 2 — результат имеет логический тип;
(x > 0) and (y < 0) — результат имеет логический тип.
Порядок вычисления первого выражения такой: сначала выполняется обращение
к стандартной функции sin и результат делится на 2, затем получившееся чис-
ло умножается на x, и только после этого выполняется сложение с переменной t.
Скобки в третьем выражении необходимы по той причине, что приоритет операций
отношения ниже, чем логической операции and.
Структура программы
Программа на Паскале состоит из заголовка, разделов описаний и раздела опера-
торов.
program имя; { заголовок – не обязателен }
разделы описаний
begin
раздел операторов
end. (* программа заканчивается точкой *)
1
Эта операция рассмотрена на с. 105.
2
in — операция проверки принадлежности множеству (с. 65).
25
26 Часть I. Основы программирования
26
Глава 1. Основные понятия языка 27
Оператор присваивания
Присваивание — это занесение значения в память. В общем виде оператор присваи-
вания записывается так:
переменная := выражение
Здесь символами := обозначена операция присваивания. Внутри знака операции
пробелы не допускаются.
Механизм выполнения оператора присваивания такой: вычисляется выражение, и его
результат заносится в память по адресу, который определяется именем переменной, на-
ходящейся слева от знака операции. Схематично это полезно представить себе так:
переменная ← выражение
Напомню, что константа и переменная являются частными случаями выражения.
Примеры операторов присваивания:
a := b + c / 2; b := a; a := b; x := 1; x := x + 0.5;
Обратите внимание: b := a и a := b — это совершенно разные действия!
ПРИМЕЧАНИЕ
Чтобы не перепутать, что чему присваивается, запомните мнемоническое правило:
присваивание — это передача данных «налево».
27
28 Часть I. Основы программирования
Процедуры ввода-вывода
Любая программа при вводе исходных данных и выводе результатов взаимодей-
ствует с внешними устройствами. Совокупность стандартных устройств ввода
и вывода, то есть клавиатуры и экрана дисплея, называется консолью. Обмен дан-
ными с консолью является частным случаем обмена с внешними устройствами, ко-
торый будет подробно рассмотрен в разделе «Текстовые файлы» (с. 69), а кратко —
в следующем разделе.
Ввод с клавиатуры
Для ввода с клавиатуры определены процедуры read и readln:
read(список);
readln[(список)];
В скобках указывается список имен переменных через запятую. Квадратные скобки
указывают на то, что список может отсутствовать. Например:
read(a, b, c); readln(y);
readln;
Вводить можно целые, вещественные, символьные и строковые величины. Вво-
димые значения должны разделяться любым количеством пробельных символов
(пробел, табуляция, перевод строки).
Ввод значения каждой переменной выполняется так:
1. Значение переменной выделяется как группа символов, расположенных между
разделителями.
2. Эти символы преобразуются во внутреннюю форму представления, соответ-
ствующую типу переменной.
3. Значение записывается в ячейку памяти, определяемую именем переменной.
Например, при вводе вещественного числа 3.78 в переменную типа real оно преоб-
разуется из четырех символов (3, «точка», 7 и 8) в шестибайтовое представление
в виде мантиссы и порядка.
Процедура readln после ввода всех значений выполняет переход на следующую
строку исходных данных. Иными словами, если в следующей части программы
есть ввод, он будет выполняться из следующей строки исходных данных. При ис-
пользовании процедуры read очередные исходные данные будут взяты из той же
строки. Процедура readln без параметров просто ожидает нажатия клавиши Enter.
Особенность ввода символов и строк состоит в том, что пробельные символы в них
ничем не отличаются от всех остальных, поэтому разделителями являться не могут.
Например, пусть определены переменные
var a : integer;
b : real;
d : char;
и в программе есть процедура ввода read(a, b, c). Допустим, переменной а надо
задать значение, равное 2, переменной b — 3,78, а в d записать символ #. Любой
28
Глава 1. Основные понятия языка 29
Вывод на экран
При выводе выполняется обратное преобразование: из внутреннего представления
в символы, выводимые на экран. Для этого в языке определены стандартные про-
цедуры write и writeln.
write(список);
writeln[(список)];
Как вы уже догадались, процедура write выводит указанные в списке величины на
экран, а writeln вдобавок к этому переводит курсор на следующую строку. Процеду-
ра writeln без параметров просто переводит курсор на следующую строку.
Выводить можно величины логических, целых, вещественных, символьного и строко-
вого типов. В списке могут присутствовать не только имена переменных, но и вы-
ражения, а также их частный случай — константы. Кроме того, для каждого выво-
димого значения можно задавать его формат, например:
writeln('Значение a = ', a:4, ' b = ', b:6:2, sin(a) + b);
Рассмотрим этот оператор подробно (переменные a и b описаны выше). В спи-
ске вывода пять элементов, разделенных запятыми. В начале записана строковая
29
30 Часть I. Основы программирования
СОВЕТ
При выводе всегда сопровождайте выводимые значения понятными комментариями
и указывайте форматы. Это экономит время осмысления результатов.
30
Глава 1. Основные понятия языка 31
1
А удовольствие — необходимое условие для написания хорошей программы!
31
32 Часть I. Основы программирования
СОВЕТ
При отладке программы бывает удобно выводить одну и ту же информацию и на экран,
и в текстовый файл.
32
Глава 2. Управляющие операторы языка
В теории программирования доказано, что программу для решения задачи любой
сложности можно составить только из трех структур, называемых следованием,
ветвлением и циклом. Следованием называется конструкция, представляющая со-
бой последовательное выполнение двух или более операторов (простых или состав-
ных). Ветвление задает выполнение либо одного, либо другого оператора в зависи-
мости от выполнения какого-либо условия. Цикл задает многократное выполнение
оператора (рис. 2.1).
33
34 Часть I. Основы программирования
Операторы ветвления
Операторы ветвления if и варианта case применяются для того, чтобы в зависи-
мости от конкретных значений исходных данных обеспечить выполнение разных
последовательностей операторов. Оператор if обеспечивает передачу управления
на одну из двух ветвей вычислений, а оператор case — на одну из произвольного
числа ветвей.
Условный оператор if
Условный оператор if используется для разветвления процесса вычислений на два
направления. Структурная схема оператора приведена на рис. 2.2. Формат оператора:
if выражение then оператор_1 [else оператор_2;]
Сначала вычисляется выражение, которое должно иметь логический тип. Если оно
имеет значение true, выполняется первый оператор, иначе — второй. После этого
управление передается на оператор, следующий за условным.
Операторы, входящие в состав условного оператора, могут быть простыми или со-
ставными. Составной оператор (блок) обрамляется ключевыми словами begin и end.
Блок применяют в том случае, когда по какой-либо ветви требуется выполнить не-
сколько операторов: ведь иначе компилятор не сможет понять, где заканчивается
ветвь и начинается следующая часть программы.
ВНИМАНИЕ
Отсутствие операторных скобок (ключевых слов begin и end) в ветви else компилятор
как ошибку не распознает!
34
Глава 2. Управляющие операторы языка 35
if a < b then
if a < c then m := a else m := c
else
if b < c then m := b else m := c; { 3 }
В примере 1 отсутствует ветвь else. Такая конструкция называется «пропуск опе-
ратора», поскольку присваивание значения переменной b либо выполняется, либо
пропускается в зависимости от выполнения условия a < 0, после чего управление
всегда передается следующему оператору.
Если требуется проверить несколько условий, их объединяют знаками логических
операций. Так, выражение в примере 2 будет истинно в том случае, если выполнит-
ся одновременно условие a < b и хотя бы одно из условий a > d и a = 0. Если опустить
скобки, в которые взята операция ИЛИ, будет выполнено сначала логическое И,
а потом — ИЛИ, поскольку его приоритет ниже. Скобки, в которые заключены
операции отношения, обязательны, потому что приоритет у логических операций
выше, чем у операций отношения. Поскольку по ветви else требуется выполнить
два оператора, они заключены в блок.
В примере 3 вычисляется наименьшее из значений трех переменных: a, b и с.
ВНИМАНИЕ
Частая ошибка при программировании условных операторов — неверная запись про-
верки на принадлежность диапазону. Например, условие 0 < x < 1 нельзя записать
непосредственно. Правильный способ: if(0 < x) and (x < 1) then…, поскольку факти-
чески требуется задать проверку выполнения одновременно двух условий: x > 0 и x < 1.
Вторая ошибка — отсутствие блока после else, если на самом деле по этой ветви требу-
ется выполнить более одного действия. Эта ошибка не может быть обнаружена компи-
лятором, поскольку является не синтаксической, а семантической, то есть смысловой.
⎧0, x < − 2⎫
⎪− x − 2, − 2 ≤ x < −1⎪
⎪⎪ ⎪⎪
y = ⎨ x, − 1 ≤ x < 1⎬ .
⎪ − x + 2, 1≤ x < 2⎪
⎪ ⎪
⎪⎩0, x ≥ 2 ⎪⎭
35
36 Часть I. Основы программирования
36
Глава 2. Управляющие операторы языка 37
37
38 Часть I. Основы программирования
…
константы_n : оператор_n;
[ else : оператор ]
end;
ВНИМАНИЕ
Если по какой-либо ветви требуется записать не один, а несколько операторов, они
заключаются в блок с помощью ключевых слов begin и end.
38
Глава 2. Управляющие операторы языка 39
СОВЕТ
Хотя наличие слова else не обязательно, рекомендуется всегда описывать случай,
когда значение выражения не совпадает ни с одной из констант. Это облегчает поиск
ошибок при отладке программы.
Операторы цикла
Операторы цикла используются для вычислений, повторяющихся многократно.
В Паскале имеется три вида циклов: цикл с предусловием while, цикл с постусло-
вием repeat и цикл с параметром for. Каждый из них состоит из определенной по-
следовательности операторов.
1
Приведено упрощенное описание. Более подробно работа с клавиатурой рассматривается
в разделе «Модуль Crt» на с. 93.
39
40 Часть I. Основы программирования
Начальные установки служат для того, чтобы до входа в цикл задать значения пере-
менных, которые в нем используются.
Проверка условия продолжения цикла (ромб «Выражение») выполняется на каждой
итерации либо до тела цикла (тогда говорят о цикле с предусловием, см. рис. 2.5, а),
либо после тела цикла (цикл с постусловием, см. рис. 2.5, б). Разница между ними
состоит в том, что тело цикла с постусловием всегда выполняется хотя бы один раз,
после чего проверяется, надо ли его выполнять еще раз. Проверка необходимости
выполнения цикла с предусловием делается до тела цикла, поэтому возможно, что
он не выполнится ни разу.
Параметром цикла называется переменная, которая используется при проверке
условия цикла и принудительно изменяется на каждой итерации, причем, как пра-
вило, на одну и ту же величину. Если параметр цикла целочисленный, он называется
40
Глава 2. Управляющие операторы языка 41
ВНИМАНИЕ
Если в теле цикла требуется выполнить более одного оператора, необходимо заключить
их в блок с помощью ключевых слов begin и end.
⎧t , x < 0⎫
⎪ ⎪
y = ⎨ tx, 0 ≤ x < 10 ⎬
⎪2t , x ≥ 10 ⎪⎭
⎩
для аргумента, изменяющегося в заданных пределах с заданным шагом.
Опишем алгоритм в словесной форме.
1. Ввести исходные данные.
2. Взять первое значение аргумента.
3. Определить, какому из интервалов оно принадлежит.
4. Вычислить значение функции по соответствующей формуле.
5. Вывести строку таблицы.
6. Перейти к следующему значению аргумента.
7. Если оно не превышает конечное значение, повторить шаги 3–6, иначе закон-
чить.
1
Рекуррентной называется формула, в которой новое значение переменной вычисляется
с использованием ее предыдущего значения.
41
42 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Еще один пример использования цикла while приведен на с. 47.
42
Глава 2. Управляющие операторы языка 43
в явном виде (шаг 2). Начинающие часто забывают про этот блок. В данном слу-
чае при отсутствии этого оператора переменной х будет присвоено значение 0, по-
скольку в Паскале глобальные переменные обнуляются автоматически.
Блок модификации параметра цикла представлен оператором, выполняющимся на
шаге 6. Для перехода к следующему значению аргумента текущее значение нара-
щивается на величину шага и заносится в ту же переменную. Начинающие часто
забывают про модификацию параметра, в результате чего программа «зациклива-
ется». Если с вами произошла такая неприятность, попробуйте для завершения
программы нажать клавиши Ctrl+Break, а впредь перед запуском программы для
параметра цикла проверяйте:
присвоено ли ему начальное значение;
изменяется ли он на каждой итерации цикла;
верно ли записано условие продолжения цикла.
1⎛ x ⎞
yn = y + ,
2 ⎜⎝ n − 1 yn − 1 ⎟⎠
43
44 Часть I. Основы программирования
44
Глава 2. Управляющие операторы языка 45
ВНИМАНИЕ
Если в теле цикла требуется выполнить более одного оператора, необходимо заключить
их в блок с помощью ключевых слов begin и end.
45
46 Часть I. Основы программирования
x2 x4 x6 x 2n
y =1+ + + + ... + + ...
2! 4! 6! 2n !
Этот ряд сходится при любых значениях аргумента. При увеличении номера n мо-
дуль члена ряда Cn стремится к нулю. При некотором n неравенство |Cn| ≥ ε пере-
стает выполняться, и вычисления прекращаются.
Общий алгоритм прост: задать начальное значение суммы ряда, а затем многократ-
но вычислять очередной член ряда и добавлять его к ранее найденной сумме, пока
абсолютная величина очередного члена ряда не станет меньше заданной точности.
До выполнения программы предсказать, сколько членов ряда потребуется просум-
мировать, невозможно. В цикле такого рода есть опасность, что он никогда не за-
вершится — как из-за возможных ошибок в вычислениях, так и из-за ограниченной
области сходимости ряда (данный ряд сходится на всей числовой оси, но существу-
ют ряды Тейлора, которые сходятся только для определенного интервала значений
аргумента). Кроме того, в данном случае значения функции при увеличении абсо-
лютной величины аргумента сильно возрастают и могут переполнить разрядную
сетку. Поэтому для надежности программы необходимо предусмотреть аварийный
выход из цикла с печатью предупреждающего сообщения по достижении некоторо-
го максимально допустимого количества итераций.
1
Есть и еще одна процедура, runerror, вызывающая завершение программы с ошибкой вре-
мени выполнения (run-time error), но обычно этого же результата добиваются совершенно
непроизвольно.
46
Глава 2. Управляющие операторы языка 47
Прямое вычисление члена ряда по приведенной выше общей формуле, когда х воз-
водится в степень, вычисляется факториал, а затем числитель делится на знаме-
натель, имеет два недостатка, которые делают этот способ непригодным. Первый
недостаток — большая погрешность вычислений. При возведении в степень и вы-
числении факториала можно получить очень большие числа, при делении которых
друг на друга произойдет потеря точности, поскольку количество значащих цифр,
хранимых в ячейке памяти, ограничено; кроме того, большие числа могут перепол-
нить разрядную сетку. Второй недостаток связан с эффективностью вычислений:
легко заметить, что при вычислении очередного члена ряда предыдущий уже из-
вестен, поэтому вычислять каждый член ряда «от печки» нерационально.
Для уменьшения количества выполняемых действий следует воспользоваться ре-
куррентной формулой получения последующего члена ряда через предыдущий:
Cn+1 = Cn × T, где T — некоторый множитель. Подставив в эту формулу Cn и Cn+1, по-
лучим выражение для вычисления Т:
Cn +1 2n ! x 2( n +1) x2 .
T = = 2n =
Cn x (2(n + 1))! (2n + 1)(2n + 2)
Текст программы с комментариями приведен в листинге 2.6.
47
48 Часть I. Основы программирования
48
Глава 3. Типы данных, определяемые
программистом
Информация, которую требуется обрабатывать в программе, имеет различную
структуру. Для ее адекватного представления используются типы данных, которые
программист определяет сам в разделе описания типов type. Типу дается произ-
вольное имя, которое можно затем использовать для описания программных объ-
ектов точно так же, как и стандартные имена типов.
type имя_типа = описание_типа
...
var имя_переменной : имя_типа
Можно задать тип и непосредственно при описании переменных:
var имя_переменной : описание_типа
В этом случае, как видите, имя типу не дается. Этот способ удобно применять, если
тип используется только в одном месте программы.
ПРИМЕЧАНИЕ
Компилятор не сопоставляет различные описания переменных. Это означает, что, если
две переменные описаны в разных операторах с помощью идентичных описаний, их
типы будут считаться различными.
49
50 Часть I. Основы программирования
Массивы
При использовании простых переменных, которые мы рассматривали до этого
момента, каждой области памяти, предназначенной для хранения какого-либо
50
Глава 3. Типы данных, определяемые программистом 51
значения, соответствует свое имя. Если с группой величин одинакового типа тре-
буется выполнять однообразные действия, им дают одно имя, а различают по по-
рядковому номеру (индексу). Это позволяет компактно записывать множество
операций с помощью циклов.
Конечная именованная последовательность однотипных величин называется мас-
сивом. Чтобы описать массив, надо определить, к какому типу принадлежат его эле-
менты и каким образом они пронумерованы (какого типа его индекс).
type имя_типа = array [тип_индекса] of тип_элемента
Здесь array и of — ключевые слова, тип индекса задается в квадратных скобках.
Примеры описания типа:
type mas = array [1 .. 10] of real;
Color = array [byte] of mas;
Active = array [Menu] of boolean;
В первом операторе описан тип массива из вещественных элементов, которые ну-
меруются от 1 до 10. Во втором операторе элементами массива являются масси-
вы типа mas, а нумеруются они в пределах, допустимых для типа byte, то есть от 0
до 255. Еще более экзотический пример — третий, где в качестве индекса исполь-
зовано имя типа из раздела «Перечисляемый тип данных» (с. 49), а сами элементы
могут принимать значения true или false.
Итак, тип элементов массива может быть любым, кроме файлового, тип индексов —
интервальным, перечисляемым или byte. Чаще всего для описания индекса исполь-
зуется интервальный тип данных.
ВНИМАНИЕ
Размещение массива в памяти происходит до выполнения программы, поэтому при
описании индекса можно применять только константы или константные выражения.
Переменные использовать нельзя!
Обычно при описании массива верхняя граница его индекса задается в виде имено-
ванной константы, например:
const n = 6;
type intmas = array [1 .. n] of integer;
После задания типа массива переменные этого типа описываются обычным обра-
зом:
var a, b : intmas;
Если требуемый тип массива используется только в одном месте программы, мож-
но описать тип прямо при определении переменных:
var a, b : array [1 .. n] of integer;
С массивами в целом можно выполнять только одну операцию: присваивание. При
этом массивы должны быть одного типа:
b := a;
51
52 Часть I. Основы программирования
52
Глава 3. Типы данных, определяемые программистом 53
53
54 Часть I. Основы программирования
Двумерные массивы
Элемент массива может быть любого типа, кроме файлового, следовательно, он мо-
жет быть и массивом, например:
const n = 4; m = 3;
type mas = array [1 .. n] of integer;
mas2 = array [1 .. m] of mas;
Более компактно это можно записать так:
type mas2 = array [1 .. m, 1 .. n] of integer;
Здесь описан тип массива, состоящего из m массивов, каждый из которых содержит
n целых чисел. Иными словами, это матрица из m строк и n столбцов (рис. 3.2). Обе
размерности массива должны быть константами или константными выражениями,
поскольку инструкции по выделению памяти формируются компилятором до вы-
полнения программы. Имя типа указывается при описании переменных, например:
var a, b : mas2;
В памяти двумерный массив располагается по строкам:
a11 a12 a13 a14 a21 a22 a23 a24 a31 a32 a33 a34
| – 1–я строка – | – 2–я строка – | – 3–я строка – |
54
Глава 3. Типы данных, определяемые программистом 55
ВНИМАНИЕ
Необходимо помнить, что компилятор воспринимает как номер строки первый индекс,
как бы он ни был обозначен в программе.
55
56 Часть I. Основы программирования
элементов массива (по строкам или по столбцам) роли не играет. Нахождение ко-
личества положительных элементов каждой строки требует просмотра матрицы по
строкам. Схема алгоритма приведена на рис. 3.3, программа — в листинге 3.4.
56
Глава 3. Типы данных, определяемые программистом 57
СОВЕТ
Записывайте операторы инициализации накапливаемых в цикле величин непосред-
ственно перед циклом, в котором они вычисляются.
57
58 Часть I. Основы программирования
Строки
Строки используются для хранения последовательностей символов. В Паскале су-
ществует три типа строк:
стандартные (string);
определяемые программистом на основе string;
строки в динамической памяти (рассматриваются на с. 101).
Строка типа string может содержать до 255 символов. Под каждый символ отво-
дится по одному байту, в котором хранится код символа. Еще один байт отводит-
ся под фактическую длину строки. Таким образом, в памяти под одну переменную
типа string всегда отводится 256 байт.
Для коротких строк использовать стандартную строку неэффективно, поэтому есть
возможность самостоятельно задавать максимальную длину строки. Например,
ниже описан собственный тип данных с именем str4:
type str4 = string [4]; { переменная такого типа занимает в памяти 5 байтов }
58
Глава 3. Типы данных, определяемые программистом 59
ВНИМАНИЕ
Длина строки должна быть константой или константным выражением, потому что
инструкции по выделению памяти формируются компилятором до начала выполнения
программы.
Операции
Строки можно присваивать друг другу. Если максимальная длина результирующей
строки меньше длины исходной, лишние символы справа отбрасываются:
s2 := 'shooshpanchik';
s1 := s2; { в s1 будут помещены символы "shoo" }
Строки можно склеивать (сцеплять) между собой с помощью операции конкатена-
ции, которая обозначается знаком +, например:
s1 := 'ком';
s2 := s1 + 'пот'; { результат — "компот" }
Строки можно сравнивать друг с другом с помощью операций отношения. При
сравнении строки рассматриваются посимвольно слева направо, при этом срав-
ниваются коды соответствующих пар символов. Строки равны, если они име-
ют одинаковую длину и посимвольно эквивалентны. В строках разной длины
59
60 Часть I. Основы программирования
60
Глава 3. Типы данных, определяемые программистом 61
Записи
В программах часто возникает необходимость логического объединения данных.
Например, база данных предприятия содержит для каждого сотрудника его фами-
лию, дату рождения, должность, оклад и т. д.; программа моделирования движения
поездов — пункты отправления и прибытия, время, количество вагонов и многое
другое. Однотипные данные, как вы уже знаете, организуются в массивы, а для
объединения разнотипных данных предназначен тип «запись». Он вводится с по-
мощью ключевого слова record1. Элементы записи называются полями.
type имя_типа = record
описание 1-го поля записи;
описание 2-го поля записи;
...
описание n-го поля записи;
end;
Поля записи могут быть любого типа, кроме файлового. Например, для каждого
товара на складе требуется хранить его наименование, цену и количество единиц:
type goods = record
name : string[20];
price : real;
number : integer;
end;
1
Обратите внимание, что запись наряду с массивом является типом, определяемым програм-
мистом, а не стандартным типом языка.
61
62 Часть I. Основы программирования
62
Глава 3. Типы данных, определяемые программистом 63
63
64 Часть I. Основы программирования
Поле варианта должно быть порядкового типа. Часто его делают перечисляемым.
Например, пусть требуется хранить в памяти характеристики геометрических фи-
гур — прямоугольника, треугольника и круга. Для каждой из них задается местопо-
ложение (это будет общая часть записи), а также длины сторон для прямоугольни-
ка, координаты двух вершин для треугольника и радиус для круга:
type figure = (rect, triangle, circle);
shape = record
x, y : real;
case kind : figure of
rect : (height, width : real);
triangle : (x2, y2, x3, y3 : real);
circle : (radius : real);
end;
Можно не хранить в записи поле варианта, в этом случае его имя не указывается:
type shape = record
x, y : real;
case integer of
0 : (height, width : real);
1 : (x2, y2, x3, y3 : real);
2 : (radius : real);
end;
Доступ к вариантным полям остается на совести программиста, то есть ничто не
мешает записать в вариантную часть одни поля, а обратиться к ним через другие:
type my_string = record
case integer of
0 : (s : string [10]);
1 : (x : array [1 .. 11] of byte);
2 : (z : record
len : byte; c : array [1 .. 10] of char;
end);
end;
var a : my_string;
...
a.s := 'shooshpanchiki';
writeln(length(a.s), a.x[1], a.z.len );
В последней строке приведено три способа доступа к одному и тому же байту, хра-
нящему длину строки s.
Множества
Множественный тип данных в языке Паскаль соответствует математическому
представлению о множествах: это ограниченная совокупность различных элемен-
тов. Множество создается на основе элементов базового типа — это может быть
перечисляемый тип, интервальный или byte. В множестве не может быть более
256 элементов, а их порядковые номера должны лежать в пределах от 0 до 255.
64
Глава 3. Типы данных, определяемые программистом 65
65
66 Часть I. Основы программирования
66
Глава 3. Типы данных, определяемые программистом 67
begin
randomize;
cast := [];
for i := 1 to 5 do begin
write ( i, '–й заезд: ');
lap := [];
for j := 1 to 6 do begin
repeat
n := random(30) + 101;
until not (n in lap) and not (n in cast);
lap := lap + [n];
write (n:4);
end;
cast := cast + lap;
writeln;
end;
end.
Файлы
Файловые типы данных введены в язык для работы с внешними устройствами —
файлами на диске, портами, принтерами и т. д. Передача данных с внешнего устрой-
ства в оперативную память называется чтением, или вводом, обратный процесс —
записью, или выводом.
Файловые типы языка Паскаль бывают стандартные и определяемые программи-
стом. Стандартными являются текстовый файл (text) и бестиповой файл (file).
Они описываются в программе, например, так:
var ft : text;
fb : file;
Программист может определить файл, состоящий из элементов определенного
типа. Такой файл называется компонентным, или типизированным:
var fc : file of <тип_компонентов>;
Компоненты могут быть любого типа, кроме файлового. Любой файл, в отличие от
массива и записи, может содержать неограниченное количество элементов.
Текстовые файлы предназначены для хранения информации в виде строк симво-
лов. При выводе в текстовый файл данные преобразуются из внутренней формы
представления в символьную, понятную человеку, при вводе выполняется обрат-
ное преобразование.
Бестиповые и компонентные файлы хранят данные в том же виде, в котором они
представлены в оперативной памяти, то есть при обмене с файлом происходит по-
битовое копирование информации.
Доступ к файлам может быть последовательным, когда очередной элемент можно
прочитать (записать) только после аналогичной операции с предыдущим элемен-
том, и прямым, при котором выполняется чтение (запись) произвольного элемента
по заданному адресу. Текстовые файлы позволяют выполнять только последова-
тельный доступ, в бестиповых и компонентных можно использовать оба метода.
67
68 Часть I. Основы программирования
Прямой доступ возможен благодаря тому, что данные в этих файлах условно раз-
делены на блоки одинакового размера, и перед операцией обмена выполняется
установка текущей позиции файла на заданный блок. Прямой доступ в сочетании
с отсутствием преобразований обеспечивает высокую скорость получения инфор-
мации.
Для понимания сути работы с файлами полезно разделить их по признаку наличия
или отсутствия преобразования информации при выполнении чтения-записи и по
способу доступа (табл. 3.2).
68
Глава 3. Типы данных, определяемые программистом 69
В Паскале есть подпрограммы, применяемые для файлов любого типа, а также под-
программы для работы только с определенными типами файлов. Подпрограммы
для работы со всеми типами файлов: assign, close, erase, rename, reset, rewrite, eof
и IOresult. Их описание приведено в приложении 2.
Текстовые файлы
Текстовый файл представляет собой последовательность строк символов перемен-
ной длины. Каждая строка заканчивается символами перевода строки и возврата
каретки (их коды — 13 и 10). Эти символы вставляются в физический файл при
нажатии клавиши Enter. При чтении файла эти символы не вводятся в переменные
в программе, а воспринимаются как разделитель.
Текстовый файл можно открыть не только для чтения или записи с помощью про-
цедур reset и rewrite, но и для добавления информации в конец. Для этого служит
процедура append. Для чтения из текстового файла применяются процедуры read(f,
список) и readln(f, [список]). Они отличаются от известных вам процедур ввода
с клавиатуры (см. с. 28) только наличием первого параметра — имени логического
файла. Это неудивительно, поскольку консольный ввод-вывод является частным
случаем обмена с текстовым файлом.
Для ввода с клавиатуры определено стандартное имя файла INPUT, а для вывода на
экран — OUTPUT. Эти файлы отличаются от обычных тем, что их нельзя открывать
и закрывать, поскольку это делается автоматически, и их имена можно не указы-
вать при обращении к процедурам ввода и вывода.
Процедуры записи в текстовый файл — write(f, список) и writeln(f, [список]). При
записи в текстовый файл происходит преобразование из внутренней формы пред-
ставления выводимых величин в символьные строки. Описание процедур приве-
дено на с. 29. Чтение и запись выполняются последовательно, то есть записать или
считать очередной символ можно только после предыдущего.
В Паскале есть несколько стандартных подпрограмм, которые предназначены толь-
ко для работы с текстовыми файлами: flush, settextbuf, seekEof и seekEoln. Все эти
подпрограммы описаны в приложении 2.
Бестиповые файлы
Бестиповые файлы предназначены для хранения участков оперативной памяти на
внешних носителях. После описания файловой переменной
var имя : file;
ее требуется связать с физическим файлом с помощью процедуры assign. Чтение
и запись производится через буфер «порциями», равными размеру буфера. Размер
буфера, отличающийся от стандартного (128 байт), можно задать с помощью вто-
рого параметра процедур reset и rewrite при открытии файла:
reset(var f : file; bufsize : word)
rewrite(var f : file; bufsize : word)
Размер буфера должен находиться в пределах от 1 байта до 64 Кбайт.
69
70 Часть I. Основы программирования
70
Глава 3. Типы данных, определяемые программистом 71
Компонентные файлы
Компонентные файлы применяются для хранения однотипных элементов в их вну-
тренней форме представления. Тип компонентов задается после ключевых слов file of.
var имя : file of тип_компонентов;
Компоненты могут быть любого типа, кроме файлового, например вещественным
числом, массивом, множеством, строкой, записью или массивом записей. В опера-
циях ввода-вывода могут участвовать только величины того же типа, что и компо-
ненты файла, например:
type mas = array [1 .. 100] of real;
var a, b : mas;
f : file of mas;
begin
assign(f, 'some_file.dat'); rewrite(f);
...
write(f, a, b);
close(f)
end.
Обратите внимание, что компонентом этого файла является массив целиком. В та-
кой файл нельзя записать отдельный элемент массива или простую переменную
вещественного типа. За одну операцию записывается или считывается столько
компонентов, сколько перечислено в процедурах write или read.
Пример. Из текстового файла прочитать пары вещественных чисел, считая первое
вещественной, а второе — мнимой составляющей комплексного числа, и записать их
в файл комплексных чисел (листинг 3.10).
71
72 Часть I. Основы программирования
Прямой доступ
При последовательном доступе чтение или запись очередного элемента файла воз-
можны только после аналогичной операции с предыдущим элементом. Например,
чтобы получить n-й элемент файла, требуется считать n – 1 элементов. Бестипо-
вые и компонентные файлы состоят из блоков одинакового размера. В бестиповом
файле размер блока равен длине буфера, а в компонентном — длине компонента.
Это позволяет применить к таким файлам прямой доступ, при котором операции
выполняются с заданным блоком. Прямой доступ применяется только для физиче-
ских файлов, расположенных на дисках.
С помощью стандартной процедуры seek производится установка текущей позиции
в файле на начало заданного блока, и следующая операция чтения-записи выпол-
няется, начиная с этой позиции. Первый блок файла имеет номер 0.
Ниже описаны стандартные подпрограммы для реализации прямого доступа:
filepos(var f) : longint
Функция возвращает текущую позицию в файле f. Для только что открытого фай-
ла это будет 0. После чтения или записи первого блока текущая позиция равна 1.
После прочтения последней записи функция возвратит количество блоков в файле:
filesize(var f) : longint
Функция возвращает количество блоков в открытом файле f:
seek(var f; n: longint)
Процедура выполняет установку текущей позиции в файле (позиционирование).
В параметре n задается номер блока, к которому будет выполняться обращение.
Блоки нумеруются с нуля. Например, чтобы работать с четвертым блоком, необхо-
димо задать значение n, равное 3. Процедура seek работает с открытыми файлами.
truncate(var f)
Процедура устанавливает в текущей позиции признак конца файла и удаляет все
последующие блоки.
Пример. Программа выводит на экран заданную по номеру запись из файла, сфор-
мированного в листинге 3.9.
72
Глава 3. Типы данных, определяемые программистом 73
f : file;
i, k : integer;
filename : string;
begin
{$I–}
writeln('Введите имя входного файла'); readln(filename);
assign(f, filename);
reset(f, sizeof(real) * 4);
if IOResult <> 0 then begin
writeln('Файл ', filename, ' не найден'); exit end;
{$I+}
while true do begin
writeln('Введите номер записи или –1 для окончания');
readln(k);
if (k > filesize(f)) or (k < 0) then begin
writeln('Такой записи в файле нет',); exit end;
seek(f, k);
blockread(f, buf, 1);
for i:= 1 to 4 do write(buf[i]:6:1);
end;
close(f);
end.
Таким же образом можно изменять заданную запись в файле. Файл при этом может
быть открыт как для чтения, так и для записи. Попытка чтения-записи несуществу-
ющего блока приводит к ошибке времени выполнения.
ПРИМЕЧАНИЕ
Стандартный модуль Dos содержит подпрограммы, с помощью которых можно об-
ращаться к некоторым функциям операционной системы, например получать и уста-
навливать время создания и атрибуты файла, выполнять поиск в заданных каталогах
по имени файла, получать объем свободного дискового пространства и т. д. Восполь-
зоваться этими подпрограммами можно, подключив к своей программе модуль Dos
в разделе uses. Модуль Dos описан в приложении 2 на с. 362.
Совместимость типов
Операнды в выражениях должны быть совместимых типов. Совместимость типов
величин, участвующих в каждой операции, достигается при выполнении по край-
ней мере одного из следующих условий.
Оба типа одинаковые.
Оба типа вещественные.
Оба типа целочисленные.
Один тип является поддиапазоном другого.
Оба типа являются отрезками одного и того же основного типа.
Оба типа являются множественными типами с совместимыми базовыми ти-
пами.
73
74 Часть I. Основы программирования
Один тип является строковым типом, другой — строковым типом или типом
pchar.
Один тип — pointer, другой — любой тип указателя.
Один тип — p c h a r , другой — символьный массив с нулевой базой вида
array [0 .. X] of char (только при разрешении расширенного синтаксиса дирек-
тивой {$X+}).
Оба типа являются указателями идентичных типов (только при разрешении
расширенного синтаксиса директивой {$X+}).
Оба типа являются процедурными с идентичными типами результатов, одина-
ковым числом параметров и соответствием между параметрами.
Совместимость по присваиванию
Этот вид совместимости требуется при присваивании значений, например в опера-
торе присваивания или при передаче значений в подпрограмму.
Значение типа T1 является совместимым по присваиванию с типом T2 (то есть до-
пустим оператор T1 := T2), если выполняется одно из следующих условий.
T1 и T2 — тождественные типы (кроме файловых или типов, содержащих элемен-
ты файлового типа).
T1 и T2 — совместимые порядковые типы, при этом значения типа T2 попадают
в диапазон возможных значений T1.
T1 и T2 — вещественные типы, при этом значения типа T2 попадают в диапазон
возможных значений T1.
T1 — вещественный тип, а T2 — целочисленный.
T1 и T2 — строковые типы.
T1 — строковый тип, а T2 — символьный (char).
T1 и T2 — совместимые множественные типы, при этом все значения типа T2 по-
падают в диапазон возможных значений T1.
T1 и T2 — совместимые типы указателей.
T1 — тип pchar, а T2 — строковая константа (только при разрешении расширенного
синтаксиса директивой {$X+})1.
T1 — тип pchar, а T2 — символьный массив с нулевой базой вида array [0 .. n]
of char (только при разрешении расширенного синтаксиса директивой {$X+}).
T1 и T2 — совместимые процедурные типы.
T1 представляет собой процедурный тип, а T2 — процедура или функция с иден-
тичным типом результата и соответствующими параметрами.
На этапе компиляции и выполнения выдается сообщение об ошибке, если совме-
стимость по присваиванию необходима, а ни одно из условий предыдущего списка
не выполнено.
1
Директивы компилятора приведены в приложении 3.
74
Глава 4. Модульное программирование
С увеличением объема программы становится невозможным удерживать в памяти
все детали. Естественным способом борьбы со сложностью любой задачи является
ее разбиение на части. В Паскале задача может быть разделена на более простые
и понятные фрагменты — подпрограммы, после чего программу можно рассматри-
вать в более укрупненном виде — на уровне взаимодействия подпрограмм.
Использование подпрограмм является первым шагом к повышению степени аб-
стракции программы и ведет к упрощению ее структуры. Разделение программы на
подпрограммы позволяет также избежать избыточности кода, поскольку подпро-
грамму записывают один раз, а вызывать ее на выполнение можно многократно из
разных точек программы.
Следующим шагом в повышении уровня абстракции программы является группи-
ровка подпрограмм и связанных с ними данных в отдельные файлы (модули), ком-
пилируемые раздельно. Разбиение на модули уменьшает время перекомпиляции и
облегчает процесс отладки, скрывая несущественные детали за интерфейсом моду-
ля и позволяя отлаживать программу по частям (при этом, возможно, разным про-
граммистам). Интерфейсом модуля являются заголовки всех подпрограмм и опи-
сания доступных извне типов, переменных и констант.
Подпрограммы
Вы уже неоднократно пользовались стандартными подпрограммами Паскаля, те-
перь пришло время научиться писать собственные. Подпрограммы нужны для
того, чтобы упростить структуру программы и облегчить ее отладку. В виде под-
программ оформляются логически законченные части программы.
Подпрограмма — это фрагмент кода, к которому можно обратиться по имени. Она
описывается один раз, а вызываться может столько раз, сколько необходимо. Одна
и та же подпрограмма может обрабатывать различные данные, переданные ей в ка-
честве аргументов.
В Паскале имеется два вида подпрограмм: процедуры и функции. Они определяют-
ся в соответствующих разделах описания (до начала блока исполняемых операто-
ров) и имеют незначительные различия в синтаксисе и правилах вызова.
Само по себе описание не приводит к выполнению подпрограммы. Чтобы подпро-
грамма выполнилась, ее надо вызвать. Вызов записывается в том месте програм-
мы, где требуется получить результаты работы подпрограммы. Подпрограмма
75
76 Часть I. Основы программирования
Процедуры
Структура процедуры аналогична структуре основной программы:
procedure имя [(список параметров)]; { заголовок }
разделы описаний
begin
раздел операторов
end;
Квадратные скобки в данном случае не являются элементом синтаксиса, а означа-
ют, что список параметров может отсутствовать. Рассмотрим простой пример.
Пример. Найти разность средних арифметических значений двух вещественных мас-
сивов из 10 элементов.
Как видно из условия, для двух массивов требуется найти одну и ту же величину —
среднее арифметическое. Следовательно, логичным будет оформить его нахождение
в виде подпрограммы, которая сможет работать с разными массивами (листинг 4.1).
76
Глава 4. Модульное программирование 77
average(a, av_a); { 3 }
average(b, av_b); { 4 }
dif := av_a – av_b;
writeln('Разность значений ', dif:6:2);
end.
Описание процедуры average расположено в строках с {1} по {2}. В строках {3} и {4}
эта процедура вызывается сначала для обработки массива а, затем — массива b.
Массивы передаются в качестве аргументов. Результат вычислений возвращается
в главную программу через второй параметр процедуры.
Пока от вас не требуется понимать все детали синтаксиса, главное — уловить общий
смысл использования подпрограмм. На таком простом алгоритме мы, естественно,
не получили выигрыша в длине программы, но этот вариант по сравнению с вы-
числением двух средних арифметических «в лоб» имеет ряд важных преимуществ.
Во-первых, мы получили более простую главную программу. Во-вторых, у нас те-
перь есть подпрограмма, с помощью которой можно вычислить среднее арифме-
тическое элементов любого вещественного массива из n элементов. Мы можем ис-
пользовать ее многократно и в этой, и в других программах, причем для этого нам не
требуется помнить, как она работает, — достаточно взглянуть на ее заголовок. Мы
можем оформить подпрограмму в виде модуля и передать коллегам. И наконец, мы
можем вносить в эту подпрограмму изменения и дополнения, будучи уверенными,
что это не отразится на главной программе.
Функции
Описание функции отличается от описания процедуры незначительно:
function имя [(список параметров)] : тип; { заголовок }
разделы описаний
begin
раздел операторов
имя := выражение;
end;
Квадратные скобки в данном случае не являются элементом синтаксиса, а означа-
ют, что список параметров может отсутствовать. Функция вычисляет одно значе-
ние, которое передается через ее имя. Следовательно, в заголовке должен быть опи-
сан тип этого значения, а в теле функции — оператор, присваивающий вычисленное
значение ее имени. Он не обязательно должен находиться в конце функции. Более
того, таких операторов может быть несколько — это определяется алгоритмом. Рас-
смотрим пример применения функции для программы, приведенной в предыду-
щем разделе.
Пример. Найти разность средних арифметических значений двух вещественных мас-
сивов из 10 элементов (листинг 4.2).
77
78 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Ничто не мешает вычислять в функции не одно значение, а несколько. В этом случае
одно, «главное», значение передается через имя функции, а остальные — через список
параметров по адресу (о способах передачи параметров будет рассказано далее). Но
в таком случае чаще всего лучше использовать не функцию, а процедуру. И наоборот:
если подпрограмма формирует только одно значение, предпочтительно оформить ее
в виде функции.
78
Глава 4. Модульное программирование 79
ПРИМЕЧАНИЕ
Если переменная внутри подпрограммы определена в разделе описания констант,
память под нее выделяется не в сегменте стека, а в сегменте данных, и начальное
значение ей присваивается один раз до начала работы программы, а не при входе
в подпрограмму. Время жизни такой переменной — вся программа, то есть значение
этой переменной сохраняется между вызовами подпрограммы. Область действия
переменной — подпрограмма, в которой она описана, то есть вне подпрограммы к этой
переменной обратиться нельзя.
79
80 Часть I. Основы программирования
ВНИМАНИЕ
Подпрограмму надо писать таким образом, чтобы вся необходимая для ее использо-
вания информация содержалась в ее заголовке.
80
Глава 4. Модульное программирование 81
Имя параметра может быть произвольным. Параметр х можно представить себе как
локальную переменную, которая получает свое значение из главной программы при
вызове подпрограммы. В подпрограмму передается копия значения аргумента.
Механизм передачи следующий: из ячейки памяти, в которой хранится перемен-
ная, передаваемая в подпрограмму, берется ее значение и копируется в область
сегмента стека, называемую областью параметров. Подпрограмма работает с этой
копией, следовательно, доступа к ячейке, где хранится сама переменная, не имеет.
По завершении работы подпрограммы стек освобождается. Такой способ называ-
ется передачей по значению. Ясно, что им можно пользоваться только для величин,
которые не должны измениться после выполнения подпрограммы, то есть для ее
исходных данных.
При вызове подпрограммы на месте параметра, передаваемого по значению, может
находиться выражение (а также, конечно, его частные случаи — переменная или
константа). Тип выражения должен быть совместим по присваиванию с типом па-
раметра, то есть выражение должно быть таким, чтобы его можно было присвоить
параметру по правилам Паскаля (о совместимости типов см. с. 73).
Например, если в вызывающей программе описаны переменные
var x : integer; c : byte; y : longint;
то следующие вызовы подпрограммы Р, заголовок которой описан выше, будут син-
таксически правильными:
P(x); P(c); P(y); P(200); P(x div 4 + 1);
ПРИМЕЧАНИЕ
Если передаваемое в подпрограмму целое значение не соответствует допустимому для
типа параметра диапазону, оно усекается. Для вещественных значений в аналогичном
случае возникает ошибка переполнения.
Параметры-переменные
Признаком параметра-переменной является ключевое слово var перед описанием
параметра:
var имя : тип;
Например, параметр-переменная целого типа в процедуре Р записывается так:
procedure P(var x : integer);
При вызове подпрограммы в область параметров копируется не значение перемен-
ной, а ее адрес, и подпрограмма через него имеет доступ к ячейке, в которой хранит-
ся переменная. Этот способ передачи параметров называется передачей по адресу.
81
82 Часть I. Основы программирования
Параметры-константы
Параметр-константу можно узнать по ключевому слову const перед описанием па-
раметра. Оно говорит о том, что в пределах подпрограммы данный параметр изме-
нить невозможно:
const имя : тип;
При вызове подпрограммы на месте параметра-константы, как и в случае параметра-
значения, может быть записано выражение, тип которого совместим по присваи-
ванию с типом параметра. Однако компилятор при передаче параметров-констант
формирует более эффективный код, чем для параметров-значений. Фактически
82
Глава 4. Модульное программирование 83
СОВЕТ
В списке параметров записывайте сначала все входные параметры, затем — все вы-
ходные. Давайте параметрам имена, по которым можно получить представление об
их назначении.
Нетипизированные параметры
Как можно догадаться из названия, при описании нетипизированных параметров
не указывается тип. Передаются они всегда по адресу — либо как константы, либо
как переменные, например:
procedure P(const a, b; var y);
Казалось бы, все прекрасно, однако есть одно «но»: передать-то их можно, а вот де-
лать с ними в подпрограмме что-либо до тех пор, пока они не приведены к какому-
либо определенному типу, все равно нельзя! Более того, раз тип параметров не
указан, компилятор не может проверить допустимость действий, выполняемых
с ними в подпрограмме, и ответственность за эти действия ложится на плечи про-
граммиста.
Впрочем, в этом нет ничего удивительного: ведь вся жизнь устроена так, что чем
больше свободы действий, тем больше ответственность! Но довольно о грустном,
давайте лучше рассмотрим пример применения этих параметров (листинг 4.4).
83
84 Часть I. Основы программирования
84
Глава 4. Модульное программирование 85
85
86 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Процедурные типы применяются не только для передачи имен подпрограмм в под-
программу. Можно описать переменную такого типа и присваивать ей имя конкретной
подпрограммы соответствующего типа или значение другой переменной того же типа.
Это удобно для организации вызова различных подпрограмм из одного и того же места
программы в зависимости от условий. Пример использования приведен на с. 109.
2x
q= , r = cos x − 0, 2 x
1 − sin 2 x
на интервале [a, b] с заданным количеством его разбиений (листинг 4.6).
86
Глава 4. Модульное программирование 87
87
88 Часть I. Основы программирования
Рекурсивные подпрограммы
Рекурсивной называется подпрограмма, в которой содержится обращение к самой
себе. Такая рекурсия называется прямой. Есть также косвенная рекурсия, когда две
или более подпрограммы вызывают друг друга.
При обращении подпрограммы к самой себе происходит то же самое, что и при
обращении к любой другой функции или процедуре: в стек записывается адрес
возврата, резервируется место под локальные переменные, происходит передача
параметров, после чего управление передается первому исполняемому оператору
подпрограммы. При повторном вызове этот процесс повторяется. Для завершения
вычислений каждая рекурсивная подпрограмма должна содержать хотя бы одну
нерекурсивную ветвь, заканчивающуюся возвратом в вызывающую программу.
При завершении подпрограммы область ее локальных переменных освобождается,
а управление передается на оператор, следующий за рекурсивным вызовом.
Простой пример рекурсивной функции — вычисление факториала (это не означает,
что факториал следует вычислять именно так). Чтобы получить факториал числа n,
требуется умножить на n факториал (n – 1)!. Известно также, что 0! = 1 и 1! = 1.
function fact(n : byte) : longint;
begin
if (n = 0) or (n = 1) then fact := 1
else fact := n * fact(n – 1);
end;
Рассмотрим, что происходит при вызове этой функции при n = 3. В стеке отводит-
ся место под параметр n, ему присваивается значение 3, и начинается выполнение
функции. Условие в операторе if ложно, поэтому управление передается на ветвь
else. Для вычисления выражения n * fact(n – 1) требуется повторно вызвать функ-
цию fact. Для этого в стеке отводится новое место под параметр n, ему присваива-
ется значение 2, и выполнение функции начинается сначала. В третий раз функция
вызывается со значением параметра, равным 1, и вот тут-то становится истинным
88
Глава 4. Модульное программирование 89
Модули
Модуль — это подключаемая к программе библиотека ресурсов. Он может содер-
жать описания типов, констант, переменных и подпрограмм. В модуль обычно
объединяют связанные между собой ресурсы: например, в составе оболочки есть
модуль Graph для работы с экраном в графическом режиме. Модули применяются
как библиотеки, которые могут использоваться различными программами, и для
разбиения сложной программы на составные части.
Чтобы использовать модуль, достаточно знать только его интерфейс: детали реали-
зации модуля скрыты от его пользователя. Это позволяет успешно создавать про-
граммы большого объема, поскольку мозг человека1 может хранить одновременно
1
А тем более программиста.
89
90 Часть I. Основы программирования
Описание модулей
Исходный текст каждого модуля хранится в отдельном файле с расширением .pas.
Модуль состоит из секций (разделов). Общая структура модуля:
unit имя; { заголовок модуля }
interface { ------------- интерфейсная секция модуля }
{ описание глобальных элементов модуля (видимых извне) }
implementation { --------------- секция реализации модуля }
{ описание локальных (внутренних) элементов модуля }
begin { ------------------- секция инициализации }
{ может отсутствовать }
end.
ВНИМАНИЕ
Имя файла, в котором хранится модуль, должно совпадать с именем, заданным после
ключевого слова unit.
Модуль может использовать другие модули, для этого их надо перечислить в опе-
раторе uses, который может находиться только непосредственно после ключевых
слов interface или implementation. Если модули подключаются к интерфейсной ча-
сти, все константы и типы данных, описанные в интерфейсной секции этих моду-
лей, могут использоваться в любом описании в интерфейсной части данного моду-
ля. Если модули подключаются к части реализации, все описания из этих модулей
могут использоваться только в секции реализации.
В интерфейсной секции модуля определяют константы, типы данных, переменные,
а также заголовки процедур и функций. Полностью же подпрограммы описыва-
ются в секции реализации, скрытой от пользователя модуля. Это естественно, по-
скольку для применения подпрограммы требуется знать только информацию, кото-
рая содержится в ее заголовке (при условии, что подпрограмма написана грамотно).
90
Глава 4. Модульное программирование 91
ПРИМЕЧАНИЕ
Кроме того, откомпилированный модуль может находиться в том же каталоге, что и ис-
пользующие его программы, а также в библиотеке исполняющей системы. Поместить
модуль в библиотеку исполняющей системы можно с помощью утилиты tpumover.
exe, которая входит в состав системы программирования. Этот способ применяется
для часто используемых и хорошо отлаженных модулей.
91
92 Часть I. Основы программирования
Использование модулей
Чтобы использовать в программе величины, описанные в интерфейсной части
модуля, имя модуля следует указать в разделе uses (напомню, что он должен рас-
полагаться перед всеми остальными разделами). Можно записать несколько имен
модулей через запятую, например:
program example;
uses Average, Graph, Crt;
После этого все описания, расположенные в интерфейсных секциях модулей, ста-
новятся известными в программе, и ими можно пользоваться точно так же, как
и величинами, определенными в ней непосредственно. Поиск модулей выполняет-
ся сначала в библиотеке исполняющей системы, затем в текущем каталоге, а после
этого — в каталогах, заданных в диалоговом окне OptionsDirectories.
Если в программе описана величина с тем же именем, что и в модуле, для обраще-
ния к величине из модуля требуется перед ее именем указать через точку имя моду-
ля. Например, пусть в программе определена процедура Circle, а в разделе uses упо-
минается модуль Graph, который также содержит процедуру с таким именем. Для
обращения к процедуре Circle из модуля Graph следует записать ее имя в виде Graph.
Circle. Имя модуля с точкой может предшествовать любому идентификатору: кон-
станте, типу данных, переменной или подпрограмме.
ПРИМЕЧАНИЕ
К любой программе автоматически подключается стандартный модуль System, ко-
торый содержит библиотеку исполняющей системы Паскаля.
92
Глава 4. Модульное программирование 93
Модуль System
Модуль System содержит базовые средства языка, которые поддерживают ввод-
вывод, работу со строками, операции с плавающей точкой и динамическое распре-
деление памяти. Этот модуль автоматически используется во всех программах, его
не требуется указывать в операторе uses. Он содержит все стандартные и встро-
енные процедуры, функции, константы и переменные Паскаля. Полное описание
модуля приведено в приложении 2 (с. 392).
Модуль Crt
Модуль Crt предназначен для организации эффективной работы с экраном, кла-
виатурой и встроенным динамиком. Программы, не использующие модуль Crt,
выполняют вывод на экран с помощью средств операционной системы DOS, что
является весьма медленным способом. При подключении модуля Crt выводимая
информация посылается в базовую систему ввода-вывода (ВIОS) или непосред-
ственно в видеопамять. При этом ввод-вывод выполняется гораздо быстрее, кроме
того, появляется возможность управлять цветом и размещением на экране. Полное
описание модуля приведено в приложении 2 (с. 357).
ПРИМЕЧАНИЕ
Модуль Crt предназначен для использования только на IBM PC-совместимых ком-
пьютерах.
93
94 Часть I. Основы программирования
Работа с экраном
Текущие цвета символа и фона задаются с помощью процедур TextColor
и TextBackGround и действуют на следующие за ними процедуры вывода. Текущие
атрибуты хранятся в младшем байте переменной TextAttr, ее значение можно уста-
новить и непосредственно. Вывод выполняется в текущую позицию курсора. Для
ее изменения служит процедура GotoXY.
Окно определяется с помощью процедуры Window. Оно задается координатами ле-
вого верхнего и правого нижнего угла. После определения окна позиционирование
курсора выполняется относительно него. Если окно не задано, им считается весь
экран.
Очистка текущего окна выполняется с помощью процедуры ClrScr, которая запол-
няет его пробелами с текущим цветом фона и устанавливает курсор в левый верх-
ний угол.
Пример. Программа «Угадай число» (листинг 4.9).
94
Глава 4. Модульное программирование 95
95
96 Часть I. Основы программирования
Работа с клавиатурой
Стандартные процедуры read и readln воспринимают только алфавитно-цифровые
символы и конец строки (символы с кодами #13 и #10). Модуль Crt позволяет рабо-
тать с управляющими клавишами и комбинациями клавиш.
Нажатие каждой клавиши преобразуется либо в ее ASCII-код, либо в так называе-
мый расширенный код (скан-код) и записывается в буфер клавиатуры, из которого
затем и выбирается процедурами ввода. Под каждый код отводится два байта. Если
нажатие клавиш соответствует символу из набора ASCII, в первый байт заносит-
ся код символа. Если нажата, например, клавиша управления курсором, функцио-
нальная клавиша или комбинация клавиш с Ctrl или Alt, то первый байт равен нулю,
а во втором находится расширенный код, соответствующий этой комбинации. Рас-
ширенные коды приведены в приложении 6 на с. 443.
Для работы с клавиатурой модуль Crt содержит функции ReadKey и KeyPressed.
Функция ReadKey : сhar считывает символ с клавиатуры, но не отображает его на
экране. При нажатии специальной клавиши или комбинации функция возвращает
символ с кодом 0, а при повторном вызове — расширенный код клавиши.
Функция KeyPressed : boolean возвращает значение truе, если на клавиатуре нажата
клавиша, и false в противном случае. Символ (или символы) остаются в буфере
клавиатуры.
Пример работы с расширенными кодами приведен на с. 39.
96
Глава 4. Модульное программирование 97
Модуль Graph
Модуль обеспечивает работу с экраном в графическом режиме. Полное описание
ресурсов, входящих в модуль Graph, приведено в приложении 2 (с. 370).
Экран в графическом режиме представляется в виде совокупности точек — пик-
селов (pixel, сокращение от picture element). Цвет каждого пиксела можно задавать
отдельно. Начало координат находится в левом верхнем углу экрана и имеет коор-
динаты (0, 0). Количество точек по горизонтали и вертикали (разрешение экрана)
и количество доступных цветов зависят от графического режима. Графический ре-
жим устанавливается с помощью служебной программы — графического драйвера.
В состав оболочки входят несколько драйверов, каждый из которых может работать
в нескольких режимах. Режим устанавливается при инициализации графики либо
автоматически, либо программистом. Самый «мощный» режим, поддерживаемый
модулем Graph, — 640 × 480 точек, 16 цветов. Модуль Graph обеспечивает:
вывод линий и геометрических фигур заданным цветом и стилем;
закрашивание областей заданным цветом и шаблоном;
вывод текста различным шрифтом, заданного размера и направления;
определение окон и отсечение по их границе;
использование графических спрайтов и работу с графическими страницами.
В графическом режиме (в отличие от текстового) курсор невидим, однако его мож-
но переместить в любую точку экрана. Текущее положение курсора используют
многие процедуры вывода изображений. При определении на экране окна началом
координат считается левый верхний угол этого окна.
97
98 Часть I. Основы программирования
Перед выводом изображения необходимо определить его стиль, то есть задать цвет
фона, цвет линий и контуров, тип линий (например, сплошная или пунктирная), их
толщину, шаблон (орнамент) заполнения, вид и размер шрифта, и т. д.
Эти параметры устанавливаются с помощью соответствующих процедур. Возмож-
ные значения параметров определены в модуле Graph в виде многочисленных кон-
стант. Например, константа DottedLn определяет пунктирную линию, а константа
CenterText — выравнивание текста по центру отведенного ему поля.
ПРИМЕЧАНИЕ
Если программист не задал стиль, при выводе изображений используются параметры,
заданные по умолчанию. Например, линия выводится белым цветом, нормальной
толщины, сплошная.
98
Глава 4. Модульное программирование 99
99
100 Часть I. Основы программирования
100
Глава 4. Модульное программирование 101
скорости отрисовки изображение формируется один раз, после чего заносится в па-
мять с помощью процедуры GetImage (оператор 9). Объем памяти, необходимый для
размещения спрайта, определяется с помощью процедуры ImageSize, выделение па-
мяти выполняет процедура GetMem (оператор 8).
СОВЕТ
Современные программы чаще всего пишутся для работы в многозадачных средах, где
каждой программе доступен не весь экран, а окно, размеры которого пользователь, как
правило, может изменять. Поэтому полезно сразу же учиться писать программы так, как
будто они выполняют вывод в окно неизвестного размера. Для этого все координаты вы-
числяются относительно размеров окна, получаемых с помощью функций, а небольшие
числа (например, отступы от края экрана) задаются именованными константами.
Модуль Strings
Модуль Strings предназначен для работы со строками, заканчивающимися нуль-
символом, то есть символом с кодом 0 (их часто называют ASCIIZ-строками). Этот
вид строк введен в Паскаль специально для работы с длинными строками и про-
граммирования под Windows. Модуль Strings содержит функции копирования,
101
102 Часть I. Основы программирования
1
Работа с динамической памятью рассматривается в следующем разделе, поэтому имеет
смысл вернуться к изучению этого материала позже.
102
Глава 4. Модульное программирование 103
strlower(str);
p := text; { 3 }
num := 0;
while true do begin
p := strpos(p, str);
if p = nil then break else inc(num);
inc(p);
end;
writeln(' Количество повторений: ', num)
end.
В первой части программы открывается компонентный файл, определяется его
длина, выделяется соответствующий объем динамической памяти, после чего туда
посимвольно считывается содержимое файла и переводится в нижний регистр.
Операторы, помеченные комментарием 2, выполняют чтение в буфер образца для
поиска. Затем буфер копируется в динамическую память. Это необходимо, потому
что процедура readln не умеет считывать строки в переменные типа pChar.
Цикл 3 выполняет поиск подстроки str в тексте text. Функция strpos возвращает
указатель на начало найденного фрагмента или nil, если он не найден. Для того
чтобы при каждом проходе цикла выполнялся поиск очередного вхождения образ-
ца, начальный адрес поиска сдвигается на единицу.
103
Глава 5. Работа с динамической памятью
Напомню (см. с. 78), что в PC-совместимых компьютерах память условно разделе-
на на сегменты. Компилятор формирует сегменты кода, данных и стека, а остальная
доступная программе память называется динамической (хипом, кучей). Ее можно
использовать во время выполнения программы.
В программах, приведенных ранее, для хранения данных использовались про-
стые переменные, массивы или записи. По их описаниям в разделе var компилятор
определяет, сколько места в памяти необходимо для хранения каждой величины.
Такие переменные можно назвать статическими. Распределением памяти под них
занимается компилятор, а обращение к этим переменным выполняется по имени.
Динамические переменные создаются в хипе во время выполнения программы. Об-
ращение к ним осуществляется через указатели.
С помощью динамических переменных можно обрабатывать данные, объем кото-
рых до начала выполнения программы не известен. Память под такие данные выде-
ляется порциями, или блоками, которые связываются друг с другом. Такой способ
хранения данных называется динамическими структурами, поскольку их размеры
изменяются в процессе выполнения программы.
Указатели
Имя переменной служит для обращения к области памяти, которую занимает ее
значение. Каждый раз, когда в исполняемых операторах программы упоминается
какое-либо имя, компилятор подставляет на его место обращение к соответствую-
щей ячейке памяти.
Программист может определить собственные переменные для хранения адресов
областей памяти. Такие переменные называются указателями. В указателе можно
хранить адрес данных или программного кода (например, адрес точки входа в про-
цедуру). Адрес занимает четыре байта и хранится в виде двух слов, одно из которых
определяет сегмент, второе — смещение.
Указатели в Паскале можно разделить на два вида: стандартные и определяемые
программистом. Величины стандартного типа pointer предназначены для хране-
ния адресов данных произвольного типа, например:
var p : pointer;
104
Глава 5. Работа с динамической памятью 105
Операции с указателями
Для указателей определены только операции присваивания и проверки на равен-
ство и неравенство. В Паскале, в отличие от других языков, запрещаются любые
арифметические операции с указателями, их ввод-вывод и сравнение на больше-
меньше. Рассмотрим правила присваивания указателей.
Любому указателю можно присвоить стандартную константу nil, которая означает,
что указатель не ссылается на какую-либо конкретную ячейку памяти.
Указатели стандартного типа pointer совместимы с указателями любого типа.
Указателю на конкретный тип данных можно присвоить только значение указа-
теля того же или стандартного типа.
Операция @ и функция addr позволяют получить адрес переменной1, например:
var x : word; { переменная }
pw : ^word; { указатель на величины типа word }
...
pw := @w; { или pw := addr(w); }
Для обращения к значению переменной, адрес которой хранится в указателе, при-
меняется операция разадресации (разыменования), обозначаемая с помощью сим-
вола ^ справа от имени указателя, например:
pw^ := 2;
inc(pw^);
writeln(pw^);
В первом операторе в ячейку памяти, адрес которой хранится в переменной pw,
заносится число 2. При выполнении оператора вывода на экране появится чис-
ло 3.
1
В режиме {$T-}, принятом по умолчанию, тип результата операции @ совместим со всеми
типами указателей.
105
106 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Указатели стандартного типа разыменовывать нельзя.
106
Глава 5. Работа с динамической памятью 107
ПРИМЕЧАНИЕ
Эту реакцию можно изменить, задав собственную функцию обработки ошибки вы-
деления памяти.
107
108 Часть I. Основы программирования
ВНИМАНИЕ
При присваивании указателю другого значения старое значение теряется (рис. 5.3). Это
приводит к появлению так называемого мусора (на рисунке обозначен овалом), когда
доступа к участку динамической памяти нет, а сам он помечен как занятый.
108
Глава 5. Работа с динамической памятью 109
109
110 Часть I. Основы программирования
110
Глава 5. Работа с динамической памятью 111
ПРИМЕЧАНИЕ
Обратите внимание, что тип указателя pnode на запись node определен раньше, чем
сама запись. Это не противоречит принципу «использование только после описания»,
поскольку для описания переменной типа pnode информации вполне достаточно.
Стеки
Стек является простейшей динамической структурой. Добавление элементов
в стек и выборка из него выполняются из одного конца, называемого вершиной сте-
ка. Другие операции со стеком не определены. При выборке элемент исключается
из стека.
Говорят, что стек реализует принцип обслуживания LIFO (last in — first out, послед-
ним пришел — первым обслужен). Стек можно представить себе как узкое дупло,
в которое засовывают, скажем, яблоки1. Достать первое яблоко можно только после
1
Почему яблоки? Ну, придумайте что-нибудь получше!
111
112 Часть I. Основы программирования
того, как вынуты все остальные. Кстати, сегмент стека назван так именно потому,
что память под локальные переменные выделяется по принципу LIFO. Стеки ши-
роко применяются в системном программном обеспечении, компиляторах, в раз-
личных рекурсивных алгоритмах.
Для работы со стеком используются две статические переменные: указатель на вер-
шину стека и вспомогательный указатель.
var top, p : pnode;
Тип указателей должен соответствовать типу элементов стека. Мы будем строить
стек из элементов, тип которых описан в предыдущем разделе.
Занесение первого элемента в стек выполняется в два приема: сначала выделяется
место в памяти и адрес его начала заносится в указатель на вершину стека (опера-
тор 1), а затем заполняются все поля элемента стека (операторы 2):
new(top); { 1 }
top^.d := 100; top^.s := 'Вася'; top^.p := nil; { 2 }
Значение nil в поле указателя на следующий элемент говорит о том, что этот эле-
мент в стеке является последним (рис. 5.4).
При добавлении элемента в стек, кроме создания элемента и заполнения его инфор-
мационной части (операторы 1 и 2), требуется связать его с предыдущим элемен-
том (оператор 3) и обновить указатель на вершину стека (оператор 4), потому что
теперь в вершине стека находится новый элемент:
new(p); { 1 }
p^.d := 10; p^.s := 'Петя'; { 2 }
p^.p := top; { 3 }
top := p; { 4 }
Стек, состоящий из трех элементов, изображен на рис. 5.5.
Выборка из стека состоит в получении информационной части элемента (опера-
тор 1), переносе указателя на вершину стека на следующий элемент (оператор 2)
и освобождении памяти из-под элемента (оператор 3):
with top^ do writeln (d, s); { 1 }
p := top; top := top^.p; { 2 }
dispose(p); { 3 }
112
Глава 5. Работа с динамической памятью 113
113
114 Часть I. Основы программирования
Очереди
Очередь — это динамическая структура данных, добавление элементов в которую
выполняется в один конец, а выборка — из другого конца. Другие операции с оче-
редью не определены. При выборке элемент исключается из очереди. Говорят, что
очередь реализует принцип обслуживания FIFO (first in — first out, первым при-
шел — первым обслужен)1. В программировании очереди применяются очень ши-
роко — например, при моделировании, буферизованном вводе-выводе или диспет-
черизации задач в операционной системе.
Для работы с очередью используются указатели на ее начало и конец, а также вспо-
могательный указатель:
var beg, fin, p : pnode;
Тип указателей должен соответствовать типу элементов, из которых состоит оче-
редь. Мы будем создавать очередь из тех же элементов, что и стек (см. с. 111).
Начальное формирование очереди — это создание ее первого элемента и установка
на него обоих указателей (рис. 5.6):
new(beg);
beg^.d := 100; beg^.s := 'Вася'; beg^.p := nil;
fin := beg;
1
От очереди в кассу одноименная структура данных отличается тем, что без очереди влезть
невозможно.
114
Глава 5. Работа с динамической памятью 115
Выборка элемента выполняется из начала очереди (оператор 1), при этом выбран-
ный элемент удаляется (оператор 3), а указатель на начало очереди сдвигается на
следующий элемент (оператор 2):
with beg^ do writeln (d, s); { 1 }
p := beg; beg := beg^.p; { 2 }
dispose(p); { 3 }
В листинге 5.3 приведена программа, которая формирует очередь из пяти целых
чисел и их текстового представления и выводит еe на экран. Для разнообразия опе-
рации с очередью оформлены в виде процедур. Процедура начального формирова-
ния называется first, помещения в конец очереди — add, а выборки — get.
115
116 Часть I. Основы программирования
Линейные списки
В линейном списке каждый элемент связан со следующим и, возможно, с предыду-
щим. В первом случае список называется односвязным, во втором — двусвязным.
Также применяются термины «однонаправленный» и «двунаправленный». Если
последний элемент связать указателем с первым, получится кольцевой список.
Каждый элемент списка содержит ключ, идентифицирующий этот элемент. Ключ
обычно бывает либо целым числом, либо строкой и является частью поля данных.
В качестве ключа в процессе работы со списком могут выступать разные части
поля данных. Например, если создается линейный список из записей, содержащих
фамилию, год рождения, стаж работы и пол, любая часть записи может выступать
116
Глава 5. Работа с динамической памятью 117
117
118 Часть I. Основы программирования
118
Глава 5. Работа с динамической памятью 119
119
120 Часть I. Основы программирования
Удаление элемента выполняет процедура del. В ней две ветви, поскольку при уда-
лении элемента из начала списка изменяется указатель на начало, а при удалении
второго и последующих элементов требуется связывать предыдущий и последую-
щий по отношению к удаляемому элементы.
Бинарные деревья
Бинарное дерево — это динамическая структура данных, состоящая из узлов, каж-
дый из которых содержит кроме данных не более двух ссылок на различные бинар-
ные деревья. На каждый узел имеется ровно одна ссылка. Начальный узел называ-
ется корнем дерева.
Пример бинарного дерева приведен на рис. 5.9 (корень обычно изображается
сверху). Узел, не имеющий поддеревьев, называется листом. Исходящие узлы на-
зываются предками, входящие — потомками. Высота дерева определяется количе-
ством уровней, на которых располагаются его узлы.
Если дерево организовано таким образом, что для каждого узла все ключи его лево-
го поддерева меньше ключа этого узла, а все ключи его правого поддерева — боль-
ше, оно называется деревом поиска. Одинаковые ключи не допускаются. В дереве
поиска можно найти элемент, двигаясь от корня и переходя на левое или правое
поддерево в зависимости от значения ключа в каждом узле. Такой поиск гораздо
120
Глава 5. Работа с динамической памятью 121
ПРИМЕЧАНИЕ
Для так называемого сбалансированного дерева, в котором количество узлов справа
и слева различается не более чем на единицу, высота дерева равна двоичному логарифму
количества узлов. Линейный список можно представить как вырожденное бинарное
дерево, в котором каждый узел имеет не более одной ссылки. Для списка среднее время
поиска равно половине длины списка.
121
122 Часть I. Основы программирования
print_tree( правое_поддерево )
end;
Можно обходить дерево и в другом порядке, например: сначала корень, потом под-
деревья, но приведенная функция позволяет получить последовательность клю-
чей, отсортированную по возрастанию, поскольку сначала посещаются вершины
с меньшими ключами, расположенные в левом поддереве. Результат обхода дерева,
изображенного на рис. 5.9:
1, 6, 8, 10, 20, 21, 25, 30
Если в функции обхода первое обращение идет к правому поддереву, результат об-
хода будет другим:
30, 25, 21, 20, 10, 8, 6, 1
Таким образом, деревья поиска можно применять для сортировки значений. При
обходе дерева узлы не удаляются.
Для бинарных деревьев определены операции:
включения узла в дерево;
поиска по дереву;
обхода дерева;
удаления узла.
Для простоты будем рассматривать дерево, каждый элемент которого содержит
только целочисленный ключ и два указателя.
type pnode = ^node;
node = record
data : word; { ключ }
left : pnode; { указатель на левое поддерево }
right : pnode { указатель на правое поддерево }
end;
Доступ к дереву в программе осуществляется через указатель на его корень:
var root : pnode;
Рассмотрим сначала функцию поиска по дереву, так как она используется и при
включении, и при удалении элемента (листинг 5.5).
122
Глава 5. Работа с динамической памятью 123
find := false;
end;
Функция возвращает булев признак успешности поиска. Ей передаются указатель
на корень дерева, в котором выполняется поиск (root), и искомый ключ (key). Вы-
ходными параметрами функции являются указатели на найденный элемент (p)
и его предка (parent).
ПРИМЕЧАНИЕ
Указатель на предка используется при удалении и вставке элемента. Для упрощения
алгоритмов можно добавить этот указатель в каждый элемент дерева.
Удаление элемента — более сложная задача, поскольку при этом необходимо сохра-
нить свойства дерева поиска. Ее можно разбить на четыре этапа.
1. Найти узел, который будет поставлен на место удаляемого.
2. Реорганизовать дерево так, чтобы не нарушились его свойства.
3. Присоединить новый узел к узлу-предку удаляемого узла.
4. Освободить память из-под удаляемого узла.
Удаление узла происходит по-разному в зависимости от его расположения в дереве.
Если узел является листом, то есть не имеет потомков, достаточно обнулить со-
ответствующий указатель узла-предка (рис. 5.10). Если узел имеет только одного
потомка, этот потомок ставится на место удаляемого узла, а в остальном дерево не
изменяется (рис. 5.11).
123
124 Часть I. Основы программирования
Сложнее всего случай, когда у узла есть оба потомка, но и здесь есть простой осо-
бый случай: если у его правого потомка нет левого потомка, удаляемый узел заме-
няется на своего правого потомка, а левый потомок удаляемого узла подключается
вместо отсутствующего левого потомка. Звучит не очень-то понятно, поэтому рас-
смотрите этот случай на рис. 5.12.
В общем же случае на место удаляемого узла ставится самый левый лист его право-
го поддерева (или наоборот — самый правый лист его левого поддерева). Это не
нарушает свойств дерева поиска. Этот случай иллюстрируется рис. 5.13.
124
Глава 5. Работа с динамической памятью 125
125
126 Часть I. Основы программирования
Иначе, если у узла p нет правого поддерева, на его место будет поставлена вершина
его левого поддерева (оператор 8). В противном случае оба поддерева узла суще-
ствуют, и для определения заменяющего узла вызывается вспомогательная функ-
ция descent, выполняющая спуск по дереву (оператор 9).
В этой функции первым делом проверяется особый случай, описанный выше (опе-
ратор 1, см. рис. 5.12). Если же условие отсутствия левого потомка у правого по-
томка удаляемого узла не выполняется, организуется цикл (оператор 2), на каждой
итерации которого указатель на текущий узел запоминается в переменной prev, а
указатель y смещается вниз и влево по дереву до того момента, пока не станет ссы-
латься на узел, не имеющий левого потомка — он-то нам и нужен!
В операторе 3 к этой пустующей ссылке присоединяется левое поддерево удаляе-
мого узла. Перед тем как присоединять к этому узлу правое поддерево удаляемо-
го узла (оператор 5), требуется «пристроить» его собственное правое поддерево.
Мы присоединяем его к левому поддереву предка узла y, заменяющего удаляемый
(оператор 4), поскольку этот узел перейдет на новое место.
Функция descent возвращает указатель на узел, заменяющий удаляемый. Если мы
удаляем корень дерева, надо обновить указатель на корень (оператор 10), иначе —
присоединить этот указатель к соответствующему поддереву предка удаляемого
узла (оператор 11). После того как узел удален из дерева, освобождается занимае-
мая им память (оператор 12).
В листинге 5.7 приведен пример работы с бинарным деревом.
126
Глава 5. Работа с динамической памятью 127
127
128 Часть I. Основы программирования
30
25
21
20
10
8
6
1
Для имитации структуры дерева перед значением узла выводится количество про-
белов, пропорциональное уровню узла. Если закомментировать цикл печати пробе-
лов, отсортированный по убыванию массив будет выведен в столбик. Заметьте, что
функция обхода дерева длиной всего в несколько строк может напечатать дерево
любого размера — ограничением является лишь размер стека.
128
Глава 5. Работа с динамической памятью 129
var st : ^stack;
new(st);
Обращение к элементу стека будет содержать операцию разадресации:
st^[top].d := d;
Для реализации очереди требуются две переменные целого типа — для хранения
индекса элементов массива, являющихся началом и концом очереди.
Линейный список реализуется с помощью вспомогательного массива целых чисел
и переменной, хранящей номер первого элемента, например:
10 25 20 6 21 8 1 30 — массив данных
2 3 4 5 6 7 8 –1 — вспомогательный массив
1 — индекс первого элемента в списке
i-й элемент вспомогательного массива содержит для каждого i-го элемента масси-
ва данных индекс следующего за ним элемента. Отрицательное число или нуль ис-
пользуется как признак конца списка (в принципе, для этого можно использовать
любое число, не входящее в множество значений индекса массива). Массив после
сортировки выглядит так:
10 25 20 6 21 8 1 30 — массив данных
3 8 5 6 2 1 4 –1 — вспомогательный массив
7 — индекс первого элемента в списке
Для создания бинарного дерева используются два вспомогательных массива (ин-
дексы вершин его правого и левого поддерева) и переменная, в которой хранится
индекс корня. Признак пустой ссылки — отрицательное число или нуль. Например,
дерево, приведенное на рис. 5.9, можно представить следующим образом:
10 25 20 6 21 8 1 30 — массив данных
4 3 –1 7 –1 –1 –1 –1 — левая ссылка
2 8 5 6 –1 –1 –1 –1 — правая ссылка
1 — индекс корневого элемента дерева
ВНИМАНИЕ
При работе с подобными структурами необходимо контролировать возможный выход
индексов за границы массива.
129
Глава 6. Технология структурного
программирования
Мы рассмотрели множество тем: типы данных и операторы Паскаля, использова-
ние подпрограмм и модулей, работу с динамической памятью и файлами. Такой ба-
гаж позволяет писать довольно сложные программы, но коммерческий программ-
ный продукт можно создать, только следуя определенной дисциплине. Рассмотрим
свойства, отличающие кустарные поделки от профессиональных продуктов.
130
Глава 6. Технология структурного программирования 131
Постановка задачи
Создание любой программы начинается с постановки задачи. Изначально задача
формулируется в терминах предметной области, и необходимо перевести ее на
язык понятий, более близких к программированию. Поскольку программист редко
досконально разбирается в предметной области, а заказчик — в программировании
(простой пример: требуется написать бухгалтерскую программу), постановка за-
дачи может стать весьма непростым итерационным процессом. Кроме того, при по-
становке задачи заказчик зачастую не может четко и полно сформулировать свои
требования и критерии.
На этом этапе также определяется среда, в которой будет выполняться программа:
требования к аппаратуре, используемая операционная система и другое программ-
ное обеспечение.
Постановка задачи завершается созданием технического задания, а затем внешней
спецификации программы, включающей в себя:
описание исходных данных и результатов (типы, форматы, точность, способ
передачи, ограничения)1;
описание задачи, реализуемой программой;
способ обращения к программе;
описание возможных аварийных ситуаций и ошибок пользователя.
Таким образом, программа рассматривается как черный ящик, для которого опре-
делена функция и входные и выходные данные.
1
Под типами и форматами не имеются в виду типы языка программирования.
131
132 Часть I. Основы программирования
Проектирование
Под проектированием программы понимается определение общей структуры
и взаимодействия модулей. На этом этапе применяется технология нисходящего
проектирования, основная идея которого теоретически проста: разбиение задачи на
подзадачи меньшей сложности, пригодные для рассмотрения по отдельности. При
этом используется метод пошаговой детализации.
Можно представить себе этот процесс так: сначала программа пишется на языке не-
которой гипотетической машины, которая способна понимать самые обобщенные
действия, а затем каждое из них описывается на более низком уровне абстракции,
и т. д. Очень важной на этом этапе является спецификация интерфейсов, то есть
определение способов взаимодействия подзадач.
132
Глава 6. Технология структурного программирования 133
ВНИМАНИЕ
На этапе проектирования следует учитывать возможность будущих модификаций
программы и стремиться проектировать программу таким образом, чтобы вносить
изменения было как можно проще.
Структурное программирование
Программирование здесь рассматривается «в узком смысле», то есть понимается
как запись программы на языке программирования по готовому алгоритму. Этот
процесс часто называют кодированием, чтобы отличить его от полного цикла раз-
работки программы.
Кодирование также организуется по принципу «сверху вниз»: вначале кодируются
модули самого верхнего уровня и составляются тестовые примеры для их отладки,
при этом на месте еще не написанных модулей следующего уровня ставятся так
называемые заглушки — временные программы. Заглушки в простейшем случае
просто выдают сообщение о том, что им передано управление, а затем возвращают
его в вызывающий модуль. В других случаях заглушка может выдавать значения,
заданные заранее или вычисленные по упрощенному алгоритму.
Таким образом, сначала создается логический скелет программы, который затем
обрастает плотью кода. Казалось бы, более логично применять к процессу програм-
мирования восходящую технологию: написать и отладить сначала модули нижнего
уровня, а затем объединять их в более крупные фрагменты, но этот подход имеет
ряд недостатков.
133
134 Часть I. Основы программирования
Нисходящее тестирование
Этот этап записан последним, но это не значит, что тестирование не должно прово-
диться на предыдущих этапах. И проектирование, и программирование обязательно
должно сопровождать (а лучше предшествовать) написание набора тестов — про-
верочных исходных данных и соответствующих им наборов эталонных реакций.
Необходимо различать процессы тестирования и отладки программы. Тестирова-
ние — это процесс, посредством которого проверяется правильность программы.
Тестирование носит позитивный характер, его цель — показать, что программа
работает правильно и удовлетворяет всем проектным спецификациям. Отлад-
ка — процесс исправления ошибок в программе, при котором цель исправить все
ошибки не ставится. Исправляют ошибки, обнаруженные при тестировании. При
планировании следует учитывать, что процесс обнаружения ошибок подчиняется
закону насыщения, то есть большинство ошибок обнаруживается на ранних стади-
ях тестирования, и чем меньше в программе осталось ошибок, тем дольше искать
каждую из них.
Существует две стратегии тестирования: «черный ящик» и «белый ящик». При ис-
пользовании первой внутренняя структура программы во внимание не принимает-
ся и тесты составляются так, чтобы полностью проверить функционирование про-
граммы на корректных и некорректных входных воздействиях.
Стратегия «белого ящика» предполагает проверку всех операторов, ветвей или
условий алгоритма. Общее число ветвей определяется комбинацией всех альтерна-
тив на каждом этапе. Это конечное число, но оно может быть очень большим, поэ-
тому программа разбивается на фрагменты, после исчерпывающего тестирования
134
Глава 6. Технология структурного программирования 135
которых они рассматриваются как элементарные узлы более длинных ветвей. Кро-
ме данных, обеспечивающих выполнение операторов в требуемой последователь-
ности, тесты должны содержать проверку граничных условий (например, переход по
условию х > 10 должен проверяться для значений, бˆольших, меньших и равных 10).
Отдельно проверяется реакция программы на ошибочные исходные данные.
Недостатком стратегии «белого ящика» является то, что обнаружить с помощью
нее отсутствующую ветвь невозможно, а стратегия «черного ящика» требует боль-
шого количества вариантов входных воздействий, поэтому на практике применяют
сочетание обеих стратегий.
ВНИМАНИЕ
Идея нисходящего тестирования предполагает, что к тестированию программы при-
ступают еще до того, как завершено ее проектирование. Это позволяет раньше опро-
бовать основные межмодульные интерфейсы, а также убедиться в том, что программа
в основном удовлетворяет требованиям пользователя. Только после того, как логиче-
ское ядро испытано настолько, что появляется уверенность в правильности реализа-
ции основных интерфейсов, приступают к кодированию и тестированию следующего
уровня программы.
Правила программирования
С приобретением опыта программист вырабатывает собственные правила и стиль.
При этом совершенно необязательно наступать на все грабли самому. Разумное
следование приведенным ниже рекомендациям поможет избежать многих распро-
страненных ошибок. Конечно, на все случаи жизни советы дать невозможно, ведь
не зря многие считают программирование искусством.
ВНИМАНИЕ
Главная цель, к которой нужно стремиться, — получить легко читаемую программу воз-
можно более простой структуры. В конечном счете все технологии программирования
направлены на достижение именно этой цели, поскольку только так можно добиться
надежности программы и легкости ее модификации.
135
136 Часть I. Основы программирования
136
Глава 6. Технология структурного программирования 137
case, а не нескольких if. Для просмотра массива лучше пользоваться циклом for.
Оператор goto применяют весьма редко, например, для принудительного выхода
из нескольких вложенных циклов, а в большинстве других ситуаций лучше ис-
пользовать другие средства языка, такие как процедуры break или exit.
Программа должна быть «прозрачна». Если какое-либо действие можно за-
программировать разными способами, то предпочтение должно отдаваться не
наиболее компактному и даже не наиболее эффективному, а такому, который
легче для понимания. Особенно это важно тогда, когда пишут программу одни,
а сопровождают другие, что является широко распространенной практикой.
«Непрозрачное» программирование может повлечь огромные затраты на поиск
ошибок при отладке.
Не следует размещать в одной строке много операторов. Как и в русском языке,
после знаков препинания должны использоваться пробелы:
f=a+b; { плохо! Лучше f = a + b; }
Вложенные блоки должны иметь отступ в 3–4 символа, причем блоки одного
уровня вложенности должны быть выровнены по вертикали. Форматируйте
текст по столбцам везде, где это возможно.
var p : pnode; { удаляемый узел }
parent : pnode; { предок удаляемого узла }
y : pnode; { узел, заменяющий удаляемый }
...
if p^.left = nil then y := p^.right
else if p^.right = nil then y := p^.left
else y := descent(p);
В последних трех строках показано, что иногда большей ясности можно до-
биться, если не следовать правилу отступов буквально.
Помечайте конец длинного составного оператора, например:
while true do begin
while not eof(f) do begin
for i := 1 to 10 do begin
for j := 1 to 10 do begin
{ две страницы кода }
end { for j := 1 to 10 }
end { for i := 1 to 10 }
end { while not eof(f) )
end { while true }
Для организации циклов пользуйтесь наиболее подходящим оператором. Цикл
repeat применяется только в тех случаях, когда тело в любом случае потребуется
выполнить хотя бы один раз, например при проверке ввода. Цикл for использу-
ется, если число повторений известно заранее и параметр имеет порядковый тип,
цикл while — во всех остальных случаях. При записи итеративных циклов (в ко-
торых для проверки условия выхода используются соотношения переменных,
формируемых в теле цикла) необходимо предусматривать аварийный выход
по достижении заранее заданного максимального количества итераций. Чтобы
137
138 Часть I. Основы программирования
Документирование программы
Программа, если она используется, живет не один год, и необходимость вносить
в нее какие-то изменения и дополнения появляется сразу же после ввода в эксплуа-
тацию. Сопровождение программы занимает гораздо больший промежуток време-
ни, чем ее написание, поэтому документирование программы имеет очень большое
значение. Основная часть документации должна находиться в тексте программы.
Хорошие комментарии написать почти так же сложно, как и хорошую программу.
Даже если сопровождающим программистом является автор программы, разбирать-
ся через год в плохо документированном тексте — сомнительное удовольствие.
138
Глава 6. Технология структурного программирования 139
1
Совсем хорошо, если они при этом не будут содержать орфографических ошибок.
139
Глава 7. Введение в объектно-ориентированное
программирование
140
Глава 7. Введение в объектно-ориентированное программирование 141
141
142 Часть I. Основы программирования
Описание объектов
Объект — это тип данных, поэтому он определяется в разделе описания типов.
В других языках объектный тип называют классом. Объект похож на тип record, но
кроме полей данных в нем можно описывать методы. Методами называются под-
программы, предназначенные для работы с полями объекта. Внутри объекта опи-
сываются только заголовки методов:
type имя = object
[ private ]
описание полей
[ public ]
заголовки методов
end;
Поля и методы называются элементами объекта. Их видимостью управляют дирек-
тивы private и public. Ключевое слово private (закрытые) ограничивает видимость
перечисленных после него элементов файлом, в котором описан объект. Действие
директивы распространяется до другой директивы или до конца объекта. В объекте
может быть произвольное количество разделов private и public. По умолчанию все
142
Глава 7. Введение в объектно-ориентированное программирование 143
ПРИМЕЧАНИЕ
В объекте monster перечислены только те методы, которые необходимы для демон-
страции синтаксиса объектов и возможностей ООП. При написании реальной игры
элементов было бы гораздо больше.
143
144 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Во многих учебниках по ООП каждому полю сопоставлены два метода: один для
установки значения поля, другой для его получения, что на первый взгляд вызывает
недоумение, потому что увеличивает объем программы и не дает никаких преимуществ.
Дело в том, что применять ООП имеет смысл в основном для сложных объектов,
а в книгах обычно приводятся примеры простых, потому что сложный объект займет
много места, а пояснение основных принципов ООП станет не таким прозрачным.
Поэтому вы должны понимать, что поля реальных объектов часто представляют собой
сложные структуры данных, доступ к которым исключительно с помощью методов
оправдан и необходим. В этом случае можно не только изменять метод доступа к полю,
но и при необходимости вносить в структуру поля изменения, не отражающиеся на ин-
терфейсе объекта. Для полей, представляющих собой простые переменные, часто требу-
ется контролировать допустимость устанавливаемых значений. Кроме того, обращение
ко всем полям с помощью методов обеспечивает единообразие доступа к объекту.
1
Впрочем, с помощью модуля Graph при всем желании не удастся нарисовать ничего пора-
жающего воображение.
144
Глава 7. Введение в объектно-ориентированное программирование 145
ВНИМАНИЕ
В отличие от остальных, объектный тип можно определять только в разделе описания
типов самого внешнего блока программы или модуля. Иными словами, нельзя описы-
вать объектный тип внутри подпрограммы и в разделе описания переменных.
145
146 Часть I. Основы программирования
Экземпляры объектов
Переменная объектного типа называется экземпляром объекта. Часто экземпляры
называют просто объектами. Время жизни и видимость объектов зависят от вида
и места их описания и подчиняются общим правилам Паскаля. Экземпляры объ-
ектов, так же как и переменные других типов, можно создавать в статической или
динамической памяти, например:
var Vasia : monster; { описывается статический объект }
pm : ^monster; { описывается указатель на объект }
...
new(pm); { создается динамический объект }
Можно определять массивы объектов или указателей на объекты и создавать из
них динамические структуры данных. Если объектный тип описан в модуле, для
создания в программе переменных этого типа следует подключить модуль в раз-
деле uses:
uses graph, monsters;
146
Глава 7. Введение в объектно-ориентированное программирование 147
Доступ к элементам объекта осуществляется так же, как к полям записи: либо с ис-
пользованием составного имени, либо с помощью оператора with.
Vasia.erase;
with pm^ do begin init(100, 100, 30); draw; end;
При обращении указывается имя экземпляра объекта, а не имя типа. Если поля
объекта являются открытыми или объектный тип описан в той же программной
единице, где используется, к полям можно обращаться так же, как к методам:
pm^.x := 300; { если бы x было public }
Если объект описан в модуле, получить или изменить значения элементов со спец-
ификатором private в программе можно только через обращение к соответствую-
щим методам. При создании каждого объекта выделяется память, достаточная для
хранения всех его полей. Методы объекта хранятся в одном экземпляре. Для того
чтобы методу было известно, с данными какого экземпляра объекта он работает,
при вызове ему в неявном виде передается параметр self, определяющий место рас-
положения данных этого объекта. Фактически внутри метода обращение к полю x
объекта имеет вид self.x. При необходимости имя self можно использовать внутри
метода явным образом, например, @self представляет собой адрес начала области,
в которой хранятся поля объекта.
Объекты одного типа можно присваивать друг другу, при этом выполняется по-
элементное копирование всех полей. Кроме того, в Паскале определены правила
расширенной совместимости типов объектов (см. с. 153). Все остальные действия
выполняются над отдельными полями объектов.
В листинге 7.2 приведен пример программы, использующей модуль monsters.
147
148 Часть I. Основы программирования
1
Перед запуском программы рекомендуется из эстетических соображений закомментиро-
вать вывод диагностического сообщения «ба-бах!» в методе attack.
148
Глава 8. Иерархии объектов
Управлять большим количеством разрозненных объектов достаточно сложно. С этой
проблемой можно справиться путем упорядочивания и ранжирования объектов, то
есть объединяя общие для нескольких объектов свойства в одном объекте и используя
этот объект в качестве базового.
Эту возможность предоставляет механизм наследования. Он позволяет строить ие-
рархии, в которых объекты-потомки получают свойства объектов-предков и могут
дополнять их или изменять. Таким образом, наследование обеспечивает важную
возможность повторного использования кода. Написав и отладив код базового объ-
екта, его можно приспособить для работы в различных ситуациях с помощью на-
следования. Это экономит время разработки и повышает надежность программ.
ПРИМЕЧАНИЕ
Для обозначения объектов-предков также используется термин «родительские объ-
екты», а потомков называют производными объектами.
Наследование
Объект в Паскале может иметь произвольное количество потомков и только одного
предка. При описании объекта имя его предка записывается в круглых скобках по-
сле ключевого слова object.
Допустим, нам требуется ввести в игру еще один тип персонажей, который должен
обладать свойствами объекта monster, но по-другому выглядеть и атаковать. Будет
логично сделать новый объект потомком объекта monster. Проанализировав код ме-
тодов этого объекта, переопределим только те, которые реализуются по-другому
(листинг 8.1).
149
150 Часть I. Основы программирования
150
Глава 8. Иерархии объектов 151
СОВЕТ
Вызов одноименного метода предка из метода потомка всегда используется для того,
чтобы сохранить функции предка и дополнить их, не повторяя фрагмент кода. Кроме
уменьшения объема программы, это облегчает ее модификацию, поскольку изменения,
внесенные в метод предка, автоматически отражаются во всех его потомках.
Методы отрисовки draw и erase также переопределены, потому что изображение де-
мона отличается от изображения монстра и, следовательно, формируется другой по-
следовательностью подпрограмм (для простоты представим демона в виде «смайли-
ка» — схематичной улыбки, часто используемой в электронной переписке).
Переопределен и метод attack: теперь атака выполняется по-разному в зависимо-
сти от наличия магической силы. Чтобы подчеркнуть, что демон атакует не так, как
монстр, текст диагностического сообщения изменен.
Чтобы перемещать демона, требуется выполнить те же действия, что записаны
в методе move для перемещения монстра: необходимо стереть его изображение
на старом месте, обновить координаты и нарисовать на новом месте. На первый
взгляд можно без проблем унаследовать этот метод, а также метод hit1. Так мы
и поступим.
Добавим описание объекта daemon в интерфейсную часть модуля monsters (c. 145),
а тексты его методов — в раздел реализации. Проверим работу новых методов с по-
мощью программы:
program test_inheritance;
uses graph, crt, monsters;
var Vasia : daemon;
gd, gm : integer;
begin
gd := detect; initgraph(gd, gm, '...');
if graphresult <> grOk then begin
writeln('ошибка инициализации графики'); exit end;
Vasia.init(100, 100, 20, 10, 6);
Vasia.draw; Vasia.attack;
readln;
Vasia.erase;
readln;
end.
1
В следующем разделе разъясняется, почему первый взгляд не всегда верен. Проблемы все-
таки будут, но решаемые.
151
152 Часть I. Основы программирования
Раннее связывание
Продолжим тестирование объекта daemon, вставив в приведенную выше програм-
му перед первой из процедур readln вызовы методов, унаследованных из объекта
monster.
Vasia.move(200, 100); Vasia.move(200, 200); Vasia.hit;
Результаты запуска программы разочаровывают: на экране появляется изображе-
ние не демона, а монстра — символ @! Значит, из метода move вызываются методы
рисования и стирания объекта-предка. Да и метод атаки, вызываемый из hit, судя
по диагностическому сообщению, также принадлежит объекту monster. Чтобы разо-
браться, отчего это происходит, рассмотрим механизм работы компилятора.
Исполняемые операторы программы в виде инструкций процессору находятся
в сегменте кода. Каждая подпрограмма имеет точку входа. Вызов подпрограммы
при компиляции заменяется на последовательность команд, которая передает
управление в эту точку, а также выполняет передачу параметров и сохранение реги-
стров процессора. Этот процесс называется разрешением ссылок и в других языках
152
Глава 8. Иерархии объектов 153
153
154 Часть I. Основы программирования
ПРИМЕЧАНИЕ
Здесь у вас может возникнуть вопрос, зачем вообще присваивать объекты разных
типов. Забегая вперед, скажу, что эта возможность используется в основном вместе
с виртуальными методами, которые дают возможность единообразного обращения
к разным объектам одной иерархии.
154
Глава 8. Иерархии объектов 155
155
156 Часть I. Основы программирования
компилятору, что эти методы будут обрабатываться по-другому. Для этого в Паска-
ле существует директива virtual. Она записывается в заголовке метода, например:
procedure attack; virtual;
Слово virtual в переводе с английского значит «фактический». Объявление метода
виртуальным означает, что все ссылки на этот метод будут разрешаться по факту
его вызова, то есть не на стадии компиляции, а во время выполнения программы.
Этот механизм называется поздним связыванием. Для его реализации необходимо,
чтобы адреса виртуальных методов хранились там, где ими можно будет в любой
момент воспользоваться, поэтому компилятор формирует для этих методов табли-
цу виртуальных методов (VMT — virtual method table). В первое поле этой таблицы
записывается размер объекта, а затем идут адреса виртуальных методов (в том чис-
ле и унаследованных) в порядке описания в объекте. Для каждого объектного типа
создается одна VMT.
Каждый объект во время выполнения программы должен иметь доступ к VMT.
Обеспечение этой связи нельзя поручить компилятору, так как она должна уста-
навливаться позже — при создании объекта во время выполнения программы. По-
этому связь экземпляра объекта с VMT устанавливается с помощью специального
метода, называемого конструктором.
Класс, имеющий хотя бы один виртуальный метод, должен содержать конструк-
тор:
type monster = object
constructor init(x_, y_, health_, ammo_ : word);
procedure attack; virtual;
procedure draw; virtual;
procedure erase; virtual;
procedure hit;
procedure move(x_, y_ : word);
private
x, y : word;
health, ammo : word;
color : word;
end;
daemon = object (monster)
constructor init(x_, y_, health_, ammo_, magic_ : word);
procedure attack; virtual;
procedure draw; virtual;
procedure erase; virtual;
procedure wizardry;
private
magic: word;
end;
По ключевому слову constructor компилятор вставляет в начало метода фрагмент,
который записывает ссылку на VMT в специальное поле объекта (память под это
156
Глава 8. Иерархии объектов 157
1
Когда в документации встречается термин «поведение программы не определено», это
чаще всего означает очень скверное поведение.
157
158 Часть I. Основы программирования
1
При переопределении обычных методов никаких ограничений на соответствие типов не
накладывается.
158
Глава 8. Иерархии объектов 159
stado[i].hit; stado[j].hit;
end;
delay(200);
until keypressed;
end.
Единственное изменение, которое пришлось сделать в части исполняемых операто-
ров программы, — добавление еще одного параметра в метод инициализации init.
Запустив программу1, можно наблюдать процесс самоуничтожения демонов, что
свидетельствует о том, что теперь из методов move и hit, унаследованных из базо-
вого класса, вызываются методы attack, draw и erase, определенные в производном
классе.
Виртуальные методы незаменимы и при передаче объектов в подпрограммы. В за-
головке подпрограммы описывается либо объект базового типа, передаваемый по
адресу, либо указатель на этот объект, а при вызове в нее передается объект или ука-
затель производного класса. В этом случае виртуальные методы, вызываемые для
объекта из подпрограммы, будут соответствовать типу аргумента, а не параметра.
Рассмотрим пример: функцию, которая передвигает объект по экрану по нажатию
клавиш управления курсором (листинг 8.3).
1
Предварительно рекомендуется отключить вывод сообщения в методе attack.
159
160 Часть I. Основы программирования
program dragging;
uses graph, crt, monsters;
var Vasia : monster;
Misha : daemon;
x, y : integer;
gd, gm : integer;
begin
gd := detect; initgraph(gd, gm, '...');
if graphresult <> grOk then begin
writeln('ошибка инициализации графики'); exit end;
Vasia.init(200, 200, 10, 8); Vasia.draw;
while drag(Vasia) do;
Misha.init(400, 400, 10, 8 ,2); Misha.draw;
while drag(Misha) do;
end.
ПРИМЕЧАНИЕ
Здесь имеется в виду, что drag помещена в модуль monsters как отдельная функция.
Если же рассматривать передвижение объекта по нажатию клавиш управления кур-
сором как свойство объекта, можно описать эту функцию как метод объекта. В этом
случае параметры ему не потребуются.
160
Глава 9. Объекты в динамической памяти
Для хранения объектов в программах чаще всего используется динамическая па-
мять, поскольку это обеспечивает гибкость программы и эффективное использо-
вание памяти. Благодаря расширенной совместимости типов можно описать ука-
затель на базовый класс и хранить в нем ссылку на любой его объект-потомок, что
в сочетании с виртуальными методами позволяет единообразно работать с различ-
ными классами иерархии. Из объектов или указателей на объекты создают различ-
ные динамические структуры.
161
162 Часть I. Основы программирования
1
Это верно только для виртуальных методов.
162
Глава 9. Объекты в динамической памяти 163
ВНИМАНИЕ
Вызов деструктора вне процедуры Dispose память из-под объекта не освобождает.
163
164 Часть I. Основы программирования
164
Глава 9. Объекты в динамической памяти 165
165
166 Часть I. Основы программирования
166
Глава 9. Объекты в динамической памяти 167
167
168 Часть I. Основы программирования
168
Глава 9. Объекты в динамической памяти 169
randomize;
report(' доступно в начале программы: ');
stado.init;
for i := 1 to n do begin
case random(2) of
0 : p := new(pmonster, init(random(600), random(440), 10, 8));
1 : p := new(pdaemon, init(random(600), random(440), 10, 8, 6));
end;
stado.add(p); { добавление объекта в список }
end;
report(' доступно после выделения памяти: ');
stado.draw; { отрисовка объектов }
stado.done; { уничтожение объектов }
report(' доступно после освобождения памяти: ');
readln;
end.
Рассмотрим подробнее деструктор объекта list. Уничтожение объекта состоит
в освобождении памяти из-под двух типов структур: полиморфных графических
объектов в списке и записей типа node, которые служат для связи элементов списка.
Список уничтожается поэлементно, начиная с первого элемента. Для этого исполь-
зуются два указателя: указатель beg на начало списка из объекта list и вспомога-
тельный указатель p.
Вызов dispose в операторе 1 освобождает память из-под первого объекта типа
monster или daemon; затем указатель beg продвигается к следующей записи node с по-
мощью оператора 2. После этого освобождается память из-под самой записи node
(оператор 3), и процесс повторяется, пока весь список не будет уничтожен. Важно
отметить способ, которым освобождаются объекты monster:
dispose(p^.pm, done);
Здесь p^.pm является указателем на объект monster, метод done — деструктором этого
объекта. Фактически p^.pm не обязательно указывает на объект типа monster — это
может быть любой его потомок. Уничтожаемый объект является полиморфным,
и во время компиляции его фактический размер и тип неизвестны. Деструктор done
ищет размер экземпляра в VMT объекта и передает этот размер процедуре dispose,
которая освобождает именно такое количество байтов в динамической памяти, ка-
кое фактически занимал объект.
169
.
170
Часть II. Практикум
171
Семинар 1. Линейные программы
Теоретический материал: глава 1.
На этом семинаре вы освоите работу в среде Turbo Pascal 7.0 или аналогичных средах
и научитесь писать линейные программы. Чтобы осмысленно написать даже самую
простую программу, надо представлять себе, из каких элементов она состоит, по ка-
ким правилам описываются константы, переменные и выражения, как выполняется
ввод с клавиатуры и вывод на экран — в общем, все, о чем написано в первой главе.
Чтобы заставить программу работать, необходимо освоить основные приемы ис-
пользования оболочки Turbo Pascal 7.0 (приложение 7) или аналогичных. Для на-
чала рассмотрим программу, выполняющую расчет по простейшей формуле.
1
По оптимистической оценке.
172
Семинар 1. Линейные программы 173
kurs_euro = 43.3; { 2 }
var rouble, dollar, euro : real; { 3 }
begin
writeln('Введите сумму в рублях: '); { 4 }
readln(rouble); { 5 }
dollar := rouble / kurs_dollar; { 6 }
euro := rouble / kurs_euro; { 7 }
writeln('Рублей: ', rouble:8:2, ' Долларов: ', dollar:7:2,
' Евро: ', euro:7:2); { 8 }
readln;
end.
Рассмотрите каждую строку программы, найдите в первой главе сведения о поняти-
ях, выделенных далее курсивом. Вместо комментариев в этой программе записаны
просто цифры для того, чтобы ссылаться на соответствующие строки программы.
В первой строке обычно пишут заголовок программы.
Разделы описаний представлены строками 2 и 3. Здесь два раздела: описания кон-
стант и описания переменных. Все переменные, используемые в программе, долж-
ны быть предварительно описаны, чтобы компилятор знал, сколько под них выде-
лить места в памяти и что с ними можно делать. Внутреннее представление одного
и того же целого и вещественного числа различно: для вещественных чисел (тип
real) в памяти хранится мантисса и порядок, а целые (тип integer) представляются
просто в двоичной форме. Более того, для действий с целыми и вещественными
величинами формируются различные наборы машинных команд. Поэтому-то опи-
сание типа каждой переменной является обязательным.
Тип переменных выбирается исходя из возможного диапазона значений и требуе-
мой точности представления данных. Имена переменным дают исходя из их назна-
чения. От того, насколько удачно подобраны имена, зависит читабельность про-
граммы — одна из ее важнейших характеристик.
Далее между ключевыми словами begin и end располагается раздел операторов.
В строке 4 записано так называемое приглашение. Оно применяется для того, что-
бы пользователь программы (сейчас это вы) знал, в какой момент следует вводить
данные с клавиатуры. Стандартная процедура writeln выводит на экран записан-
ную в ней строку символов, и курсор переходит на следующую строку.
ПРИМЕЧАНИЕ
Процедура — это последовательность операторов, к которой можно обратиться по име-
ни. Термин «стандартная процедура» означает, что процедура с таким именем включена
в состав библиотек, поставляемых вместе с компилятором Паскаля. К процедуре можно
обратиться, задав ее имя, а затем в скобках — так называемые параметры. Некоторые
процедуры параметров не имеют.
173
174 Часть II. Практикум
СОВЕТ
Рекомендуется всегда предварять ввод данных приглашением, а выводимые значения
сопровождать пояснениями. Полезно также непосредственно после ввода данных вы-
водить их на экран для контроля правильности ввода.
СОВЕТ
Тщательно форматируйте текст программы так, чтобы его было удобно читать. Ставьте
пробелы после знаков препинания, отделяйте пробелами знаки операций, не пишите
много операторов на одной строке, используйте комментарии и пустые строки для
разделения логически законченных фрагментов программы.
174
Семинар 1. Линейные программы 175
175
176 Часть II. Практикум
СОВЕТ
Перед каждым запуском программы сохраняйте ее на диске — ведь если она «повиснет»,
все ваши усилия по набору текста окажутся напрасными.
176
Семинар 1. Линейные программы 177
Если вы видите приглашение к вводу суммы, значит, программа набрана без оши-
бок, успешно скомпилирована и начала работать. Подсчитайте, сколько у вас денег,
введите эту сумму в программу и оцените результат. Нажатие клавиши Enter вернет
вас к тексту программы.
ВНИМАНИЕ
В соответствии с правилами Паскаля в вещественных числах дробная часть отделяется
не запятой, а точкой!
Если же после нажатия клавиш Ctrl+F9 вы наблюдаете в верхней строке окна ан-
глийский текст на красном фоне — это сообщение о синтаксической ошибке. Из-
учите следующий раздел, а затем сличите текст программы на экране с текстом
в книге и исправьте найденные неточности.
Ошибки компиляции
Начинающие, да и не только они, часто делают ошибки при вводе программы. Ком-
пилятор устанавливает курсор на предположительное место ошибки в тексте про-
граммы, выдает свою версию о причине ошибки и прекращает работу. Полный
список сообщений об ошибках приведен в приложении 4, а здесь рассмотрим не-
сколько примеров.
Отсутствие точки с запятой между операторами. Компилятор устанавливает
курсор на оператор, перед которым пропущена точка с запятой, и выдает сообще-
ние о том, что он ожидал увидеть на этом месте:
Error 85: ";" expected.
Отсутствие закрывающего апострофа в строковой константе. Поскольку компи-
лятор не может найти, где заканчивается строковая константа, выдается сообщение
о том, что она превышает допустимую длину:
Error 8: String constant exceeds line.
Отсутствие открывающего апострофа в строковой константе приводит либо
к выдаче предыдущего сообщения, либо к сообщению о синтаксической ошибке:
Error 5: Syntax error.
Не описана переменная. Эта ошибка выдается и в том случае, если вы ошиблись
в написании имени или ключевого слова, например пропустили букву:
Error 3: Unknown identifier.
Несоответствие количества открывающих и закрывающих скобок. Если отсутству-
ет закрывающая скобка, выдается такое сообщение:
Error 89: ")" expected.
Неверное написание ключевых слов и имен стандартных процедур. В зависимости от
места ошибки могут быть выданы различные сообщения, например:
Error 36: BEGIN expected.
Error 3: Unknown identifier.
177
178 Часть II. Практикум
Исходными данными для этой задачи являются шесть целых величин, задающих
моменты начала и конца интервала, результатами — три целых величины.
Вы уже знаете, что тип переменной выбирается исходя из диапазона и требуемой
точности представления данных, а имя дается в соответствии с ее содержимым.
Нам потребуется хранить исходные данные, представляющие собой положитель-
ные числа, не превышающие 60, поэтому можно ограничиться типом byte.
Назовем переменные для хранения начала интервала hour1, min1 и sec1, для хране-
ния конца интервала — hour2, min2 и sec2, а результирующие величины — hour, min
и sec.
Для решения задачи необходимо преобразовать оба момента времени в секунды,
вычесть первый из второго, а затем преобразовать результат обратно в часы, ми-
нуты и секунды. Следовательно, нам потребуется промежуточная переменная для
хранения интервала в секундах. Она может иметь весьма большие значения, ведь
в сутках 86 400 секунд. В величинах типа byte могут храниться значения, не превы-
шающие 255, поэтому здесь использовать его нельзя. Для этой переменной следует
выбрать длинный целый тип (longint), поскольку «обычный» целый тип integer
может хранить только значения из диапазона –32 768...32 767.
Текст программы приведен в листинге С1.2.
178
Семинар 1. Линейные программы 179
ВНИМАНИЕ
Данные при вводе разделяются пробелами, символами перевода строки или табуляции
(но не запятыми!).
Попробуйте при вводе поменять моменты начала и конца отсчета интервала1. Про-
грамма выдаст странные трехзначные числа. Это происходит потому, что отрица-
тельные величины, получившиеся в результате вычисления выражения, были при-
своены переменным, которые могут быть только положительными.
Для внутреннего представления отрицательных целых чисел используется так на-
зываемый дополнительный код. Любое отрицательное число имеет двоичную еди-
ницу в старшем разряде. Например, внутреннее представление числа –1 состоит
из двоичных единиц во всех разрядах. В типах данных, состоящих только из поло-
жительных значений, старший (в данном случае восьмой) разряд воспринимается
точно так же, как и все остальные. Вот почему при интерпретации отрицательного
числа как положительного получаются числа, бˆольшие или равные 128 (27).
Выходов из этой ситуации, как всегда, два: либо проверять допустимость вво-
димых значений, что является темой следующего семинара, либо использовать
для представления величин тип данных, в множество значений которого входят
1
Только не вводите вместо чисел буквы или другие символы — компилятор будет вас ругать,
часто употребляя при этом слово «invalid».
179
180 Часть II. Практикум
π ⋅ x − e0,2 α
+ 2 tg 2α + 1, 6 ⋅ 103 ⋅ log10 x 2 .
y=
2 tg 2α ⋅ sec x
Из формулы видно, что исходными данными для этой программы являются две
величины — x и γ. Поскольку их тип и точность представления в условии не ого-
ворены, выберем для них вещественный тип (real). Все, что от нас требуется для
решения этой задачи, — правильно записать формулу на языке Паскаль.
Для работы с вещественными величинами в Паскале (см. табл. 1.10) существуют
стандартные функции получения числа π (Pi), вычисления квадрата (sqr), квадрат-
ного корня (sqrt) и экспоненты (exp), однако нет ни тангенса, ни секанса, ни деся-
тичного логарифма. Ничего не поделаешь — придется выразить эти функции через
имеющиеся в библиотеке:
sin α 1 ln α .
tg α = ; sec α = ; log10 α =
cos α cos α ln10
Второе, на что стоит обратить внимание, — повторяющееся два раза выражение 2tg2α.
Для упрощения записи формулы и оптимизации вычислений полезно вычислить
его заранее и запомнить результат в промежуточной переменной (листинг С1.3).
180
Семинар 1. Линейные программы 181
2
× y.
x
Если же мы хотим, чтобы выражение x × y было в знаменателе, следует заключить
его в круглые скобки или сначала поделить числитель на х, а потом на y, то есть за-
писать как 2 / (x × y) или 2 / x / y.
В приведенной ранее программе и числитель и знаменатель должны заключаться
в скобки. Можно записать эту формулу и другим способом — более лаконичным,
но менее очевидным:
y := (sqrt(Pi * x) - exp(0.2 * sqrt(a)) + temp +
1600 * ln(sqr(x)) / ln(10)) / temp * cos(x);
Круглые скобки в выражениях можно использовать и без необходимости, просто
для визуальной группировки частей сложного выражения.
181
182 Часть II. Практикум
Итоги
1. Приступая к написанию программы, четко определите, что является ее исходны-
ми данными и что требуется получить в результате.
2. Выбирайте тип переменных с учетом диапазона и требуемой точности представ-
ления данных.
3. Давайте переменным имена, отражающие их назначение.
4. Ввод с клавиатуры предваряйте приглашением, а выводимые значения — по-
яснениями. Для контроля сразу же после ввода выводите исходные данные на
дисплей (по крайней мере, в процессе отладки).
5. До запуска программы подготовьте тестовые примеры, содержащие исходные
данные и ожидаемые результаты. Отдельно проверьте реакцию программы на
неверные исходные данные.
6. При записи выражений обращайте внимание на приоритет операций.
7. Разделяйте данные при вводе пробелами, символами перевода строки или табу-
ляции.
8. Тщательно форматируйте текст программы и снабжайте его содержательными
комментариями.
Задания
Напишите программу расчета по двум формулам. Предварительно подготовьте те-
стовые примеры для второй формулы с помощью калькулятора (результаты вы-
числений по обеим формулам должны совпадать). Отсутствующие функции вы-
разите через имеющиеся.
1 1 5
1. z1 = 2 sin2 (3π − 2α) cos2 (5π + 2α); z2 = − sin( π − 8α).
4 4 2
π
2. z1 = cos α + sin α + cos 3α + sin 3α ; z2 = 2 2 cos α ⋅ sin( + 2α).
4
sin 2α + sin 5α − sin 3α
3. z1 = ; z2 = 2 sin α .
cos α + 1 − 2 sin2 2α
182
Семинар 1. Линейные программы 183
2b + 2 b2 − 4 1
15. z1 = ; z2 = .
2 b+2
b −4 +b+2
x 2 + 2 x − 3 + ( x + 1) x 2 − 9 x+3
16. z1 = ; z2 = .
2
x − 2 x − 3 + ( x − 1) x − 9 2 x−3
183
184 Часть II. Практикум
1 + a + a2 1 − a + a2 −1 4 − a2 .
19. z1 = ( + 2 − ) (5 − 2a2 ); z2 =
2a + a2 2a − a2 2
(m − 1) m − (n − 1) n m − n.
20. z1 = ; z2 =
3 2
m n + nm + m − m m
184
Семинар 2. Разветвляющиеся вычислительные
процессы
Теоретический материал: глава 2, с. 34–39.
СОВЕТ
Если для решения какой-либо задачи требуется точность более 11–12 десятичных раз-
рядов или диапазон, не входящий в 10–39..1038, следует описать переменные как double.
Для сугубо специальных случаев (а также параноиков) предназначен тип extended.
185
186 Часть II. Практикум
⎧ x≤0 ⎫
⎪ ⎪
{ 2 2
}
круг: x + y ≤ 1 ; треугольник: ⎨ y ≤ 0 ⎬ .
⎪ y ≥ − x − 2⎪
⎩ ⎭
Программа для решения задачи приведена в листинге С2.1.
ПРИМЕЧАНИЕ
Для удобочитаемости программы можно ставить скобки даже в тех местах, где они не
обязательны, например, для визуальной группировки условий.
1
Справка: уравнение окружности радиуса R с центром в точке (x0, y0) выглядит так: (x – x0)2 +
+ (y – y0)2 = R2; уравнение прямой — y = kx + b.
186
Семинар 2. Разветвляющиеся вычислительные процессы 187
187
188 Часть II. Практикум
СОВЕТ
Хотя наличие слова else не обязательно, рекомендуется всегда обрабатывать случай,
когда значение выражения не совпадает ни с одной из констант. Это облегчает поиск
ошибок при отладке программы.
Итоги
1. Если в одном условном операторе требуется проверить выполнение нескольких
условий, они записываются после ключевого слова if и объединяются с помощью
логических операций and, or, xor и not. Получившееся выражение вычисляется
в соответствии с приоритетами операций.
2. Если в какой-либо ветви вычислений условного оператора if требуется выпол-
нить более одного оператора, то они объединяются в блок с помощью ключевых
слов begin и end.
3. Проверка вещественных величин на равенство опасна.
4. В операторе варианта выражение, стоящее после ключевого слова case, и констан-
ты, помечающие ветви, должны быть одного и того же порядкового типа.
5. Рекомендуется всегда описывать в операторе case ветвь else.
6. Оператор case предпочтительнее оператора if в тех случаях, когда количество
направлений вычисления в программе больше двух, а выражение, по значению
которого производится переход на ту или иную ветвь, имеет порядковый тип.
Часто это справедливо даже для двух ветвей, поскольку повышает наглядность
программы.
188
Семинар 2. Разветвляющиеся вычислительные процессы 189
Задания
Задание С2.1
Написать программу, которая по введенному значению аргумента вычисляет зна-
чение функции, заданной в виде графика. Параметр R задается константой.
№ Графики
продолжение
189
190 Часть II. Практикум
№ Графики
190
Семинар 2. Разветвляющиеся вычислительные процессы 191
№ Графики
10
11
12
13
продолжение
191
192 Часть II. Практикум
№ Графики
14
15
16
17
192
Семинар 2. Разветвляющиеся вычислительные процессы 193
№ Графики
18
19
20
193
194 Часть II. Практикум
Задание С2.2
Написать программу, которая определяет, попадает ли точка с заданными коорди-
натами в область, закрашенную на рисунке серым цветом. Результат работы про-
граммы вывести в виде текстового сообщения.
№ Область № Область
1 2
3 4
5 6
7 8
194
Семинар 2. Разветвляющиеся вычислительные процессы 195
№ Область № Область
9 10
11 12
13 14
15 16
продолжение
195
196 Часть II. Практикум
№ Область № Область
17 18
19 20
196
Семинар 3. Организация циклов
Теоретический материал: глава 2, с. 39–48.
x3 x5 x7
y=x− + − + ...
3! 5! 7!
Этот ряд сходится на всей числовой оси. Для достижения заданной точности
требуется суммировать члены ряда до тех пор, пока абсолютная величина оче-
редного члена не станет меньше или равна ε. Зависимость абсолютной величины
члена ряда от его номера для x = 5 (аргумент задается в радианах) представлена
на рис. С3.1.
197
198 Часть II. Практикум
n x 2n −1
Cn = (−1) .
(2n − 1)!
На первый взгляд может показаться, что придется организовать циклы для рас-
чета факториала и степеней. При этом можно получить очень большие числа, при
делении которых друг на друга произойдет потеря точности, поскольку количество
значащих цифр, хранимых в ячейке памяти, ограничено. Кроме того, большие чис-
ла могут переполнить разрядную сетку.
Мы поступим по-другому. Легко заметить, что (n+1)-й член ряда вычисляется после
n-го, поэтому программа получится более простой и эффективной, если находить
член ряда не «с нуля», а умножением предыдущего члена на некоторую величину.
Найдем эту величину. Для этого сначала запишем формулу для (n+1)-го члена
ряда, подставив в предыдущее выражение (n+1) вместо n:
(n +1) x 2n +1 .
Cn +1 = (−1)
(2n + 1)!
Теперь найдем выражение α, на которое надо будет умножить Сn, чтобы получить
Cn+1:
n +1
C (−1) x 2n +1 (2n − 1)! x2
Cn +1 = Cn α => α = n +1 = = − ;
Cn (2n + 1)! (−1)n x 2n −1 2n (2n + 1)
x2 .
Cn +1 = −Cn
2n (2n + 1)
198
Семинар 3. Организация циклов 199
199
200 Часть II. Практикум
СОВЕТ
Называйте переменную-флаг так, чтобы по имени можно было понять, о чем свиде-
тельствует ее истинное значение.
1
Корнем уравнения называется значение, при подстановке которого в уравнение оно пре-
вращается в тождество.
200
Семинар 3. Организация циклов 201
x := (L + R) / 2; { середина интервала }
if (cos(x) - x) * (cos(L) - L) < 0 { если значения разных знаков, то }
then R := x { корень в правой половине интервала }
else L := x { иначе корень в левой половине интервала }
until abs(R - L) < 1e-4;
writeln('Корень равен ', x:9:4);
end.
201
202 Часть II. Практикум
202
Семинар 3. Организация циклов 203
Итоги
1. При написании любого цикла надо иметь в виду, что в нем всегда явно или неявно
присутствуют четыре элемента, реализующие: начальные установки, тело цикла,
модификацию параметра цикла и проверку условия продолжения цикла.
2. Области применения операторов цикла:
оператор for применяется, если требуется выполнить тело цикла заданное
число раз;
оператор repeat используют, когда цикл требуется обязательно выполнить
хотя бы один раз, например, при анализе корректности ввода данных;
оператор while удобнее во всех остальных случаях.
3. Выражение, определяющее условие продолжения циклов while и repeat, вычис-
ляется в соответствии с приоритетами операций и должно иметь тип boolean.
4. Для принудительного перехода к следующей итерации цикла используется про-
цедура continue, для преждевременного выхода из цикла — процедура break.
5. Чтобы избежать ошибок при программировании циклов, рекомендуется:
заключать в блок тело циклов while и for, если в них требуется выполнить
более одного оператора;
проверять, всем ли переменным, встречающимся в правой части операторов
присваивания в теле цикла, присвоены до этого начальные значения, а также
возможно ли выполнение других операторов;
проверять, изменяется ли в цикле хотя бы одна переменная, входящая в усло-
вие выхода из цикла;
если количество повторений цикла заранее не известно, предусматривать
аварийный выход из цикла по достижении некоторого достаточно большого
количества итераций.
Задания
Задание С3.1
Вычислить и вывести на экран в виде таблицы значения функции, заданной гра-
фически (см. задание С.2.1), на интервале от xнач до xкон с шагом dx. Интервал и шаг
203
204 Часть II. Практикум
задать таким образом, чтобы проверить все ветви программы. Таблицу снабдить за-
головком и шапкой.
Задание С3.2
Для десяти выстрелов, координаты которых задаются с клавиатуры, вывести тек-
стовые сообщения о попадании в мишень из задания С2.2.
Задание С3.3
Вычислить и вывести на экран в виде таблицы значения функции, заданной с по-
мощью ряда Тейлора, на интервале от xнач до xкон с шагом dx с точностью ε. Та-
блицу снабдить заголовком и шапкой. Каждая строка таблицы должна содержать
значение аргумента, значение функции и количество просуммированных членов
ряда.
∞
x +1 1 ⎛1 1 1 ⎞
1. ln
x −1
=2
n =0 (
∑
2n + 1) x 2 n +1
= 2 ⎜ + 3 + 5 + ... ⎟ ,
⎝ x 3x 5x ⎠
x > 1.
∞
(−1)n x n x2 x3 x4
2. e −x
= ∑
n =0
n!
=1− x + − +
2! 3! 4!
− ..., x < ∞.
∞
xn x2 x3 x4
3. e x = ∑ n ! = 1 + x + 2! + 3! + 4! − ...,
n =0
x < ∞.
∞
(−1)n x n +1 x2 x3 x4
4. ln (x + 1) = ∑n =0
n +1
=x−
2
+
3
−
4
− ..., − 1 < x ≤ 1.
∞
1+ x x 2n +1 ⎛ x3 x5 ⎞
5. ln
1− x
=2
n =0
∑
2n + 1
= 2⎜ x +
⎜
⎝ 3
+
5
+ ... ⎟ ,
⎟
⎠
x < 1.
∞
xn ⎛ x2 x3 ⎞
6. ln(1 − x) = − ∑n =1
n
= −⎜x +
⎝ 2
+
3
+ ...⎟ −1 ≤ x < 1 .
⎠
π
∞
(−1)n +1 x 2n +1 π x3 x5
7. arcctg x =
2
+ ∑n =0
2n + 1
=
2
−x+
3
−
5
− ... | x |≤ 1.
π
∞
(−1)n +1 π 1 1 1
8. arctg x =
2
+ ∑
n =0 (
2n + 1) x 2n +1
= − + 3 − 5 ... x > 1.
2 x 3x 5x
(−1)n x 2n +1
∞
x3 x5 x7
9. arctg x = ∑ =x− + − + ... | x |≤ 1 .
n =0
(2n + 1) 3 5 7
204
Семинар 3. Организация циклов 205
∞
x 2n +1 x3 x5 x7
10. Arth x = ∑ 2n + 1 = x +
n =0
3
+
5
+
7
+ ..., | x |< 1.
∞
1 1 1 1
11. Arth x = ∑ (2n + 1)x
n =0
2 n +1
= + 3 + 5 + ..., | x |> 1 .
x 3x 5x
π
∞
(−1)n +1 π 1 1 1
12. ar ctg x = − +
2 ∑
n =0
(2n + 1) x 2n+1
=− − + 3 − 5 + ..., x < −1 .
2 x 3x 5x
∞
(−1)n x 2n x4 x6 x8
∑
2
−x
13. e = = 1 − x2 + − + − ..., | x |< ∞.
n! 2! 3! 4!
n =0
∞
(−1)n x 2n x2 x4 x6
14. cos x = ∑
n =0
(2n)!
=1− + −
2! 4! 6!
+ ..., | x |< ∞ .
∞
sin x (−1)n x 2n x2 x4 x6
15.
x
= ∑n =0
(2n + 1)!
=1− + −
3! 5! 7!
− ..., | x |< ∞ .
∞
( x − 1)2n +1 ⎛ x − 1 (x − 1)3 (x − 1)
5 ⎞
16. ln x = 2 ∑ (2n + 1)(x + 1)
n =0
2 n +1
= 2⎜ + +
⎜⎝ x + 1 3 (x + 1)3 5 (x + 1)5
+ ...⎟ , x > 0.
⎟⎠
∞
(−1)n ( x − 1)n +1 ( x − 1)2 ( x − 1)3
17. ln x = ∑
n =0
(n + 1)
= ( x − 1) −
2
+
3
+ ..., 0 < x ≤ 2 .
∞
( x − 1)n +1 x − 1 ( x − 1)2 ( x − 1)3 1
18. ln x = ∑ (n + 1)(x + 1)
n =0
n +1
=
x
+
2x 2
+
3x 3
+ ..., x > .
2
∞
1 ⋅ 3 ⋅ ... ⋅ (2n − 1) ⋅ x 2n +1
19. arcsin x = x + ∑n =1
2 ⋅ 4 ⋅ ... ⋅ 2n ⋅ (2n + 1)
=
x3 1 ⋅ 3 ⋅ x5 1 ⋅ 3 ⋅ 5 ⋅ x7 1 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ x9
=x+ + + + ..., x < 1.
2⋅3 2⋅4⋅5 2⋅4⋅6⋅7 2⋅4⋅6⋅8⋅9
π ⎛
∞
1 ⋅ 3 ⋅ ... ⋅ (2n − 1) ⋅ x 2n +1 ⎞
20. arccos x = −⎜x +
2 ⎜
⎝
∑ n =1
2 ⋅ 4 ⋅ ... ⋅ 2n ⋅ (2n + 1) ⎟
⎟ =
⎠
π ⎛ x3 1 ⋅ 3 ⋅ x5 1 ⋅ 3 ⋅ 5 ⋅ x7 1 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ x9 ⎞
= −⎜x + + + + ... ⎟ , x < 1.
2 ⎜⎝ 2⋅3 2⋅4⋅5 2⋅4⋅6⋅7 2 ⋅ 4 ⋅ 6 ⋅ 8 ⋅ 9 ⎟⎠
205
Семинар 4. Одномерные массивы
Теоретический материал: глава 3, с. 49–54.
6 –8 15 9 –1 3 5 –10 12 2
макс + + + мин
206
Семинар 4. Одномерные массивы 207
207
208 Часть II. Практикум
СОВЕТ
После нахождения каждой величины вставлена отладочная печать. Рекомендую не
пренебрегать этим способом отладки и не жалеть времени, стараясь сделать печать
максимально понятной, то есть содержащей необходимые пояснения и хорошо от-
форматированной. Кроме того, полезно выводить на печать исходные данные.
208
Семинар 4. Одномерные массивы 209
Тестовых примеров для этой задачи должно быть по крайней мере три — для слу-
чаев, когда элемент a[imin] расположен левее элемента a[imax]; элемент a[imin] рас-
положен правее элемента a[imax]; элементы a[imin] и a[imax] совпадают. Последняя
ситуация имеет место, когда в массиве все элементы имеют одно и то же значение.
Желательно также проверить, как работает программа, если элементы a[imin]
и a[imax] расположены рядом, а также в начале и в конце массива (граничные слу-
чаи). В массиве должны присутствовать как положительные, так и отрицательные
элементы.
При отладке программ, использующих массивы, удобно заранее подготовить ис-
ходные данные в текстовом файле и считывать их в программе. Помимо всего про-
чего это дает возможность, не торопясь, продумать, какие данные требуется ввести
для полной проверки программы, и заранее рассчитать, что должно получиться
в результате.
Результат работы программы тоже бывает полезно выводить не на экран, а в тек-
стовый файл для последующего неспешного анализа. Работа с файлами подробно
рассматривается в главе 3 на с. 67, а в первой главе давались самые необходимые
сведения. В листинге С4.3 приведена версия предыдущей программы, использую-
щая файлы.
209
210 Часть II. Практикум
СОВЕТ
При отладке программы можно выводить одну и ту же информацию и на экран,
и в текстовый файл. Для этого каждую процедуру вывода дублируют.
210
Семинар 4. Одномерные массивы 211
создавать более надежные программы, а нет ничего более важного для программы,
чем надежность.
Если же стоит задача вводить неизвестное количество чисел до тех пор, пока не
будет введен какой-либо признак окончания ввода, то заранее выделить доста-
точное количество памяти не удастся и придется воспользоваться так называе-
мыми динамическими структурами данных, например списком. Мы рассмотрим
эти структуры на семинаре 8, а пока остановимся на первом предположении — что
количество элементов массива вводится с клавиатуры до начала ввода самих эле-
ментов.
Перейдем к созданию алгоритма решения задачи. По аналогии с предыдущей за-
дачей первым приходит в голову такое решение: просматривая массив с начала
до конца, найти номер последнего отрицательного элемента, а затем организовать
цикл суммирования всех элементов, расположенных правее него. Вот как выглядит
построенная по этому алгоритму программа (сразу же признаюсь, что она далеко не
так хороша, как может показаться с первого взгляда):
program sum_elem_1;
const n = 1000;
var a : array [1 .. n] of real;
i : integer; { номер текущего элемента }
ineg : integer; { номер последнего отрицательного элемента }
nf : integer; { фактическое количество элементов в массиве }
sum : real; { сумма элементов }
begin
writeln('Введите количество элементов');
readln(nf);
if nf > n then begin
writeln('Превышение размеров массива'); exit
end;
writeln('Введите элементы');
for i := 1 to nf do read(a[i]);
writeln('Исходный массив:'); { 1 }
for i := 1 to nf do write(a[i]:4); writeln; { 2 }
for i := 1 to nf do
if a[i] < 0 then ineg = i; { 3 }
sum := 0;
for i := ineg + 1 to nf do sum = sum + a[i];
writeln('Сумма: ', sum:7:2);
end.
Номер последнего отрицательного элемента массива формируется в переменной
ineg. При просмотре массива в эту переменную последовательно записываются но-
мера всех отрицательных элементов массива (оператор 3), таким образом, после
выхода из цикла в ней остается номер самого последнего элемента.
С целью оптимизации программы может возникнуть мысль объединить цикл на-
хождения номера последнего отрицательного элемента с циклами ввода и кон-
трольного вывода элементов массива, но я так делать не советую, потому что ввод
211
212 Часть II. Практикум
212
Семинар 4. Одномерные массивы 213
else begin
sum := 0;
for i := ineg + 1 to nf do sum = sum + a[i];
writeln('Сумма: ', sum:7:2);
end
end.
Если не останавливаться на достигнутом и подумать, можно предложить более ра-
циональное решение: просматривать массив в обратном порядке, суммируя его эле-
менты, и завершить цикл, как только встретится отрицательный элемент:
program sum_elem_3;
const n = 1000;
var a : array [1 .. n] of real;
i : integer; { номер текущего элемента }
nf : integer; { фактическое количество элементов в массиве }
sum : real; { сумма элементов }
is_neg : boolean; { признак наличия отрицательного элемента }
begin
writeln('Введите количество элементов');
readln(nf);
if nf > n then begin
writeln('Превышение размеров массива'); exit
end;
writeln('Введите элементы');
for i := 1 to nf do read(a[i]);
is_neg := false;
sum := 0;
for i := nf downto 1 do begin
if a[i] < 0 then begin is_neg := true; break end;
sum = sum + a[i];
end;
if is_neg then writeln('Сумма: ', sum:7:2);
else writeln('Отрицательных элементов нет');
end.
В этой программе каждый элемент массива анализируется не более одного раза,
а ненужные элементы не просматриваются вообще, поэтому для больших масси-
вов этот вариант предпочтительнее. Впрочем, если в процессоре поддерживается
опережающее считывание данных, он будет работать медленнее.
Для исчерпывающего тестирования этой программы необходимо ввести по край-
ней мере три варианта исходных данных для случаев, когда массив содержит один
элемент, более одного и ни одного отрицательного элемента.
ПРИМЕЧАНИЕ
Строго говоря, для обеспечения надежности этой программы следовало бы объявить
переменную, в которой хранится сумма в виде значения типа double, потому что сумма
величин типа real может выйти за границы диапазона его представления.
213
214 Часть II. Практикум
Составим тестовый пример, чтобы более наглядно представить себе алгоритм. Ис-
ходный массив:
6 –8 15 9 –1 3 5 –10 12 2
214
Семинар 4. Одномерные массивы 215
end;
a := b;
writeln('Преобразованный массив:');
for i := 1 to n do write(a[i]:4);
end.
Обнуление «хвоста» массива происходит естественным образом, поскольку в Па-
скале глобальные переменные обнуляются.
Однако для массивов большой размерности выделение двойного объема памяти
может оказаться слишком расточительным. Поэтому далее приводится вариант
программы, в которой преобразование массива выполняется «in situ», что по латы-
ни означает «на месте».
Алгоритм работы этой программы выглядит следующим образом.
1. Просмотрев массив, определить номер самого первого из удаляемых элемен-
тов.
2. Если таковой есть, сдвигать каждый последующий элемент массива на первое
«свободное» место, обнуляя оставшуюся часть массива.
Иллюстрация алгоритма приведена на рис. С4.2.
215
216 Часть II. Практикум
1
Наихудшим по характеристикам является любимый студентами метод пузырька.
216
Семинар 4. Одномерные массивы 217
ПРИМЕЧАНИЕ
Существует более простая реализация метода быстрой сортировки, основанная на
рекурсии.
217
218 Часть II. Практикум
218
Семинар 4. Одномерные массивы 219
Итоги
1. Массив не является стандартным типом данных, он задается в разделе описания
типов. Если тип массива используется только в одном месте программы, можно
задать тип прямо при описании переменных.
2. Размерность массива может быть только константой или константным выраже-
нием. Рекомендуется задавать ее с помощью именованной константы.
3. Тип элементов массива может быть любым, кроме файлового, тип индексов —
интервальным, перечисляемым или byte.
4. При описании массива можно задать начальные значения его элементов. При
этом он описывается в разделе описания констант.
5. С массивами в целом можно выполнять только одну операцию — присваивание.
При этом массивы должны быть одного типа. Все остальные действия выполня-
ются с отдельными элементами массива.
6. Автоматический контроль выхода индекса за границы массива по умолчанию не
выполняется. Можно включить его с помощью директивы {$R+}.
7. При работе с массивами удобно готовить исходные данные в текстовом файле
и считывать их в программе.
8. Существует много алгоритмов сортировки. Они различаются по быстродей-
ствию, занимаемой памяти и области применения.
Задания
Вариант 1
1. Найти сумму отрицательных элементов массива.
2. Найти произведение элементов массива, расположенных между максимальным
и минимальным элементами.
3. Упорядочить элементы массива по возрастанию.
219
220 Часть II. Практикум
Вариант 2
1. Найти сумму положительных элементов массива.
2. Найти произведение элементов массива, расположенных между максимальным
по модулю и минимальным по модулю элементами.
3. Упорядочить элементы массива по убыванию.
Вариант 3
1. Найти произведение элементов массива с четными номерами.
2. Найти сумму элементов массива, расположенных между первым и последним
нулевыми элементами.
3. Преобразовать массив таким образом, чтобы сначала располагались все положи-
тельные элементы, а потом — все отрицательные (элементы, равные 0, считать
положительными).
Вариант 4
1. Найти сумму элементов массива с нечетными номерами.
2. Найти сумму элементов массива, расположенных между первым и последним
отрицательными элементами.
3. Сжать массив, удалив из него все элементы, модуль которых не превышает 1.
Освободившиеся в конце массива элементы заполнить нулями.
Вариант 5
1. Найти максимальный элемент массива.
2. Найти сумму элементов массива, расположенных до последнего положительного
элемента.
3. Сжать массив, удалив из него все элементы, модуль которых находится в интер-
вале [a, b]. Освободившиеся в конце массива элементы заполнить нулями.
Вариант 6
1. Найти минимальный элемент массива.
2. Найти сумму элементов массива, расположенных между первым и последним
положительными элементами.
3. Преобразовать массив таким образом, чтобы сначала располагались все элемен-
ты, равные нулю, а потом — все остальные.
Вариант 7
1. Найти номер максимального элемента массива.
2. Найти произведение элементов массива, расположенных между первым и вто-
рым нулевыми элементами.
3. Преобразовать массив таким образом, чтобы в первой его половине располага-
лись элементы, стоявшие в нечетных позициях, а во второй половине — элемен-
ты, стоявшие в четных позициях.
Вариант 8
1. Найти номер минимального элемента массива.
220
Семинар 4. Одномерные массивы 221
221
222 Часть II. Практикум
Вариант 15
1. Найти количество элементов массива, больших величины С, введенной с кла-
виатуры.
2. Найти произведение элементов массива, расположенных после максимального
по модулю элемента.
3. Преобразовать массив таким образом, чтобы сначала располагались все отрица-
тельные элементы, а потом — все положительные (элементы, равные 0, считать
положительными).
Вариант 16
1. Найти количество отрицательных элементов массива.
2. Найти сумму модулей элементов массива, расположенных после минимального
по модулю элемента.
3. Заменить все отрицательные элементы массива их квадратами и упорядочить
элементы массива по возрастанию.
Вариант 17
1. Найти количество положительных элементов массива.
2. Найти сумму элементов массива, расположенных после последнего элемента,
равного нулю.
3. Преобразовать массив таким образом, чтобы сначала располагались все элементы,
целая часть которых не превышает 1, а потом — все остальные.
Вариант 18
1. Найти количество элементов массива, меньших величины С, введенной с кла-
виатуры.
2. Найти сумму целых частей элементов массива, расположенных после последнего
отрицательного элемента.
3. Преобразовать массив таким образом, чтобы сначала располагались все эле-
менты, отличающиеся от максимального не более чем на 20%, а потом — все
остальные.
Вариант 19
1. Найти произведение отрицательных элементов массива.
2. Найти сумму положительных элементов массива, расположенных до максималь-
ного элемента.
3. Изменить порядок следования элементов в массиве на обратный.
Вариант 20
1. Найти произведение положительных элементов массива.
2. Найти сумму элементов массива, расположенных до минимального элемента.
3. Упорядочить по возрастанию отдельно элементы, стоящие на четных местах,
и элементы, стоящие на нечетных местах.
222
Семинар 5. Двумерные массивы и подпрограммы
Теоретический материал: глава 3, с. 54–58, глава 4, с. 76–89, глава 6.
223
224 Часть II. Практикум
Изменим программу так, чтобы она находила указанную величину не для всей ма-
трицы, а для каждой ее строки. Алгоритм ее работы будет выглядеть так:
1. Ввести матрицу.
2. Для каждой строки матрицы:
224
Семинар 5. Двумерные массивы и подпрограммы 225
225
226 Часть II. Практикум
СОВЕТ
Записывайте операторы инициализации величин непосредственно перед циклом,
в котором они вычисляются.
Как обычно, для начала задумаемся об исходных данных и результатах работы про-
граммы. С исходными данными проблем не возникает: потребуется ввести 54 целых
числа и разместить их в двумерном массиве1.
Результатом работы программы являются 9 целых чисел — номера строк, в кото-
рых расположены нулевые элементы. Сразу же возникает вопрос: как программа
должна реагировать на отсутствие нулевого элемента в каком-либо столбце? Мож-
но выводить либо значение индекса, не входящего в множество допустимых, либо
текстовое сообщение. Остановимся на втором варианте как более гуманном по от-
ношению к пользователю программы.
Второй вопрос касается хранения результатов. Как и в предыдущей программе, есть
два варианта: отвести для всех результатов одну переменную и выводить ее после
анализа каждого столбца или организовать массив из девяти элементов. Остано-
вимся пока на первом варианте как более простом. Пример исходных данных и ре-
зультатов приведен ранее на рис. С5.1.
Основная идея алгоритма такова: просматривать матрицу по столбцам, при нахож-
дении элемента, равного нулю, вывести его номер и перейти к следующему столбцу.
Если же столбец просмотрен до конца, но ни одного нулевого элемента не найдено,
вывести текстовое сообщение и также перейти к следующему столбцу.
Схема алгоритма решения задачи приведена на рис. С5.2, а программа — в листин-
ге С5.3. Поскольку и при наличии, и при отсутствии нулевого элемента выполняет-
ся переход в одно и то же место программы, вводится булева переменная-флаг no_
nul, равная true, если в столбце нет нулевых элементов, и false в противном случае.
1
Нормальный, иными словами, ленивый, программист предпочтет приготовить данные
в файле, см. задачу С4.1.
226
Семинар 5. Двумерные массивы и подпрограммы 227
227
228 Часть II. Практикум
индекс первого нулевого элемента, либо слово «нет» при его отсутствии. Резуль-
тат выполнения программы для матрицы, представленной на рис. С5.1, приведен
ниже:
Столбец Номер элемента
1 3
2 1
3 нет
4 2
5 6
6 1
7 3
8 4
9 нет
Тестовые примеры для этой программы составляйте таким образом, чтобы столб-
цы массива содержали разное количество нулевых элементов (ни одного элемента,
один и более элементов, все элементы).
СОВЕТ
При написании вложенных циклов следите за отступами. Все операторы одного уров-
ня вложенности должны вводиться с одним и тем же отступом. Это облегчает чтение
программы и, следовательно, поиск ошибок. Исключение составляют расположенные
один за другим операторы циклов, просматривающие матрицу целиком (см., например,
цикл ввода матрицы в этой задаче).
Изменим эту программу так, чтобы она определяла номер столбца, в котором нуле-
вой элемент располагается «выше», то есть имеет минимальный индекс строки.
Для этого можно сформировать массив из результатов, а потом найти в нем номер
минимального элемента, при этом для тех столбцов, в которых нет нулевых эле-
ментов (см. рис. С5.1), придется занести в массив какое-либо значение, большее
максимального номера строки (листинг С5.4).
228
Семинар 5. Двумерные массивы и подпрограммы 229
break
end;
end;
jm := 1;
for j := 2 to n do { поиск номера минимального элемента }
if num[j] < num[jm] then jm := j;
if num[jm] = m + 1 then writeln(' В массиве вообще нет нулей! ')
else writeln(' Выше всего 0 расположен в столбце ', jm);
end.
В массиве num столько элементов, сколько столбцов в исходной матрице. При ана-
лизе j-го столбца формируется j-й элемент массива. Если предположить, что нам
не требуется выводить индексы нулевых элементов, можно обойтись без булевой
переменной no_nul. Вместо нее применяется другое решение, также весьма распро-
страненное: в элемент массива до начала работы заносится значение, не входящее
в диапазон допустимых для индекса строки, а затем, если нулевой элемент найден,
оно корректируется.
При выводе результата необходимо предусмотреть случай, когда в матрице нет ни
одного нулевого элемента. Если же нули расположены на одной высоте в несколь-
ких столбцах, наша программа выдаст номер первого из столбцов.
Применять дополнительный массив приходится, если индексы нулевых элементов
требуются для дальнейшей работы. В противном случае проще искать минимум
«на лету», по мере нахождения индексов нулевых элементов. При реализации та-
кого варианта нам потребуется одна дополнительная переменная im для хранения
минимального значения номера нулевого элемента в столбце (листинг С5.5).
229
230 Часть II. Практикум
230
Семинар 5. Двумерные массивы и подпрограммы 231
231
232 Часть II. Практикум
При выполнении первого шага алгоритма для каждой строки сначала выполняет-
ся обнуление суммы, а затем к ней по очереди добавляется каждый элемент стро-
ки. Пример исходных данных и сформированного массива сумм элементов каждой
строки приведен на рис. С5.4.
В нашей задаче требуется упорядочить строки матрицы, поэтому каждый раз одно-
временно с обменом элементов массива сумм мы будем выполнять и обмен значе-
ний двух соответствующих строк матрицы. Иными словами, мы жестко привяжем
каждую строку матрицы к соответствующему ей элементу массива сумм, как по-
казано на рис. С5.4.
1
Обратите внимание на этот факт: для поиска в массиве, как правило, достаточно просмо-
треть его один раз, а для сортировки выбором — n – 1 раз (n — количество элементов в мас-
сиве).
232
Семинар 5. Двумерные массивы и подпрограммы 233
III. Когда алгоритм стал полностью понятен, можно переходить к написанию про-
граммы. Как правило, отдельные шаги алгоритма оформляются в виде подпро-
грамм (мы рассмотрим подпрограммы в задачах С5.4 и С5.5). Одновременно (а еще
лучше — до того) продумываются и подготавливаются тестовые примеры.
Не нужно стремиться написать сразу всю программу. Сначала пишется и отлажи-
вается фрагмент, реализующий ввод исходных данных и их контрольный вывод.
Затем промежуточную печать можно убрать и переходить к следующему функцио-
нально законченному фрагменту алгоритма. Для отладки полезно выполнять про-
грамму по шагам с наблюдением значений изменяемых величин. Все популярные
оболочки предоставляют такую возможность. Отладка программы с использовани-
ем средств оболочки Turbo Pascal 7.0 описана в приложении 7.
Текст программы сортировки матрицы приведен в листинге С5.6.
233
234 Часть II. Практикум
1
Коды ошибок, выдаваемых функцией IOResult, приведены в приложении 4.
234
Семинар 5. Двумерные массивы и подпрограммы 235
матрицы в виде процедуры. Это позволит нам использовать ее для любой матрицы
такого же типа.
235
236 Часть II. Практикум
После принятия решения о том, что именно будет оформлено в виде подпрограм-
мы, надо продумать ее интерфейс — то, что должна получать подпрограмма извне
и выдавать в результате. Как видите, порядок разработки подпрограммы такой же,
как и программы в целом (см. задачу С5.3).
Интерфейс подпрограммы — это ее заголовок, в котором должно определяться все,
что нужно знать для ее использования. Например, чтобы воспользоваться стан-
дартной подпрограммой получения абсолютного значения величины, надо знать
имя этой подпрограммы, а также тот факт, что ей следует передать одну величину
целого или вещественного типа.
Наша процедура должна преобразовывать массив, следовательно, именно он и яв-
ляется ее параметром. Другим параметром является число, по которому «обрезают-
ся» элементы массива. Параметры описываются аналогично обычным переменным:
для них указываются имя и тип. Тип должен быть или стандартным, или введенным
ранее в разделе type, а значения параметры получают при вызове подпрограммы че-
рез аргументы. Итак, мы можем определить заголовок процедуры:
const n = 6;
type matr = array [1 .. n, 1 .. n] of real;
...
procedure sorting(var m : matr; x : real); { ---- заголовок процедуры ---- }
Алгоритм преобразования матрицы аналогичен рассмотренному в предыдущей за-
даче:
1. «Обрезать» элементы матрицы.
2. Найти минимальные элементы каждой строки и записать их в одномерный
массив.
3. Упорядочить этот массив по возрастанию, одновременно с перестановкой эле-
ментов меняя местами и соответствующие строки матрицы.
Обобщенная схема алгоритма решения задачи приведена на рис. С5.6. Соответ-
ствующая алгоритму программа представлена в листинге С5.7.
236
Семинар 5. Двумерные массивы и подпрограммы 237
1
Кстати, один из главных плодов высшего образования — именно умение структурировать
информацию.
237
238 Часть II. Практикум
В нашей подпрограмме описано пять локальных величин: min, nmin, buf, i и j. Все
они нужны только для преобразования матрицы. Обратите внимание на то, что
в главной программе также описаны переменные с именами i и j, использующиеся
для той же цели, что и в подпрограмме — для просмотра массива. В данной про-
грамме это избыточно, но представьте себе, что потребуется использовать эту под-
программу в другом окружении, например, вызывать ее в цикле. Если программи-
сту, применяющему вашу процедуру, захочется назвать переменную цикла i или j
(что вполне вероятно), ему придется долго соображать, отчего цикл выполняется
только один раз, а после этого он обязательно помянет вас незлым и тихим словом1.
Итак, еще раз.
ВНИМАНИЕ
Подпрограмма должна быть максимально независимой от главной программы. Для
этого следует по возможности отказаться от использования в подпрограмме глобаль-
ных переменных. Величины, которые нужны только внутри подпрограммы, описыва-
ются как локальные, а то, что необходимо подпрограмме извне, передается ей через
параметры.
ВНИМАНИЕ
При вызове подпрограммы должно быть записано столько аргументов, сколько параме-
тров в ее заголовке. Тип аргумента должен быть совместим с типом соответствующего
ему параметра. Порядок следования аргументов должен соответствовать порядку
следования параметров.
1
Будет особенно обидно, если этим программистом окажетесь вы сами.
238
Семинар 5. Двумерные массивы и подпрограммы 239
239
240 Часть II. Практикум
Очевидно, что для решения нашей задачи требуется подсчитать количество поло-
жительных элементов в двух массивах, то есть выполнить для обоих массивов одни
и те же действия. Следовательно, эти действия надо выполнять внутри функции.
Интерфейс функции: входные данные — массив, результат — количество положи-
тельных элементов в массиве (целочисленная величина):
const n = 10;
type mas = array [1 .. n] of integer;
function n_posit(const m : mas) : integer; { ---- заголовок функции ---- }
Массив является входным параметром, о чем свидетельствует служебное слово
const (см. предыдущую задачу). Если его не указать, ничего страшного не произой-
дет, но вместо передачи одной величины (адреса начала массива) в функцию будет
передан массив целиком.
ВНИМАНИЕ
Тип массива должен быть введен до описания подпрограммы в разделе описания типов
главной программы.
240
Семинар 5. Двумерные массивы и подпрограммы 241
i : integer;
function n_posit(const m : mas) : integer; { ---- заголовок функции ---- }
var i, k : integer;
begin
k := 0;
for i := 1 to n do if m[i] > 0 then inc(k);
n_posit := k;
end; { -------- конец функции ---- }
begin { --------------- раздел операторов главной программы -------- }
writeln('Введите элементы первого массива: ');
for i := 1 to n do read(a[i]);
writeln('Введите элементы второго массива:');
for i := 1 to n do read(b[i]);
if n_posit(a) > n_posit(b) then writeln('В первом положительных больше')
else if n_posit(a) < n_posit(b) then writeln('Во втором положительных больше')
else writeln(' Одинаковое количество');
end.
Заслуживает рассмотрения способ анализа результатов вызова функции. Как ви-
дите, функция вызывается в составе выражения в условном операторе. Для пере-
бора всех трех вариантов результата приходится вызывать ее для каждого массива
дважды, что для больших массивов, конечно, нерационально. Чтобы избежать по-
вторного вызова, можно завести две переменные, в которые записывать результаты
обработки обоих массивов, а затем использовать эти переменные в условных опе-
раторах:
var n_posit_a, n_posit_b : integer;
...
n_posit_a := n_posit(a);
n_posit_b := n_posit(b);
if n_posit_a > n_posit_b then writeln('В первом положительных больше')
else if n_posit_a < n_posit_b then writeln('Во втором положительных больше')
else writeln(' Одинаковое количество');
А вот как выглядит та же задача при использовании процедуры (листинг С5.9):
241
242 Часть II. Практикум
Итоги
1. В двумерных массивах обе размерности должны быть константами или констант-
ными выражениями.
2. Массив хранится по строкам в непрерывной области памяти.
3. Первый индекс всегда представляет собой номер строки, второй — номер столбца.
Каждый индекс может изменяться в пределах, указанных при его описании.
4. Логически законченные части программы оформляются в виде подпрограмм.
Подпрограмма может обрабатывать различные данные, переданные ей при вы-
зове в качестве аргументов.
5. При написании подпрограммы следует прежде всего продумать ее интерфейс
(заголовок).
6. Глобальные переменные определяются в разделах описаний главной программы.
Они обнуляются перед началом работы и существуют с момента запуска про-
граммы до ее завершения.
7. Локальные переменные объявляются внутри подпрограммы и доступны только
в ней и подпрограммах, вложенных в нее. Они автоматически не обнуляются
и существуют только во время выполнения подпрограммы.
8. Подпрограмма должна быть максимально независима от главной программы.
Для этого следует отказаться от использования в подпрограмме глобальных
переменных. Величины, которые нужны только внутри подпрограммы, описы-
242
Семинар 5. Двумерные массивы и подпрограммы 243
Задания
Размерности массивов следует задать именованными константами. Все необхо-
димые данные должны передаваться подпрограммам в качестве параметров; все
величины, используемые только внутри подпрограмм, должны быть описаны как
локальные. Использование глобальных переменных в подпрограммах не допуска-
ется. Вывод результатов работы подпрограмм должен выполняться в главной про-
грамме.
Вариант 1
Дана целочисленная прямоугольная матрица. Определить:
1. Количество строк, не содержащих ни одного нулевого элемента (оформить в виде
функции).
2. Максимальное из чисел, встречающихся в заданной матрице более одного pаза
(оформить в виде процедуры).
243
244 Часть II. Практикум
Вариант 2
Дана целочисленная прямоугольная матрица. Определить количество столбцов, не
содержащих ни одного нулевого элемента (оформить в виде функции).
Характеристикой строки целочисленной матрицы назовем сумму ее положитель-
ных четных элементов. Переставляя строки заданной матрицы, расположить их
в соответствии с ростом характеристик (оформить в виде процедуры).
Вариант 3
Дана целочисленная прямоугольная матрица. Определить:
1. Количество столбцов, содержащих хотя бы один нулевой элемент (оформить
в виде функции).
2. Номер строки, в которой находится самая длинная серия одинаковых элементов
(оформить в виде процедуры).
Вариант 4
Дана целочисленная квадратная матрица. Определить:
1. Произведение элементов в тех строках, которые не содержат отрицательных
элементов (оформить в виде функции).
2. Максимум среди сумм элементов диагоналей, параллельных главной диагонали
матрицы (оформить в виде процедуры).
Вариант 5
Дана целочисленная квадратная матрица. Определить:
1. Сумму элементов в тех столбцах, которые не содержат отрицательных элементов
(оформить в виде функции).
2. Минимум среди сумм модулей элементов диагоналей, параллельных побочной
диагонали матрицы (оформить в виде процедуры).
Вариант 6
Дана целочисленная прямоугольная матрица. Определить:
1. Сумму элементов в тех строках, которые содержат хотя бы один отрицательный
элемент (оформить в виде функции).
2. Номера строк и столбцов всех седловых точек матрицы (оформить в виде про-
цедуры).
ПРИМЕЧАНИЕ
Матрица А имеет седловую точку Аij, если Аij является минимальным элементом в i-й
строке и максимальным в j-м столбце.
Вариант 7
Для заданной матрицы размером 8 × 8 найти:
244
Семинар 5. Двумерные массивы и подпрограммы 245
1. Такие k, что k-я строка матрицы совпадет с k-м столбцом (оформить в виде про-
цедуры).
2. Сумму элементов в тех строках, которые содержат хотя бы один отрицательный
элемент (оформить в виде функции).
Вариант 8
1. Характеристикой столбца целочисленной матрицы назовем сумму модулей его
отрицательных нечетных элементов. Переставляя столбцы заданной матрицы,
расположить их в соответствии с ростом характеристик (оформить в виде про-
цедуры).
2. Найти сумму элементов в тех столбцах, которые содержат хотя бы один отрица-
тельный элемент (оформить в виде функции).
Вариант 9
Соседями элемента Аij в матрице назовем элементы Аkl, где i – 1 ≤ k ≤ i + 1,
j – 1 ≤ l ≤ j + 1, (k, l) ≠ (i, j). Операция сглаживания матрицы дает новую матрицу
того же размера, каждый элемент которой получается как среднее арифметическое
имеющихся соседей соответствующего элемента исходной матрицы.
1. Построить результат сглаживания заданной вещественной матрицы размером
10 × 10 (оформить в виде процедуры).
2. В сглаженной матрице найти сумму модулей элементов, расположенных ниже
главной диагонали (оформить в виде функции).
Вариант 10
Элемент матрицы называется локальным минимумом, если он строго меньше всех
имеющихся у него соседей (определение соседних элементов см. в варианте 9).
1. Подсчитать количество локальных минимумов заданной матрицы размером
10 × 10 (оформить в виде процедуры).
2. Найти сумму модулей элементов, расположенных выше главной диагонали
(оформить в виде функции).
Вариант 11
1. Коэффициенты системы линейных уравнений заданы в виде прямоугольной
матрицы. С помощью допустимых преобразований привести систему к треуголь-
ному виду (оформить в виде процедуры).
2. Найти количество строк, среднее арифметическое элементов которых меньше
заданной величины (оформить в виде функции).
Вариант 12
1. Уплотнить заданную матрицу, удалив из нее строки и столбцы, заполненные
нулями (оформить в виде процедуры).
2. Найти номер первой из строк, содержащих хотя бы один положительный элемент
(оформить в виде функции).
245
246 Часть II. Практикум
Вариант 13
Осуществить циклический сдвиг элементов прямоугольной матрицы на n элемен-
тов вправо или вниз (в зависимости от введенного режима). n может быть больше
количества элементов в строке или столбце (оформить в виде процедуры).
Вариант 14
Осуществить циклический сдвиг элементов квадратной матрицы размером M × N
вправо на k элементов таким образом: элементы первой строки сдвигаются в по-
следний столбец сверху вниз, из него — в последнюю строку справа налево, из
нее — в первый столбец снизу вверх, из него — в первую строку; для остальных
элементов — аналогично (оформить в виде процедуры).
Вариант 15
1. Дана целочисленная прямоугольная матрица. Определить номер первого из
столбцов, содержащих хотя бы один нулевой элемент (оформить в виде функ-
ции).
2. Характеристикой строки целочисленной матрицы назовем сумму ее отрицатель-
ных четных элементов. Переставляя строки заданной матрицы, расположить их
в соответствии с убыванием характеристик (оформить в виде процедуры).
Вариант 16
1. Упорядочить строки целочисленной прямоугольной матрицы по возрастанию
количества одинаковых элементов в каждой строке (оформить в виде процеду-
ры).
2. Найти номер первого из столбцов, не содержащих ни одного отрицательного
элемента (оформить в виде функции).
Вариант 17
1. Путем перестановки элементов квадратной вещественной матрицы добиться того,
чтобы ее максимальный элемент находился в левом верхнем углу, следующий по
величине — в позиции (2, 2), следующий по величине — в позиции (3, 3) и т. д.,
заполнив таким образом всю главную диагональ (оформить в виде процедуры).
2. Найти номер первой из строк, не содержащих ни одного положительного эле-
мента (оформить в виде функции).
Вариант 18
Дана целочисленная прямоугольная матрица. Определить:
1. Количество строк, содержащих хотя бы один нулевой элемент (оформить в виде
функции).
2. Номер столбца, в котором находится самая длинная серия одинаковых элементов
(оформить в виде процедуры).
Вариант 19
Дана целочисленная квадратная матрица. Определить:
246
Семинар 5. Двумерные массивы и подпрограммы 247
ПРИМЕЧАНИЕ
Матрица А имеет седловую точку Аij, если Аij является минимальным элементом в i-й
строке и максимальным в j-м столбце.
247
Семинар 6. Строки, записи, модуль Crt
Теоретический материал: глава 3, с. 58–64, глава 4, с. 89–96.
248
Семинар 6. Строки, записи, модуль Crt 249
249
250 Часть II. Практикум
Даже такую простую программу лучше вводить и отлаживать по шагам. Это умение
пригодится вам в дальнейшем. Предлагаемая последовательность отладки:
1. Ввести «скелет» программы (заголовок программы, описание переменных, от-
крытие файла с проверкой успешности и ввод с клавиатуры, то есть все операто-
ры до помеченного цифрой 5). Добавить контрольный вывод введенной после-
довательности символов. Запустив программу, проверить успешность открытия
файла и ввода. Выполнить программу, задав имя несуществующего файла для
проверки вывода сообщения об ошибке. Удалить контрольный вывод.
2. Проверить цикл чтения из файла: добавить заголовок и конец цикла 5, внутри
цикла поместить только оператор ввода строки, дополнив его контрольным вы-
водом:
writeln(line);
Убедившись, что файл считывается верно, удалить контрольный вывод строки.
3. Дополнить программу оставшимися операторами. Для полной проверки про-
граммы следует выполнить ее для нескольких последовательностей. Длина
одной из них должна составлять максимально допустимую — 80 символов.
Продуманная пошаговая отладка позволяет провести ее быстро, эффективно и с удо-
вольствием1.
250
Семинар 6. Строки, записи, модуль Crt 251
251
252 Часть II. Практикум
252
Семинар 6. Строки, записи, модуль Crt 253
253
254 Часть II. Практикум
254
Семинар 6. Строки, записи, модуль Crt 255
ПРИМЕЧАНИЕ
Для повышения быстродействия программы можно завести переменную для хранения
длины искомого слова и передавать ее в функцию, однако это сделает ее интерфейс
менее лаконичным.
255
256 Часть II. Практикум
ПРИМЕЧАНИЕ
Строго говоря, для решения этой конкретной задачи запись о сотруднике может
быть просто строкой символов, из которой при необходимости выделяется подстрока
с окладом, преобразуемая затем в число, но мы для общности и удобства дальнейшей
модификации программы будем использовать тип record.
256
Семинар 6. Строки, записи, модуль Crt 257
257
258 Часть II. Практикум
ВНИМАНИЕ
При заполнении массива из файла обязательно контролируйте выход за границы
массива и при необходимости выдавайте предупреждающее сообщение.
258
Семинар 6. Строки, записи, модуль Crt 259
ПРИМЕЧАНИЕ
В реальных задачах размеры файлов могут быть весьма значительными. В этом слу-
чае либо хранят содержимое файла в динамической памяти (см. семинары 8, 9 и 10),
либо организуют многократное чтение информации из файла в буфер небольшого
размера.
259
260 Часть II. Практикум
260
Семинар 6. Строки, записи, модуль Crt 261
261
262 Часть II. Практикум
1
О заглушках рассказывается в главе 6 и в задаче С6.2.
262
Семинар 6. Строки, записи, модуль Crt 263
263
264