Министерство общего и профессионального образования Российской Федерации Воронежская государственная лесотехническая ака...
3 downloads
113 Views
406KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Министерство общего и профессионального образования Российской Федерации Воронежская государственная лесотехническая академия
ИНФОРМАТИКА Основы программирования на языке Паскаль Тексты лекций Часть 2
Воронеж − 1999
УДК 519.682.1 Стариков А.В. ИНФОРМАТИКА. Основы программирования на языке Паскаль : Тексты лекций в 3-х частях. Часть 2. Воронеж, 1999. − 64 с.
Вторая часть текстов лекций содержит информацию о структурных типах данных, файловом вводе-выводе, подпрограммах и механизмах передачи параметров в подпрограммы. Тексты лекций по основам программирования на языке Паскаль предназначены для студентов специальности 060800 − “Экономика и управление на предприятиях лесного комплекса”. Ил. 3. Табл. 2.
Печатается по решению редакционно-издательского совета ВГЛТА
Рецензенты:
кафедра прикладной математики и экономико-математических методов ВГТА, д-р техн. наук, проф. ВГТУ Кравец О.Я.
УДК 519.682.1 Стариков А.В., 1999 Оформление. Воронежская государственная лесотехническая академия, 1999
3 Лекция 6. Составные типы данных языка Паскаль. Регулярные типы (массивы). Примеры программ, использующих регулярные типы данных. Строковый тип данных (строки символов). Пример программы, использующей строковый тип данных. Составные типы данных До сих пор мы рассматривали простые типы данных (порядковые и вещественные), которые не имеют выраженной структуры, а определяют различные множества унитарных (или атомарных), т.е. не поддающихся дальнейшему дроблению, значений. Однако в Паскале предусмотрена возможность создания и использования так называемых составных (сложных или структурных) типов, которые состоят из других типов – подобно тому, как составные операторы состоят из других операторов. В данной лекции мы рассмотрим два таких типа: регулярный тип (или массив) и строковый тип (или строка символов). Массивы Массив – это одна из наиболее распространенных структур данных, использующихся в языках программирования. Массив представляет собой регулярную структуру данных, так как все элементы массива имеют один и тот же тип, называемый базовым типом. Кроме того, массив является также структурой с так называемым произвольным доступом, поскольку все его элементы одинаково доступны и могут выбираться в произвольном порядке. Для обозначения отдельного элемента к имени всего массива (групповое имя) добавляется индекс, позволяющий выбрать необходимый элемент. Для корректного определения регулярного типа (массива) требуется указать две характеристики: тип элементов массива (базовый тип) и тип индекса массива (последняя характеристика определяет количество и “способ нумерации” элементов массива). Определение регулярного типа имеет следующий вид: type регулярный-тип = array [тип-индекса] of базовый-тип; где регулярный-тип – определяемый регулярный тип (массив), тип-индекса – тип индекса массива, базовый-тип – тип элементов массива. Слова array и of являются зарезервированными (служебными) словами языка Паскаль. В качестве типа индекса может выступать любой дискретный тип, кроме longint и ограниченных типов, построенных из типа longint; в частности, допустимы перечислимые и ограниченные типы данных. Элементы массива могут иметь любой тип данных. Например, type Direction = (x, y, z);
4 Vector = array [Direction] of real; var v : Vector; Norm : real; begin v[x] := 1.5; v[y] := 2.1; v[z] := 3.7; Norm := sqr(v[x])+sqr(v[y])+sqr(v[z]); { Норма вектора } writeln(Norm) end. Типом индексов регулярного типа Vector (вектор) является перечислимый тип Direction (направление), типом элементов массива – real (вещественный). Отметим некоторые особенности массивов в языке Паскаль. В Паскале количество элементов массива всегда должно быть фиксировано, т.е. определено при компиляции программы. С одной стороны это можно расценивать как некоторый недостаток языка, поскольку не во всех программах можно заранее предсказать необходимый размер массива (который может определяться в зависимости от тех или иных условий, возникающих в процессе исполнения программы) и поэтому приходится выделять память под массив с “запасом”. С другой стороны подобное ограничение обеспечивает возможность более строгого контроля программы на этапе компиляции, что способствует, в конечном итоге, повышению ее надежности. В Паскале для задания количества элементов массива используется тип данных индекса. Количество элементов массива определяется множеством значений указанного типа, что отличает Паскаль от большинства других языков программирования, в которых размер массива задается либо целым числом (или выражением целого типа), либо диапазоном целых чисел. Такая особенность языка Паскаль придает ему дополнительную гибкость, позволяя “нумеровать” элементы массива не только целыми числами, но и значениями произвольного дискретного типа. Например, type LogicTable = array [char] of boolean; var Code : integer; Alpha : LogicTable; { Булевский массив, у которого индексом является символьное значение } begin for Code := 0 to 255 do if ((chr(Code) >= 'A') and (chr(Code) <= 'Z')) or ((chr(Code) >= 'a') and (chr(Code) <= 'z')) then Alpha[chr(Code)] := true else
5 Alpha[chr(Code)] := false; for Code := 0 to 255 do writeln(Alpha[chr(Code)]) end. Как уже отмечалось, в качестве элементов массива могут выступать значения любого типа; в частности, ими могут быть значения любых составных типов, например, массивы: var Matrix : array [1..10] of array [1..20] of byte; Переменная Matriх допускает двоякое толкование: как массив, элементы которого в свою очередь являются массивами, т.е. массив, состоящий из нескольких массивов, или как двумерный массив (матрицу). В подобных случаях, как правило, используют сокращенную эквивалентную форму записи двумерных (в общем случае – многомерных) массивов, предусматривающую заключение в квадратные скобки списка типов индексов, разделенных запятыми. Например, var Matrix : array [1..10, 1..20] of byte; Количество индексов в определении массива, называемое размерностью массива, в языке Паскаль не ограничено. Однако, при работе с массивами в Турбо-Паскале следует иметь в виду ограничение на размер общей памяти, отводимой под переменные, в 64K; чаще всего этот предел оказывается превышен из-за ошибочного описания массивов (либо состоящих из чрезмерного числа элементов, либо больших размеров этих элементов). Паскаль допускает единственную операцию над массивом в целом: в операторе присваивания, когда значение одного массива присваивается другому массиву того же типа. Например, v := u; где v и u оба имеют тип Vector (см. пример выше). Этот оператор эквивалентен следующей группе операторов: v[x] := u[x]; v[y] := u[y]; v[z] := u[y]; Алгоритмы, связанные с массивами, вероятно, представляют наиболее важную и интересную группу алгоритмов в программировании. Массивы традиционно использовались для представления списков элементов данных. Поэтому алгоритмы, связанные с обработкой списков, обычно используют понятие массива. Ниже приведены тексты нескольких программ, использующих массивы для решения поставленных задач. Примеры программ, использующих массивы
6 Задача 1. Ввести с клавиатуры 25 целых чисел и отобразить их на экране в порядке, обратном порядку их ввода. program BackOrder; const N = 25; type ListOfInt = array [1..N] of integer; var M : ListOfInt; { Массив целых значений } i : 1..N; { Индекс массива } begin for i := 1 to N do { Ввод значений } read(M[i]); for i := N downto 1 do {Вывод в порядке, обратном порядку ввода} write(M[i]) end. Задача 2. Объявить в программе двумерный целый массив 4×5 элементов, заполнить его значениями, введенными с клавиатуры, и вывести на экран в виде прямоугольной матрицы. program Matrix_4x5; const M = 4; { Количество строк } N = 5; { Количество столбцов } var Matrix: array [1..M, 1..N] of integer; { Матрица целых чисел } i : 1..M; { Индекс по строкам } j : 1..N; { Индекс по столбцам } begin for i := 1 to M do { Ввод значений для элементов массива } for j := 1 to N do begin write('Мatrix[', i, ',', j,'] = '); readln(Matrix[i,j]) end; for i := 1 to M do { Вывод значений элементов массива } begin for j := 1 to N do write(Matrix[i,j]:7); writeln end end. Задача 3. Отыскать минимальное и максимальное значения среди элементов заданного массива целого типа.
7 { Поиск минимального и максимального значений элементов массива } program MinMax; const MaxSize = 20; { Количество элементов массива } type ListSize = 1..MaxSize; var Index : ListSize; { Индекс элемента массива } Min, Max : integer; List : array [ListSize] of integer; begin for Index := 1 to MaxSize do { Ввод значений элементов массива } begin write('List[',Index:2,']= '); readln(List[Index]) end; Min := List[1]; Max := Min; for Index := 2 to MaxSize do begin if List[Index] > Max then Max := List[Index]; if List[Index] < Min then Min := List[Index] end; writeln('Min = ', Min); writeln('Max = ', Max) end. Примечание. Эту программу можно реализовать более эффективно, если совместить циклы ввода и поиска минимального и максимального элементов, т.е. записать ее так, как показано ниже. { Поиск мин. и макс. значений в массиве − модифицированная версия } program MinMax; const MaxSize = 20; { Количество элементов массива } type ListSize = 1..MaxSize; var Index : ListSize; { Индекс элемента массива } Min, Max : integer; List : array [ListSize] of integer; begin Min := MaxInt; Max := -MaxInt; for Index := 1 to MaxSize do { Ввод значений элементов массива } begin write('List[',Index:2,']= ');
8 readln(List[Index]) if List[Index] > Max then Max := List[Index]; if List[Index] < Min then Min := List[Index] end; writeln('Min = ', Min); writeln('Max = ', Max) end. Задача 4. Так же, как и в предыдущей задаче, отыскать минимальный и максимальный элемент массива, но дополнительно указать их местоположение в массиве, т.е. распечатать их индексы. { Поиск минимального и максимального значений элементов массива } program MinMax2; const MaxSize = 20; { Количество элементов массива } type ListSize = 1..MaxSize; var Index : ListSize; { Индекс массива } Min, Max : integer; Imax, Imin : 1..ListSize; List : array [ListSize] of integer; begin for Index := 1 to MaxSize do { Ввод значений элементов массива } begin write('List[',Index:2,']= '); readln(List[Index]) end; Min := List[1]; Max := Min; for Index := 2 to MaxSize do begin if List[Index] > Max then begin Max := List[Index]; Imax := Index end; if List[Index] < Min then begin Min := List[Index]; Imin := Index end end; writeln('Min = ', Min, ' Imin = ', Imin); writeln('Max = ', Max, ' Imax = ', Imax)
9 end. В задачах 3 и 4 поиск выполнялся в неупорядоченных списках. Часто списки делаются упорядоченными, т.е. значения, которые в них содержатся, организуются согласно алфавитному или цифровому порядку. Поиск в упорядоченных списках может быть выполнен гораздо эффективнее, чем в неупорядоченных списках. Упорядочение информации в списке называется сортировкой. Если значения в списке увеличиваются с начала списка к его концу, то говорят, что список отсортирован в порядке возрастания (или по возрастанию) значений; если значения уменьшаются, то говорят, что список отсортирован в порядке убывания (или по убыванию) значений. Существует множество методов сортировки списков. Ниже приведены тексты двух программ, выполняющих сортировку списка значений по возрастанию и поиск заданной величины в отсортированном списке. Задача 5. Пусть дан числовой массив x1, x2, ..., xn, элементы которого попарно различны. Используя алгоритм сортировки выбором, упорядочить элементы массива по возрастанию, т.е. так, чтобы x1 < x2 < ...< xn. Алгоритм сортировки выбором основывается на том, что каждый раз в подсписке (первоначально − это весь список) отыскивается минимальный элемент и перемещается в начало списка; при этом подсписок для очередного поиска укорачивается на 1, т.е. из него исключается уже использованный элемент. program SelectSort; const N = 10; { Количество элементов списка } var List : array [1..N] of real; Temp : real; i, j, k : 1..N; begin for i := 1 to N do { Ввод элементов списка } begin write('List[', i, ']='); read(List[i]) end; for i := 1 to N do { Сортировка элементов списка } begin k := i; for j := i+1 to N do if List[j] < List[k] then k := j; Temp := List[i]; { Минимальный в начало подсписка } List[i]:=List[k]; List[k] := Temp end; for i := 1 to N do { Вывода элементов отсортированного списка } writeln('List[', i, ']=', List[i])
10 end. Задача 6. Написать программу для отыскания указанного элемента в упорядоченном списке значений, т.е. эта программа должна либо указать местоположение значения в списке, либо выдать сообщение о том, что такое значение в списке отсутствует. Для решения задачи используем метод двоичного поиска, который основывается на последовательном разбиении области поиска (первоначально − это весь список) на два равных интервала, сравнении элемента в точке разбиения с заданным значением и, если они различны, выборе очередного интервала для разбиения. Процесс деления области поиска заканчивается в двух случаях, а именно: 1) область поиска “стянута” в точку, т.е. далее не может подвергнуться делению на интервалы, или 2) в списке обнаружен искомый элемент. program BinarySearch; const N = 10; { Количество элементов в списке } var List : array [1..N] of real; { Список значений } Low, High : 1..N; { № элементов, образующих границы интервала } Found : boolean; { Флажок “Элемент найден” } X : real; { Для хранения введенного значения } Pos : 1..N; { Индекс “срединного” элемента } begin for i := 1 to N do { Ввод упорядоченных элементов списка } read(List[i]); read(X); { Ввод искомого значения } Low := 1; { Первоначально область поиска - весь список } High := N; Found := FALSE; { Искомый элемент пока не обнаружен } { Пока область поиска не “стянута” в точку и не найдено введенное значение выполнять ... } while (Low <= High ) and not Found do begin { для каждого интервала } Pos := (Low + High) div 2; { Найти середину интервала} { Посмотреть, находится ли X перед этой позицией, т.е. слева, или за этой позицией, т.е. справа } if X < List[Pos] then { слева } High := Pos -1 { Исключить правую половину интервала } else if X > List[Pos] then { справа } Low := Pos + 1 { Исключить левую половину интервала } else Found := TRUE { Элемент найден! } end; if Found then { Если элемент найден, то ... }
11 writeln('Элемент ', X, ' находится в ', Pos, '-й позиции списка ') else { Элемент не найден } writeln('Элемент ', X, ' отсутствует в списке ') end. Строковый тип Одним из наиболее полезных и часто используемых расширений стандартного языка Паскаль, реализованных в Турбо-Паскале, является строковый тип данных. Строковый тип данных обобщает понятие символьных массивов (array [1..N] of char) за счет обеспечения возможности динамического изменения длины символьной строки. Строковый тип данных определяет множество символьных последовательностей произвольной длины (от 0 символов до заданного количества). В определении строкового типа данных используется зарезервированное слово string (строка), вслед за которым в квадратных скобках указывается максимальная длина строки; например: type Line : string [80]; var ScreenLine : Line; В данном примере строковая переменная ScreenLine может иметь в качестве своего значения любую последовательность символов, длина которой лежит в диапазоне от 0 до 80 символов. Любое из этих значений может быть присвоено строковой переменной ScreenLine в операторе присваивания или введено с клавиатуры: ScreenLine := 'Это пример строки'; readln(ScreenLine); Максимальная длина символьной строки, определенная в Турбо-Паскале, равна 255 символам. Если в определении строки длина опущена, то в этом случае подразумевается (говорят также – используется по умолчанию) число 255, т.е. следующие два определения строковых типов эквивалентны: type Line1 = string; Line2 = string [255]; Важнейшее отличие символьных строк от одномерных символьных массивов состоит в том, что строки могут динамически изменять свою длину. Например, если после выполнения оператора присваивания Line1 := 'Короткая строка'; длина строки Line составляет 15 символов, то следующий оператор присваивания: Line1 := Line1 + ' стала длиннее'; увеличит ее длину до 29 символов.
12 Если строковой переменной присваивается строковое выражение с длиной, превышающей максимально допустимую длину для данной переменной, то происходит отсечение строки до максимально возможной. Эта ситуация не расценивается как ошибочная и выполнение программы не прерывается; например, следующая программа: var ShortStr : string [5]; begin ShortStr := 'Очень длинная строка'; writeln(ShortStr) end. выведет на экран слово “Очень”. Механизм динамических строк, реализованный в Турбо-Паскале, несложен и заключается в том, что память под переменные строкового типа отводится по максимуму, а используется лишь часть этой памяти, реально занятая символами в данный момент. Так, например, при задании в программе строковой переменной с длиной, равной N символам, компилятор распределяет для нее N+1 байт памяти, из которых N байт предназначены для хранения символов строки, а один байт – для значения текущей длины строки (рис. 3).
0 1
2 3 4
5
6 7
...
k k+1 ... n-1 n
xx xx xx xx xx xx xx
...
xx
занятая часть строки
... незанятая часть строки
значение k – текущая длина строки Рис. 3. Байты (символы) в строке нумеруются целыми числами, начиная с единицы. Иногда, правда, используют нулевой байт строки, содержащий текущую длину, например: L := Ord(Line[0]); хотя для этой же цели целесообразнее использовать стандартную функцию Length, которая возвращает целое значение – длину строкового параметра, т.е. L := Length(Line); Кроме операции конкатенации над значениями строкового типа выполняются операции отношения (сравнения): < меньше <= меньше или равно > больше >= больше или равно
13 = равно <> не равно. При этом выполняются следующие правила: 1) если длины сравниваемых строк различны, то более короткая строка меньше более длинной; 2) если длины сравниваемых строк равны, то происходит посимвольное сравнение этих строк с учетом лексикографической упорядоченности значений стандартного символьного типа char. Доступ к отдельным элементам строк производится аналогично доступу к элементам одномерного символьного массива, т.е. после имени строковой переменной в квадратных скобках необходимо указать выражение целого типа, обозначающее номер элемента строки. Такая конструкция имеет тип char и является переменной, т.е. она может находиться в левой части оператора присваивания. Например: var MyLine : string; i, Count : integer; begin readln(MyLine); for i := 1 to Length(MyLine) do if ((Myline[i]>='A') and (Myline[i]<='Z')) or ((Myline[i]>='a') and (Myline[i]<='z')) then Count := Count + 1; writeln('В строке ', Count, ' латинских букв') end. Вопросы и упражнения для самопроверки 1. В чем различие между простыми и составными типами данных? Что представляет простой тип данных? Составной тип данных? 2. Почему массив часто называют регулярным типом данных? В чем собственно заключается эта регулярность? 3. Каким образом в программе можно указать количество элементов массива и их тип данных (базовый тип массива)? 4. Что представляет собой “массив массивов”? Привести примеры. 5. Каким образом в программе можно адресоваться к элементам массива? Привести примеры. 6. Может ли в качестве типа индекса массива использоваться тип longint? Почему? 7. Пусть в программе имеются следующие описания: type
14 Vector = array [1..30] of real; var A : Vector; B : array [‘0’..’9’] of Vector; C : array [char] of Vector; Для каждого из массивов А, B и C указать: • количество элементов; • значения, которые могут принимать элементы; • запись для адресации к первому и последнему элементу. 8. Написать программу для ввода с клавиатуры целых значений в квадратную матрицу 5×5 элементов и подсчета суммы значений элементов, находящихся на главной диагонали матрицы (главную диагональ матрицы, ориентированную с верхнего левого в правый нижний угол матрицы, образуют элементы, имеющие одинаковые индексы строки и столбца). 9. Модифицировать предыдущую программу (см. Упражнение 8) так, чтобы 1) заполнение матрицы целыми значениями выполнялось автоматически (использовать для этой цели функцию Random модуля System, генерирующую случайные числа из указанного диапазона значений); 2) выполнялся подсчет суммы значений элементов, образующих вспомогательную диагональ, ориентированную от левого нижнего к правому верхнему углу матрицы. 10. Написать программу, моделирующую игру “Крестики-нолики” в поле 3×3 клетки. В качестве одного игрока должен выступать пользователь, в качестве другого − программа. Программа должна 1) отображать состояние игрового поля после каждого шага игроков, выводя на экран позиции, заполненные символом ‘X’ (крестик) и ‘0’ (нолик); 2) фиксировать выигрыш одного из игроков или ничейную ситуацию. С целью упрощения решения задачи можно ввести следующее ограничение: первый игрок, т.е. пользователь, всегда имеет фору − право первого хода. Лекция 7. Комбинированный тип данных (записи). Фиксированные записи. Записи с вариантами. Примеры программ, использующих записи. Оператор присоединения (WITH). Пример программы, использующей оператор WITH. Вопросы и упражнения для самопроверки. Комбинированный тип данных (записи) Продолжим рассмотрение составных типов данных, имеющихся в Паскале. Наиболее общий метод построения данных сложной структуры заключается в объединении компонент, принадлежащих к различным типам, в один составной тип, называющийся комбинированным типом или записью. Записи используются при решении многих задач с помощью ЭВМ. Примеры из матема-
15 тики – комплексные числа, состоящие из двух вещественных чисел (действительная и мнимая части); координаты точек, состоящие из двух или более вещественных чисел в зависимости от размерности пространства заданной системы координат (абсцисса, ордината и т.д.). Пример из области социологии или статистики – листы анкеты, которые содержат сведения о людях, включающие ряд наиболее существенных характеристик, таких, как фамилия, имя и отчество, дата рождения, пол, семейное положение и другие. В Паскале для представления такой разнородной (как в последнем примере), но логически связанной информации используется комбинированный тип или запись. Существуют две разновидности записей – фиксированные записи и записи с вариантной частью (или записи с вариантами). Фиксированные записи Формально, запись – это структура данных, состоящая из фиксированного числа компонент, называемых полями, которые могут иметь различные типы данных (последнее свойство отличает записи от массивов). В определении записи для каждой из ее компонент указывается тип данных и имя (имя поля), обозначающего эту компоненту. Область действия имени поля – самая внутренняя запись (поскольку записи могут быть вложенными), в которой это имя определено. В общем виде определение комбинированного типа (записи) представляется следующим образом: type имя-записи = record имя-поля-1: тип-данных-1; имя-поля-2: тип-данных-2; ... имя-поля-N: тип-данных-N end где имя-записи – имя комбинированного типа данных (записи); имя-поля-1, имяполя-2, ..., имя-поля-N – имена полей (компонент) записи; тип-данных-1, типданных-2, ..., тип-данных-N – типы данных для соответствующих полей записи. Примеры комбинированных типов данных (записей): 1. Тип Complex предназначен для работы с комплексными числами (в Паскале отсутствует стандартный комплексный тип). type Complex = record Re : real; Im : real end; var C : Complex; { Комплексная переменная } 2. Тип Date соответствует понятию "дата"; каждое значение этого типа объединяет в себе три значения, а именно: число месяца, месяц и год.
16 type Date = record Day : 1..31; Month : (jan,feb,mar,apr,may,jun,jul,aug,sep, oct, nov, dec); Year : 1900..2100 end; var D : Date; { Переменная типа "дата" } 3. Тип Planet предназначен для создания описания планет Солнечной системы; описание каждой из планет содержит следующую информацию: название планеты, видна ли невооруженным глазом (да/нет), диаметр планеты и средний радиус орбиты. type Planet = record Name : string [15]; View : boolean; Diameter, Radius : real end; var InterPlan, ExterPlan : Planet; 4. Тип Person предназначен для создания "банка данных", содержащих информацию о какой-либо группе людей (например, о студентах академии). type Person = record Name : string [15]; Firstname : string [15]; Patronymic : string [15]; BirthDate : Date; { см. выше определение типа Date } Sex : (male, female); MarStatus : (single, married); Faculty : (tdo, lif, lmf, lhf); Course : 1..5; AverBall : real end; var Student : Person; Для обращения к компоненте (полю) записи используют следующее обозначение: имя-переменной-комбинированного-типа.имя-поля-записи т.е. вначале записывают имя переменной комбинированного типа, затем ставят точку и, наконец, записывают имя соответствующего поля записи. Подобные имена называются селекторами записи и в программах они используются так же, как имена переменных других типов. Например, C.Re := 5; { Присвоим комплексной } C.Im := 3; { переменной C значение 5+3i }
17 Для переменной Student, имеющей тип Person, могут иметь место следующие присваивания: Student.Name := 'Иванов'; Student.Firstname := 'Иван'; Student.Patronymic := 'Иванович'; Student.BirthDate.Day := 1; Student.BirthDate.Month := jan; Student.BirthDate.Year := 1977; Student.Sex := male; Student.MarStatus := single; Student.Faculty := tdo; Student.Course := 1; Student.AverBall := 4.55; Для комбинированного типа Planet и переменных InterPlan и ExterPlan, имеющих этот тип, действительны следующие присваивания: InterPlan.Name := 'Венера'; InterPlan.View := true; InterPlan.Diameter := 12104; { километров } InterPlan.Radius := 108.2; { гигаметров, т.к. описано как real } ExterPlan.Name := 'Нептун'; ExterPlan.View := false; ExterPlan.Diameter := 49500; { километров } ExterPlan.Radius := 4496.6; { гигаметров } В программе составные типы (массивы и записи) могут комбинироваться, т.е. может быть определен массив, у которого элементами являются записи, и может быть определена запись, у которой одно или несколько полей являются массивами. Например, const Distant = 10; { Номер самой дальней планеты } type Planet = record Name : string [15]; View : boolean; Diameter, Radius : real end; var SunSystem : array [1..Distant] of Planet; Number : 1..Distant; begin for Number := 1 to Distant do if SunSystem[Number].Radius < 4000 then SunSystem[Number].View := true { видна } else SunSystem[Number].View := false; { не видна }
18 ... end. Примечание. Оператор цикла в примере, приведенном выше, можно без всякого ущерба для смысла записать в следующей сокращенной форме: for Number := 1 to Distant do SunSystem[Number].View :=SunSystem[Number].Radius < 4000; Имена полей внутри записи не должны повторяться. За пределами записи можно использовать имена, совпадающие с именами полей (например, для обозначения имен полей другой записи или других переменных). В Паскале отсутствуют какие-либо другие операции, за исключением присваивания, которые оперируют с целой записью. Возвращаясь к примеру с внутренними и внешними планетами (по отношению к Земле), можно записать InterPlan := ExterPlan что эквивалентно следующим операторам присваивания: InterPlan.Name := ExterPlan.Name; InterPlan.View := ExterPlan.View; InterPlan.Diameter := ExterPlan.Diameter; InterPlan.Radius := ExterPlan.Radius Пример программы, использующей комбинированный тип Complex, приведен ниже. program ComplexSumma; { Сложение комплексных чисел } const Increment = 4; type Complex = record Re, Im : Real end; var X, Y : Complex; Pair : Integer; begin X.Re := 2; X.Im := 5; { Инициализация X } Y := X; { Инициализация Y } for Pair := 1 to 5 do begin writeln('X = ', X.Re :5:1, X.Im :5:1, 'i'); writeln('Y = ', Y.Re :5:1, Y.Im :5:1, 'i'); writeln('X + Y = ', X.Re + Y.Re, '+', X.Im + Y.Im, 'i'); writeln; X.Re := X.Re + Increment; X.Im := X.Im - Increment end end.
19 Записи с вариантами Иногда в запись необходимо включать информацию, зависящую от другой, уже включенной в эту же запись информации. Для этой цели в Паскале имеется возможность для определения записи с вариантной частью; вариантная часть позволяет задавать тип, содержащий определение нескольких вариантов структуры. Это обозначает, что разные переменные, хотя они и относятся к одному комбинированному типу, могут иметь отличающуюся друг от друга структуру. Различие может касаться как количества компонент (полей) записи, так и их типов. Для описания вариантной части записи используется конструкция CASE, напоминающая одноименный управляющий оператор (оператор выбора); есть и различия (например, отсутствует закрывающее END). Каждый вариант характеризуется списком описаний присущих ему компонент, заключенным в круглые скобки. Перед списком находятся одна или более меток. Рассмотрим следующий пример. Пусть имеется следующее определение типа: type MaritalStatus = (married, widowed, divorced, single); В этом случае любого человека можно описать с помощью записи следующего вида: type Person = record < Свойства, присущие всем людям > case MaritalStatus of married: (< Поля, присущие состоящим в браке >); single: (< Поля, присущие одиноким >); ... end; Обычно некоторое поле записи указывает, о каком варианте идет речь. Это поле, общее для всех вариантов, называется полем признака и помещается в заголовок варианта. Например, case Status : MaritalStatus of ... Прежде чем определять структуру записи с вариантной частью, полезно бывает выписать всю необходимую информацию. В случае типа Person такой информацией может являться: 1. Имя (Name) – фамилия, имя, отчество (First, Second, Last). 2. Рост (Height) – целое число. 3. Пол (Sex) – мужской, женский (male, female). 4. Дата рождения (Date of birth) – день, месяц, год (Day, Month, Year).
20 5. Число иждивенцев (Dependents) – целое число. 6. Семейное положение (Marital status): если в браке (married) или вдов (widowed): дата свадьбы (Date of marriage) – день, месяц, год (Day, Month, Year). если разведен (divorced): a) дата развода (Date of divorce) – день, месяц, год (Day, Month, Year); b) первый развод (first divorce) – нет, да (false, true). если одинокий (single): пусто. На рис.4 показаны "информационные образы" двух человек с различными атрибутами. Сидорова Тамара П етровна 169 жен 27 июнь 1970 2 разведена 17 апрель 1994 да
И ванов И горь Семенович 176 муж 12 сентябрь 1972 0 холост
Рис.4. Используя приведенную выше информацию, можно определить следующую запись Person с вариантной частью: type MaritalStatus = (Married, Widowed, Divorced, Single); Date = record Day : 1..31; Month : (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); Year : 1900..2100 end; { Date } Natural = 0..MaxInt; Person = record Name : record First, Second, Last : string [15] end; { Name } Height : Natural; { в сантиметрах }
21 Sex : (Male, Female); Birth: Date; Depdts : Natural; case Status : MaritalStatus of Married, Widowed : (Mdate : Date); Divorced : (DDate : Date; FirstD : Boolean); Single : () end; { Person } При использовании записей с вариантами следует иметь в виду следующее: 1. Имена всех полей должны быть различными, даже если они встречаются в различных вариантах. 2. Если вариант пустой (т.е. не содержит поля), он записывается в виде "метка-варианта : ()". 3. Для того, чтобы некоторое поле в записи могло выступать в качестве поля-признака (говорят также – в качестве дискриминанта записи), его тип должен задаваться именем типа. Другими словами, можно использовать в вариантной части записи case Status : MaritalStatus of, как показано выше, но нельзя − case Status : (Married, Widowed, Divorced, Single) of . 4. Любой список полей может иметь только одну вариантную часть, которая должна следовать за фиксированной частью записи, т.е. помещаться в конце записи. 5. Каждый вариант, в свою очередь, может содержать в себе вариантную часть, т.е. допускаются вложенные варианты. 6. Область действия имен констант перечислимого типа, вводимых в записи, расширяется на все вложенные блоки. Оператор присоединения (WITH) Оператор присоединения предназначен для того, чтобы сделать запись группы операторов, манипулирующих с полями определенной записи, более наглядной и, возможно, более компактной. Фактически, оператор WITH открывает область действия, содержащую имена полей указанной записи, так что в пределах этой области имена полей могут использоваться как обычные (не составные) имена. Например, имея в виду введенное выше определение типа Person, можно записать следующее: var Student: Person; { См. выше определение типа Person } begin with Student do begin
22 with Name do begin First := 'Иванов'; Second := 'Иван'; Last := 'Иванович' end; Height := 176; Sex := Male; with Birth do begin Day := 12; Month := Sep; Year := 1978 end; Depdts := 0; Status := Single end; ... end. вместо того, чтобы использовать следующую запись: var Student: Person; begin Student.Name.First := 'Иванов'; Student.Name.Second := 'Иван'; Student.Name.Last := 'Иванович'; Student.Height := 176; Student.Sex := Male; Student.Birth.Day := 12; Student.Birth.Month := Sep; Student.Birth.Year := 1978; Student.Depdts := 0; Student.Status := Single; ... end. Операторы присоединения могут вкладываться в друг в друга (см. выше). Вложенные операторы присоединения допускают сокращенную запись, т.е. оператор вида: with R1 do with R2 do with R3 do ... with Rn do S
23 можно записать в виде: with R1, R2, R3, ..., Rn do S т.е. приведенный выше пример вложенных операторов WITH, можно переписать следующим образом: with Student, Name, Birth do begin First := 'Иванов'; Second := 'Иван'; Last := 'Иванович'; Height := 176; Sex := Male; Day := 12; Month := Sep; Year := 1978; Depdts := 0; Status := Single end; Вопросы и упражнения для самопроверки 1. Определить комбинированный тип для представления следующих понятий: − цена в рублях и копейках; − время в часах, минутах и секундах; − адрес (город, улица, дом, квартира); − бланк требования на книгу (сведения о книге: шифр, автор, название, год издания; сведения о читателе: № читательского билета, фамилия; дата заказа); − экзаменационная ведомость (название предмета, фамилия преподавателя, номер группы, дата экзамена, 25 строчек с полями: фамилия студента, № его зачетной книжки, оценка за экзамен). 2. Имеются следующие описания: type Circle = record Radius : real; Center : record x, y : real end end; var K : Circle; Требуется переменной K присвоить значение соответствующее кругу радиуса 2.5 с центром в точке (0,1.8). В каких из приведенных ниже операторов присоединения правильно решается эта задача, а в каких нет и почему? a) with K do
24 begin Radius := 2.5; x := 0; y := 1.8 end; б) with K do begin Radius := 2.5; Center.x := 0; Center.y := 1.8 end; в) with K do begin Radius := 2.5; with Center do begin x := 0; y := 1.8 end end; г) with K, Center do begin Radius := 2.5; x := 0; y := 1.8 end; д) with Center, K do begin Radius := 2.5; x := 0; y := 1.8 end; 3. Определить запись с вариантной частью Person, которая позволила бы представить следующие сведения о студентах: фамилия, имя, отчество, дата рождения, номер группы; для девушек: цвет глаз (голубые, зеленые, серые, карие, черные) и цвет волос (блондинка, шатенка, брюнетка); для юношей: рост (в см) и вес (в кг). 4. Написать программу, которая позволила бы заполнить массив из 10 записей типа Person (см. выше упражнение 3), а затем выполнить вывод части содержимого этого массива: фамилия и цвет глаз и волос (для девушек) или рост и вес (для юношей). Лекция 8. Ввод-вывод в программах на языке Паскаль. Файловый тип данных. Операции над файлами. Начальные и завершающие операции ввода-вывода. Операции ввода-вывода. Операции перемещения по файлу. Специальные операции ввода-вывода. Примеры программ, использующих операции вводавывода. Вопросы и упражнения для самопроверки. Ввод-вывод в программах на языке Паскаль Паскаль, как и большинство других развитых языков программирования, содержит средства для организации хранения информации на внешних запоминающих устройствах (ВЗУ) и доступа к этой информации. В случае ПК основными ВЗУ являются диски (главным образом, жесткие и гибкие магнитные диски, хотя в последнее время все большую популярность приобретают компактные и чрезвычайно емкие лазерные диски – CD-ROM'ы). Информация в ВЗУ хранится в виде файлов. Файл – это область на носителе информации ВЗУ, хранящая некоторую совокупность данных. Средства, обеспечиваемые языком программирования, позволяют поместить данные в файл, т.е. записать их на носитель информации ВЗУ, и прочитать их с носителя. Все эти действия по записи данных в файл и чтению их из файла имеют общее название – ввод-вывод.
25
Файловый тип данных Таким образом, ввод, вывод и хранение информации в компьютере осуществляется с помощью файлов. В Паскале файл представляется в виде переменной. Это несколько необычная переменная, поскольку она может существовать как до, так и после выполнения программы и, кроме того, она может быть намного больше, чем сама программа. В силу этих причин, действия, которые можно производить в программе на языке Паскаль над файлами, значительно ограничены. Применительно к языку Паскаль понятие “файл” обозначает информационный объект, образованный последовательностью компонент одного и того же типа. Сама последовательность устанавливает естественный порядок следования компонент, причем в каждый момент времени доступна только одна компонента. Другие компоненты файла становятся доступны по мере продвижения по файлу. Количество компонент, называемое длиной файла, в определении файлового типа не фиксируется. Эта особенность явно отличает понятие файла от понятия массива. Если файл не имеет компонент, его называют пустым. Таким образом, любой файловый тип данных представляет собой объединение последовательности компонент, имеющих один и тот же тип данных (базовый тип файла), некоторого положения в этой последовательности (позиции) и режима, указывающего, формируется (записывается) файл или просматривается (читается). Определение файлового типа данных может быть записано в общем виде следующим образом: type файловый-тип = file of базовый-тип-файла; Описание файловой переменной в этом случае может быть представлено как var имя-файловой-переменной : файловый-тип; Определение файлового типа и описание файловой переменной может быть объединено в одном описании, имеющем следующий вид: var имя-переменной : file of базовый-тип-файла; Примеры описания файловых переменных: var InFile : file of integer; Table : file of string [80]; Data : file of real; DataBase : file of Person; {Определение типа Person см. в лекции 7}
26 Операции над файлами Операции над файлами в Турбо-Паскале можно разбить на четыре основные группы: • начальные и завершающие операции ввода-вывода; • собственно операции ввода-вывода; • операции перемещения по файлу; • специальные операции ввода-вывода. Рассмотрим операции этих групп более подробно. Начальные и завершающие операции В эту группу входят четыре операции, реализованные стандартными процедурами, имеющими следующие имена: Assign, Reset, Rewrite, Flush и Close. Процедура Assign(имя-файловой-переменной, спецификация-файла) определена в модуле System и предназначена для установления связи между файловой переменной, заданной первым параметром − имя-файловой-переменной, и конкретным физическим файлом, находящимся на магнитном носителе ВЗУ и заданным вторым параметром − спецификация-файла. Например: uses System; { Можно не указывать, используется по умолчанию } var IntFile : file of integer; { Файл целых чисел } SymFile : file of char; { Символьный файл } FileName : string [12]; begin Assign(IntFile, 'd:\gr711\turbo\pas\myfile.dat'); ... write('Имя файла? (имя.расширение) '); readln(FileName); Assign(SymFile, FileName); ... end. После выполнения первого вызова процедуры Assign файловая переменная IntFile будет связана с файлом myfile.dat, находящимся на диске D: в подкаталоге \gr711\turbo\pas. После выполнения второго вызова процедуры Assign файловая переменная SymFile будет связана с файлом, имя и расширение имени которого были введены пользователем с клавиатуры в ответ на полученную подсказку. В качестве второго параметра процедуры Assign допускаются имена устройств, рассматриваемых в DOS в качестве файлов, а именно: 'CON' – консоль: экран в случае вывода информации и клавиатура в случае ввода;
27 'LPT1', 'LPT2', 'LPT3' – печатающие устройства (допускается одновременно до трех печатающих устройств). Эти устройства предназначены только для вывода информации; 'PRN' – синоним 'LPT1'; 'COM1', 'COM2' – последовательные коммуникационные порты. Смысл этих файлов определяется конкретными устройствами, подключенными к этим портам. 'AUX' – синоним 'COM1'; 'NUL' – фиктивное, несуществующее устройство. Может быть использовано для вывода информации "в никуда" в тех случаях, когда в программе по какой-либо причине нужно указать имя выходного файла, а информация, записываемая в него, не требуется. После того, как с помощью процедуры Assign установлена связь между файловой переменной, описанной в программе, и внешним файлом, находящимся на диске, необходимо подготовить файловую переменную к вводувыводу (или как говорят – открыть файл). Это выполняется с помощью процедуры Reset или Rewrite, определенных в модуле System. Процедура Reset(имяфайловой-переменной) предназначена для открытия существующего файла. Под открытием в данном случае понимается поиск файла на внешнем носителе, назначение специальных системных буферов для обмена информацией с ним и установка текущего указателя файла на начало файла. Если файл на диске не существует, то возникает ошибка. Процедура Rewrite(имя-файловой-переменной) допускает, что открываемый файл может не существовать; в этом случае она создает его. Если же файл существует, то процедура Rewrite очищает его. В обоих случаях текущий указатель файла устанавливается на начало файла. Примеры использования процедур Reset и Rewrite: Assign(IntFile, 'd:\gr711\turbo\pas\myfile.dat'); Reset(IntFile); ... Assign(SymFile, FileName); Rewrite(SymFile); ... Примечание. В начале выполнения программы всегда автоматически открываются стандартные текстовые файлы Input (ввод) и Output (вывод). Input – это доступный только для ввода (чтения) файл, связанный с клавиатурой, а Output – это доступный для вывода (записи) файл, связанный с экраном. О текстовых файлах (тип данных text) см. ниже Лекцию 8. Процедура Flush(имя-файловой-переменной) определена в модуле System и предназначена для завершения обмена информацией с файлом без его закрытия. Обмен с файлом всегда реализуется через некоторый буфер в оперативной памяти, поэтому в процессе записи в файл последние записываемые данные мо-
28 гут еще находиться в этом буфере. Процедура Flush вызывает принудительный "сброс" этих данных в файл. Процедура Close(имя-файловой-переменной) определена в модуле System и предназначена для завершения действий с файлом (говорят – для закрытия файла). При этом освобождаются внутренние буферы, закрепленные за файлом при его открытии. После этого файловую переменную можно связать с какимнибудь другим файлом на диске, используя процедуру Assign. Следует отметить, что при окончании работы программы происходит автоматическое закрытие всех файлов, открытых в программе. Тем не менее, хорошим правилом считается закрытие файлов с помощью процедуры Close после окончания работы с ними. Например: ... Close(SymFile); ... Close(IntFile); ... Операции ввода-вывода В эту группу входят две операции, выполняющие чтение информации из файла и запись информации в файл и реализованные процедурами Read и Write соответственно. Процедура Read(имя-файловой-переменной, имя-переменной-1 [, имяпеременной-2, ..., имя-переменной-N]) определена в модуле System и предназначена для чтения значений из файла в переменные программы. Первым параметром процедуры Read должно быть имя файловой переменной, к которой предварительно была применена одна из операций открытия файла (Reset или Rewrite). Далее должны быть указаны переменные, в которые будут помещаться значения, считываемые из файла. Тип этих переменных должен совпадать с базовым типом файловой переменной, заданной в качестве первого параметра. Выполнение процедуры Read происходит следующим образом. Начиная с текущей позиции указателя файла, будут последовательно читаться значения, содержащиеся в файле. Каждое прочитанное значение будет присваиваться очередной переменной из тех, которые указаны в вызове процедуры. После считывания каждого значения указатель файла будет автоматически перемещаться на следующую позицию в файле. Примечание. Выше в записи формата вызова процедуры Read использованы квадратные скобки, идентифицирующие необязательные параметры, т.е. параметры, которые могут быть опущены при вызове процедуры (сами квадратные скобки, естественно, в операторе вызова процедуры не указываются).
29 При движении вдоль файла указатель текущей позиции в файле может достигнуть конца файла. Эту ситуацию можно проконтролировать с помощью предопределенной (стандартной, встроенной) логической функции EoF(имяфайловой-переменной), которая вырабатывает значение True при достижении указателем конца файла (End Of File) и False – во всех других случаях (см. ниже). Пример использования вызова процедуры Read: ... while not eof(IntFile) do { Пока не конец файла } begin read(IntFile,IntNum); { Прочитать целое число из файла } writeln(IntNum) { Вывести это число на экран } end; ... Процедура Write(имя-файловой-переменной, выражение-1 [, выражение2, ..., выражение-N]) определена в модуле System и предназначена для записи информации из программы в файл, т.е. по смыслу она противоположна процедуре Read. Первым параметром процедуры Write должно быть имя файловой переменной, предварительно открытой с помощью процедуры Rewrite. Далее должен следовать список выражений, тип которых совпадает с базовым типом файловой переменной, указанной в качестве первого параметра. Выполнение процедуры Write происходит следующим образом. Вычисляется значение очередного выражения и помещается в файл на место, указанное текущим указателем. После этого указатель автоматически сдвигается к следующей позиции в файле и действия повторятся для следующего выражения из тех, что указаны в списке вызова процедуры Write. Пример использования вызова процедуры Write: ... for i := 1 to 100 do { В цикле выполнить 100 раз } begin read(Symbol); { Прочитать символ с клавиатуры } write(SymFile, Symbol) { Записать этот символ в файл } end; ... Операции перемещения по файлу Данная группа операций предоставляет дополнительные возможности, позволяющие изменять последовательный порядок выполнения операций чтения и записи. Эта группа объединяет две процедуры − Seek и Truncate − и три функции − FileSize, FilePos и упоминавшуюся ранее EoF. Все эти процедуры и функции определены в модуле System и оперируют непосредственно с текущим указателем файла. Процедура Seek(имя-файловой-
30 переменной, номер-позиции-в-файле) перемещает указатель в файле, заданном первым параметром, на позицию с номером, указанным вторым параметром. После выполнения процедуры Seek дальнейшие операции чтения или записи в файле будут производиться, начиная с установленной позиции указателя. Процедура Truncate(имя-файловой-переменной) определена в модуле System и предназначена для отсечения хвостовой части в файле, заданном в качестве параметра этой процедуры. Усечение файла начинается с позиции, указанной указателем файла. Функции FileSize(имя-файловой-переменной) и FilePos(имя-файловойпеременной), возвращающие длинное целое, определены в модуле System и позволяют получить дополнительную информацию о файле, указанном в качестве их единственного параметра. Функция FileSize возвращает общее количество элементов в файле, т.е. длину файла, а функция FilePos – номер элемента, на который установлен текущий указатель, т.е. текущую позицию файла. Используя эти функции, можно организовать достаточно сложные алгоритмы работы с файлами. Здесь мы приведем лишь несколько простых примеров: Seek(IntFile,FilePos(IntFile)+1) { Пропуск одного числа в файле } Seek(SymFile,0) { Установка указателя на начало файла } Seek(SymFile,FileSize(SymFile)) { Установка указателя непосред-ственно за последним символом файла (возмо-жно для добавления символов в "хвост" файла) } Булевская функция EoF(имя-файловой-переменной) также определена в модуле System; она возвращает логическое (булево) значение True, если в ходе перемещения по файлу был достигнут конец файла, и False – в противном случае. Так, например, если файл использовался для чтения, то возникновение ситуации "конец файла" (и, соответственно значение True, возвращаемое функцией EoF) означает, что все элементы файла прочитаны. При записи в файл истинность функции EoF будет означать, что очередная операция записи поместит новый элемент в конец данного файла. Специальные операции ввода-вывода Данная группа операций предназначена для действий с элементами файловой системы MS-DOS – каталогами и именами файлов, позволяя создавать и удалять каталоги, удалять и переименовывать файлы и т.д. Приведем краткое описание процедур, реализующих эти операции (все они определены в модуле System). Процедура Erase(имя-файловой-переменной) удаляет указанный в качестве параметра файл с диска.
31 Процедура Rename(имя-файловой-переменной, новое-имя-файла) переименовывает файл, указанный в качестве первого параметра; новое имя файла задается строкой, указанной в качестве второго параметра. Процедура ChDir(имя-нового-каталога) позволяет сменить текущий каталог; новым текущим (рабочим) каталогом будет каталог, имя которого задано строкой, указанной в качестве параметра. Процедура MkDir(имя-нового-каталога) позволяет создать новый каталог; в качестве имени нового каталога будет использована строка, заданная параметром. Процедура RmDir(имя-каталога) позволяет удалить пустой каталог; удаляемый каталог задается строкой, указанной в качестве параметра. Примеры программ, использующих операции ввода-вывода Ниже приведен примеры двух программ, использующих файловый тип данных и операции ввода-вывода. Программа CreateFile создает небольшой “банк данных” − файл, хранящий о каждом студенте следующую информацию: фамилия, имя, отчество, номер группы и пять экзаменационных оценок (по 4-бальной шкале), полученных во время зимней сессии. Ввод данных о студентах выполняется с клавиатуры в интерактивном режиме, т.е. в режиме диалога “компьютер-пользователь”. program CreateFile; type FullName = record FirstName, SecondName, LastName : string [20] end; Person = record Name : FullName; { Ф.И.О. } Group : 100..999; { Номер группы } Balls : array [1..5] of 2..5 { Оценки } end; var DataBase : file of Person; Student : Person; FileName : string [12]; { Имя файла банка данных } i : 1..5; Answer : char; begin write('Файл? (Имя.Расширение) '); readln(FileName); { Ввести имя файла } assign(DataBase,FileName);
32 rewrite(DataBase); { Открыть файл для записи } repeat with Student, Name do begin write('Фамилия? '); readln(FirstName); write('Имя? '); readln(SecondName); write('Отчество? '); readln(LastName); write('N группы? '); readln(Group); for i := 1 to 5 do { Цикл ввода оценок } begin write(i, '-ая оценка? '); readln(Balls[i]) end end; write(DataBase, Student); write('Еще? (Д/Н) '); readln(Answer) until Answer = 'Н' or Answer = 'н'; close (DataBase) end. Программа ScanFile запрашивает у пользователя ввод фамилии студента, последовательно считывает записи из файла и при обнаружении записи, относящейся к указанному студенту, подсчитывает средний балл, полученный во время экзаменационной сессии, и выводит его на экран. Если запись о студенте отсутствует в файле, на экран выводится соответствующее сообщение. program ScanFile; type FullName = record FirstName, SecondName, LastName : string [20] end; Person = record Name : FullName; { Ф.И.О. } Group : 100..999; { Номер группы } Balls : array [1..5] of 2..5 { Оценки } end; var DataBase : file of Person; Student : Person; FileName : string [12]; { Имя файла банка данных } StudName : string[20]; { Фамилия студента } i : 1..5; Summa : byte; { Сумма баллов }
33 Flag : boolean; { Флажок “Есть запись о студенте?” } begin write('Файл? (Имя.Расширение) '); readln(FileName); { Ввести имя файла } assign(DataBase,FileName); reset(DataBase); { Открыть файл для чтения } write('Фамилия? '); readln(StudName); { Ввести фамилию студента } Flag := false; Summa := 0; while not EoF(DataBase) do { Цикл чтения-обработки записей } begin read(DataBase, Student); with Student, Name do if FirstName = StudName then begin Flag := true; { Запись о студенте найдена } for i := 1 to 5 do Summa := Summa + Balls[i]; writeln('Средний балл = ', Summa/5) end end; close(DataBase); if not Flag then writeln('Запись о студенте с фамилией ', StudName, ' в файле ', FileName, ' не найдена.') end. Вопросы и упражнения для самопроверки 1. Что такое базовый тип файла? Какие типы данных в Турбо-Паскале могут быть использованы в качестве базовых типов файлов? 2. На какие группы разделены все операции ввода-вывода в ТурбоПаскале? Какие процедуры обеспечивают выполнение следующих основных операций ввода-вывода: a) открытие файла для чтения или записи; б) чтение информации из файла или запись информации в файл; в) закрытие файла? 3. Что представляет собой понятие “текущий указатель файла”? Какие манипуляции допускаются с текущим указателем файла в языке ТурбоПаскаль? С помощью каких средств? 4. Что представляет собой ситуация “конец файла” и как ее обнаружить средствами языка Паскаль?
34 5. Привести список команд MS-DOS, выполняющих манипуляции с файлами и каталогами, аналогичные действиям следующих процедур: Erase, Rename, ChDir, MkDir и RmDir. 6. Что выведет на экран программа ScanFile при наличии в файле информации об однофамильцах? Как модифицировать эту программу, чтобы на экран выводился средний балл только для конкретного человека? 7. Разработать две программы для работы с файлами вещественных чисел. Первая программа должна позволить пользователю ввести произвольное количество вещественных чисел с клавиатуры и записать их в файл. Вторая программа должна выполнить чтение чисел из файла, определить у каждого из них знак числа и записать положительные числа в один файл, а отрицательные − в другой. 8. Разработать две программы для работы с файлами целых чисел. Первая программа должна позволить пользователю ввести произвольное количество целых чисел с клавиатуры и записать их в файл. Вторая программа должна выполнить чтение чисел из файла, проанализировать каждое из них на чётностьнечётность и записать чётные числа в один файл, а нечётные − в другой. 9. Разработать две программы для работы с файлами символов. Первая программа должна позволить пользователю ввести произвольное количество символов с клавиатуры и записать их в файл. Вторая программа должна выполнить чтение символов из файла, их анализ и запись латинских строчных и прописных букв в один файл, всех остальных символов − в другой. 10. Разработать две программы для работы с файлами символов. Первая программа должна позволить пользователю ввести произвольное количество символов с клавиатуры и записать их в файл. Вторая программа должна выполнить чтение символов из файла, их анализ и запись цифр в один файл, латинских строчных и прописных букв − в другой, все прочие символы игнорировать. 11. Разработать две программы для работы с файлами записей, каждая из которых имеет следующие 2 поля: фамилия и номер группы. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества записей в файл. Вторая программа должна запросить у пользователя ввод номера группы, а затем выполнить чтение файла и вывод записей, относящихся к указанной группе, в отдельный файл. 12. Разработать две программы для работы с файлами записей, каждая из которых имеет следующие 2 поля: фамилия и имя студента. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества записей в файл. Вторая программа должна запросить у пользователя ввод имени студента, а затем выполнить чтение записей файла и вывод на экран фамилий студентов, имеющих заданное имя. 13. Разработать две программы для работы с файлами вещественных чисел. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества вещественных чисел в файл. Вторая программа должна
35 выполнить чтение чисел файла и подсчет для них суммы отрицательных и суммы положительных вещественных чисел. 14. Разработать две программы для работы с файлами целых чисел. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества целых чисел в файл. Вторая программа должна выполнить чтение целых чисел из файла и поиск среди них максимального числа. 15. Разработать две программы для работы с файлами целых чисел. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества целых чисел в файл. Вторая программа должна выполнить чтение целых чисел из файла и поиск среди них минимального числа. 16. Разработать две программы для работы с файлами целых чисел. Первая программа должна позволить пользователю ввод с клавиатуры произвольного количества целых чисел в файл. Вторая программа должна выполнить чтение целых чисел из файла и вычисление их среднего арифметического. Лекция 9. Текстовые файлы. Процедуры и функции для работы с текстовыми файлами. Примеры программ, использующих текстовые файлы. Обработка ошибок ввода-вывода в программах на Турбо-Паскале. Вопросы и упражнения для самопроверки. Текстовые файлы В Паскале существует предопределенный файловый тип text, называемый текстовым, который предназначен для описания файлов, компонентами которого являются символьные строки переменной длины. Символьные строки, образующие текстовый файл, отличаются от рассмотренных ранее символьных строк (тип string) тем, что не имеют начального байта длины (0-й байт), а заканчиваются признаком (маркером) конца строки − парой символов CR (Carriage Return – возврат каретки, ВК) и LF (Line Feed – перевод строки, ПС), имеющих десятичные коды 13 и 10 соответственно. Текстовые файлы заканчиваются специальным признаком (маркером) конца файла (символ Ctrl-Z, имеющий код 2610). Описание файловой текстовой переменной имеет следующий вид: var имя-файловой-переменной : text; где text – предопределенный тип, предназначенный для описания текстовых файлов. Процедуры и функции для работы с текстовыми файлами
36 Для работы с текстовыми файлами используются следующие процедуры и функции. Процедура Assign (имя-файловой-переменной,имя-текстового-файла) связывает файловую текстовую переменную, имя которой указано в качестве первого параметра, с файлом на диске, имя которого указано в качестве второго параметра. В дальнейшем все обращения к файлу выполняются через файловую текстовую переменную. Процедура Rewrite (имя-файловой-переменной) открывает новый текстовый файл для записи (указатель файла при этом устанавливается на начало файла). Если файл с таким именем существует на диске в указанном каталоге, то он будет удален. Например: var MyFile : text; begin assign (MyFile, 'd:\gr711\turbo\pas\proba.pas'); rewrite ( MyFile ); ... end. Процедура Reset (имя-файловой-переменной) открывает существующий файл для чтения (указатель файла при этом устанавливается на начало файла, т.е. на первый элемент первой строки файла). Если файл на диске отсутствует, то возникает ошибка ввода-вывода. Процедура Append (имя-файловой-переменной) открывает существующий файл для добавления в него символьных строк (указатель файла при этом устанавливается на конец файла). Если файл на диске отсутствует, то возникает ошибка ввода-вывода. Примечание. После открытия текстового файла одним из указанных выше способов, можно либо только производить запись в него, либо только выполнять чтение из него. Если необходимо изменить характер работы с текстовым файлом, его приходится закрывать, а затем заново открывать, используя соответствующую процедуру. Процедура Read ([имя-файловой-переменной,] имя-переменной-1 [, имяпеременной-2, ..., имя-переменной-N]) считывает информацию из текстового файла, связанного с файловой текстовой переменной, заданной первым параметром − имя-файловой-переменной. Поскольку текстовый файл по определению содержит символьную информацию, при чтении файла очередная часть текущей строки будет пониматься как символьное представление значения, тип которого определяется типом очередной переменной из списка переменных процедуры Read. В данном случае разделителями символьных представлений значений служат символы “пробел” и/или “табуляция”. Процедура Write ([имя-файловой-переменной,] выражение-1 [, выражение-2, ..., выражение-N]) записывает информацию в текстовый файл, связанный
37 с файловой текстовой переменной, заданной первым параметром − имяфайловой-переменной. Каждое выражение, указанное в с списке параметров, вычисляется и записывается в текстовый файл. Если тип вычисленного значения отличается от символьного, то выражение преобразуется в символьное представление, а затем записывается в очередную строку текстового файла. Выше в записи форматов вызовов процедуры Read и Write использованы квадратные скобки, идентифицирующие необязательные параметры, т.е. параметры, которые могут быть опущены при вызове процедуры (сами квадратные скобки, естественно, в операторе вызова процедуры не указываются). Таким образом, простейшая форма оператора вызова − Read(имя-переменной) − фактически означает чтение строки текста (не string) из стандартного входного текстового файла (Input), т.е. с клавиатуры, и преобразование этого текста в тип данных переменной, указанной в качестве параметра процедуры Read. Простейшая форма оператора Write(выражение) означает вычисление значения указанного выражения, преобразование его в символьный вид, т.е. в последовательность символов, и вывод этой последовательности символов на экран. Подобная простая форма процедур Read и Write неоднократно использовалась нами ранее для ввода в программу данных с клавиатуры и вывода результатов работы программы на экран. Имеются две модификации процедур Read и Write – процедуры ReadLn и WriteLn. Эти процедуры выполняют те же действия, что и соответствующие процедуры Read и Write, но после операций чтения и записи производят переход к следующей строке текстового файла. Следует отметить, что операция Read автоматически переходит к следующей строке только в случае исчерпания текущей строки; процедура ReadLn позволяет совершить этот переход, не дожидаясь окончания строки. Процедура Close (имя-файловой-переменной) закрывает открытый ранее текстовый файл. При чтении информации из текстового файла возникает необходимость в контроле за достижением конца строки и конца файла. В первом случае для этой цели используется булевская функция EoLn [(имя-файловой-переменной)], а во втором – уже знакомая нам булевская функция EoF [(имя-файловойпеременной)]. Функция EoLn возвращаeт значение True (истина), если достигнут конец строки, и False (ложь) – в противном случае. Аналогично функция EoF возвращает значение True (истина), если достигнут конец файла, и False (ложь) – в противном случае. Примечание. В форматах обращений к функциям EoF и EoLn, приведенных выше, использованы квадратные скобки, означающие, что имя файловой переменной, заключенное в круглые скобки, может быть опущено. Если это имеет место, то в качестве обрабатываемого тестового файла предполагается стандартный файл Input.
38 Нужно иметь в виду, что для текстовых файлов неприменима операция поиска записи с нужным номером (процедура Seek), определенная для обычных файлов (см. предыдущую лекцию), поскольку строки текстового файла имеют произвольную длину. Некоторым подобием процедуры Seek для текстовых файлов являются булевские функции SeekEoLn и SeekEoF. Функция SeekEoLn[(имя-файловой-переменной)] производит поиск конца текущей строки. Она пропускает все символы-разделители (пробелы и табуляции) в строке, устанавливает текущий указатель файла на конце строки и возвращает значение True (истина). Если конец строки не найден, то SeekEoLn устанавливает указатель на первом значащем символе строки (т.е. на символе, отличном от пробела или табуляции) и возвращает к значение False (ложь). Функция SeekEoF[(имя-файловой-переменной)] осуществляет поиск конца файла и действует аналогично функции SeekEoLn, пропуская символыразделители; однако, кроме пробелов и табуляций она пропускает также символы конца строки (т.е. переходит со строки на строку) в поисках конца файла. Если конец файла найден, то функция SeekEoF возвращает значение True; в противном случае – она возвращает значение и устанавливает указатель на первом значащем символе в файле. Примеры программ, использующих текстовые файлы Ниже рассмотрены три задачи, связанные с обработкой текстовых файлов, и даны их возможные решения − в виде текстов программ на языке Паскаль. Задача 1. Написать программу, выполняющую копирование одного текстового файла (входной файл X) в другой текстовый файл (выходной файл Y) с сохранением построчной структуры исходного файла. program CopyText; var X, Y : text; { Файловые текстовые переменные } C : char; { Буферная символьная переменная } Name_X : string[80]; Name_Y : string[80]; begin write('Имя входного файла? '); readln(Name_X); assign(X, Name_X); reset (X); { Открыть входной файл } write('Имя выходного файла? '); readln(Name_Y); assign(Y, Name_Y); rewrite (Y); { Открыть выходной файл }
39 { Цикл посимвольного копирования одного текстового (входного) файла в другой (выходной) файл } while not eof(X) do { Пока не конец входного файла, выполнять...} begin while not eoln(X) do { Пока не конец строки во входном } begin { файле, выполнять... } read (X,C); { Считать символ из входного файла } write (Y,C) { Записать символ в выходной файл } end; readLn (X); { Перейти к новой строке во входном файле } writeLn (Y) {Записать признак конца строки в выходной файл} end; close(X); { Закрыть входной файл } close(Y) { Закрыть выходной файл } end. Задача 2. Написать программу, выполняющую подсчет количества латинских прописных и строчных латинских букв и количества арабских цифр, обнаруженных в текстовом файле. program CountLetterAndDigit; var F : text; { Файловая текстовая переменная } C : char; { Буферная символьная переменная } LetterCount, DigitCount : word; { Счетчики букв и цифр } FileName : string[80]; { Для хранения имени файла } begin write('Имя файла? '); readln(FileName); assign(F, FileName); reset (F); { Открыть текстовый файл } { Инициализация счетчиков } LetterCount := 0; DigitCount := 0; { Цикл чтения текстового файла и анализ каждого считанного символа } while not eof(F) do {Пока не конец текстового файла, выполнять...} begin read(F,C); {Считать символ из файла в буферную переменную} if ((C >= 'A') and (C <= 'Z')) or {Если прописная буква или } ((C >= 'A') and (C <= 'Z')) then { или строчная буква, то... } LetterCount := LetterCount + 1 {увеличить счетчик букв на 1} else if (C >= '0') and (C <= '9') then {Если цифра, то...} DigitCount := DigitCount + 1{увеличить счетчик цифр на 1} end;
40 close(F); { Закрыть текстовый файл } writeln('В файле ', FileName, ' обнаружены ', LetterCount, ' латинских букв и ', DigitCount, ' арабских цифр.') end. Задача 3. Написать программу, выполняющую подсчет количества слов, обнаруженных в текстовом файле. Разделителями между словами в файле считать следующие символы: пробел, горизонтальную табуляцию, признак конца строки (символы ВК и ПС), точку, запятую, точку с запятой, двоеточие, восклицательный знак, вопросительный знак, открывающую круглую скобку, закрывающую круглую скобку и др. (список можно продолжить). Следует иметь в виду, что часто символы разделители находятся в строке рядом (например, после слова стоит запятая, далее следует пробел, а лишь затем следующее слово). program CountWord; var F : text; { Файловая текстовая переменная } C : char; { Буферная символьная переменная } Cnt : word; { Счетчик слов } InWord : boolean; {Нахождение в слове или за его пределами } FileName : string[80]; { Для хранения имени файла } begin write('Имя текстового файла? '); readln(FileName); assign(F, FileName); reset (F); { Открыть текстовый файл } Cnt := 0; { Инициализация счетчика слов } InWord := False; { Первоначально за пределами слова } { Цикл чтения текстового файла и анализ каждого считанного символа } while not eof(F) do {Пока не конец текстового файла, выполнять...} begin read(F,C); {Считать символ из файла в буферную переменную} case C of {Проанализировать считанный символ} #13, #10, #9, ' ', {Символы ВК, ПС, ГТ и пробел } '.', ',', ';', ':', '!', '?', '(', ')' : { Здесь список можно продолжить} if InWord then {Если до этого находились в слове, то... } begin InWord := False; {вышли за пределы слова} Cnt := Cnt + 1 { увеличить счетчик слов на 1 } end; else InWord := True { Ветвь по умолчанию для case } end { Конец case }
41 end; {Конец while } close(F); { Закрыть текстовый файл } writeln('В файле ', FileName, ' обнаружены ', Cnt, ' слов.') end. Обработка ошибок ввода-вывода в программе на Турбо-Паскале При выполнении операций с файлами существует некоторая вероятность возникновения тех или иных ошибочных ситуаций (например, попытка открыть несуществующий на диске файл для чтения, попытка выполнить чтение файла за концом файла и т.д.). В программе на Турбо-Паскале установлены следующие правила обработки ошибок ввода-вывода. По умолчанию при выполнении любой операции ввода-вывода автоматически производится проверка на возникновение ошибки. При возникновении ошибки выполнение программы прекращается, а на экран выводится условный номер ошибки и краткое диагностическое сообщение (например, Disk read error – ошибка чтения с диска). В программе можно предусмотреть другую форму реакции на ошибки ввода-вывода. Для этого в начале “опасного” фрагмента программы необходимо отключить автоматическую проверку на возникновение ошибки вводавывода. Это делается с помощью включения в текст программы директивы {$I}. В этом случае возникновение ошибки ввода-вывода не будет приводить к немедленному прекращению выполнения программы; вместо этого код (условный номер) возникшей ошибки запоминается системой в специальной глобальной переменной. Программа с помощью функции IOResult (модуль System) можно получить этот код и построить свои дальнейшие действия в зависимости от его значения; например: ... Assign (F,'d:\gr711\turbo\pas\myfile.dat'); {$I-} { Отключить автоматический контроль } reset (F); if IOResult <> 0 then { возникла ошибка } write ('Ошибка при открытии файла'); {$I+} { Включить автоматический контроль } ... При использовании функции IOResult необходимо помнить следующую особенность: если отключен режим автоматического контроля (сработала директива {$I-}), то после возникновения ошибки все последующие операции с любым файлом будут игнорироваться, пока не произойдет обращение к функции IOResult. В этой связи хорошим правилом считается вызов функции IOResult и анализ кода ошибки сразу после выполнения операции, связанной с файлом. Кроме того, следует помнить, что вызов функции IOResult, возвращая в программу код ошибки, обнуляет этот код в глобальной переменной, поэтому
42 последующие обращения к этой функции будут всегда давать нулевой результат, пока какая-либо файловая операция не закончится с ошибкой. Поэтому имеет смысл сохранить код ошибки, возвращаемый функцией IOResult, в некоторой переменной программы, а затем проанализировать содержимое этой переменной и, если можно, то ликвидировать причину ошибки (например, предоставить пользователю возможность ввода нового имени файла, если указанный им для чтения файл отсутствует и т.д.). В приведенном ниже фрагменте программы код ошибки сохраняется в целой переменной ErrCode, а затем анализируется с целью конкретизации причины ошибки. var ErrCode : integer; { Для хранения кода ошибки ввода-вывода } ... begin ... {$I-} { Отключить автоматический контроль } reset (F); ErrCode := IOResult; if Errcode <> 0 then {Если код отличен от 0, ошибка ввода-вывода } case ErrCode of { Анализ ошибочной ситуации } 2: write('Файл не найден.'); 3: write('Путь к файлу не найден.'); 4: write('Слишком много открытых файлов.'); ... end; {$I+} { Включить автоматический контроль } ... end. В табл. 6 приведены коды ошибок ввода-вывода, сообщения, сопровождающие обнаружение этих ошибок, и наиболее вероятные причины, приводящие к этим ошибкам. Таблица 6. Код Сообщение об ошибке и возможная причина ошибки File not found 2 Файл не найден. Процедуры Reset, Append, Rename и Erase возвращают этот код, когда указанный в Assign файл не существует. Path not found Путь не найден. Процедуры Reset, Append, Rename, Erase, Rewrite, 3 ChDir, MkDir, и RmDir возвращают этот код, когда указанное в Assign имя недопустимо или требуемый каталог не существует. Too many open files Слишком много открытых файлов. Процедуры Reset, Append и
43 4
5
6
12
16
17
100
101
102
103
104
105
Rewrite возвращают этот код, когда в программе количество открытых файлов превышает значение параметра команды FILES в файле CONFIG.SYS. File access denied Доступ к файлу запрещен. Эта ошибка возникает в случае попытки доступа к файлу с атрибутами ReadOnly, Hidden, System, Directory или при доступе к закрытому файлу. Invalid file handle Неверный блок управления файлом. Эта ошибка возникает, когда испорчена файловая переменная. Invalid file access code Неверный код доступа к файлу. Эта ошибка возникает при открытии файла, если значение переменной FileMode некорректно. Cannot remove current directory Нельзя удалять текущий каталог. Эта ошибка возникает при попытке удалить с помощью процедуры RmDir текущий каталог. Cannot rename across drives Нельзя переименовывать файлы на разных дисках. Эта ошибка возникает, если в процедуре Rename в именах файлов указаны различные дисководы. Disk read error Ошибка чтения с диска. Эта ошибка возникает при попытке чтения после конца файла. Disk write error Ошибка записи на диск. Эта ошибка возникает при отcут-ствии свободного места на диске. File not assigned Файловая переменная не связана с именем файла. Не была выполнена процедура Assign. File not open Файл не открыт. Не была выполнена одна из следующих процедур открытия файла: Reset, Rewrite или Append. File not open for input Файл не открыт для ввода. Попытка выполнить чтение из файла, который не был открыт с помощью процедуры Reset. File not open for output Файл не открыт для вывода. Попытка выполнить запись в файл, который не был открыт с помощью процедуры Rewrite или Append. Invalid numeric format Неверный числовой формат. При выполнении процедуры Read или Readln во входном файле встретилась последовательность симво-
44
106
150
лов, не являющаяся числом. Disk write protected Диск защищен от записи. Попытка произвести запись на диск, защищенный от записи (например, на 5.25-дюймовую дискету с заклеенной боковой прорезью). Drive not ready Устройство не готово.
152 Вопросы и упражнения для самопроверки 1. Считая t текстовым файлом (файлом с типом text), ответить на следующие вопросы: a) Эквивалентны ли типы text и file of char? b) Какого типа файлы input и output? Почему они не описываются в программах? c) Должны ли все строки файла иметь одинаковую длину? Допустимы ли пустые строки? d) Если при записи в t надо закончить строку, то как это сделать? Какие действия влечет выполнение оператора writeln(output)? e) Верно ли, что из текстового файла можно считывать только по одному символу? А записывать? Если k − целая переменная, то допустимы ли операторы read(t,k) и write(t,k)? d) Эквивалентны ли вызовы read(input,k) и read(k), eof(input) и eof? 2. Написать программу, формирующую текстовый файл t из девяти строк, в первой из которых − один символ ‘1’, во второй − два символа ‘2’, ..., в девятой − девять символов ‘9’. 3. Написать программу, которая считывает с клавиатуры символы до первой точки и записывает их (без точки) в текстовый файл t, формируя в нем строки по 40 символов (в последней строке символов может быть и меньше). 4. Написать программу, которая: a) подсчитывает количество пустых строк в текстовом файле t; b) находит максимальную длину строк текстового файла t. 5. Написать программу для построчной печати содержимого текстового файла t. 6. Считая, что непустой текстовый файл t разбит на строки, длина каждой из которых не превосходит 80 символов, написать программу, которая, дополняя короткие строки файла t пробелами справа, формирует текстовый файл t80, все строки в котором имеют длину 80.
45 7. В текстовом файле t записана непустая последовательность вещественных чисел, разделенных пробелами. Написать программу для нахождения наибольшего из этих чисел. 8. В текстовом файле t1 записана последовательность целых чисел, разделенных пробелами. Написать программу, записывающую в текстовый файл t2 все положительные числа из t1. 9. Написать программу, которая осуществляет построчный вывод содержимого непустого текстового файла t, вставляя в начало каждой печатаемой строки ее порядковый номер (он должен занимать 4 позиции) и пробел. 10. Пусть имеется некоторый внешний файл BOOK (если нет, то создать его с помощью любого редактора текста). Написать программу, которая, игнорируя исходное деление этого файла на строки, переформатирует его, разбивая на строки так, чтобы каждая строка оканчивалась точкой либо содержала ровно 60 символов, если среди них точки.
Лекция 10. Понятие подпрограммы. Подпрограммы в языке Паскаль: процедуры и функции. Примеры программ, использующих процедуры и функции. Механизмы передачи параметров в подпрограммы. Передача параметров по значению. Передача параметров по ссылке. Завершение подпрограмм. Предварительное объявление подпрограмм. Блочная структура программ на языке Паскаль. Вопросы и упражнения для самопроверки. Понятие подпрограммы Все сложные системы – независимо от их природы (биологические, бюрократические, военные и другие) – имеют иерархическую организацию. Иерархия – это система уровней, в которой верхние уровни имеют дело с более общей информацией, а нижние уровни – с техническими подробностями. Сложные программы (программные системы) также имеют, как правило, иерархическую структуру; иерархическая организация программ на стандартном языке Паскаль базируется на таком понятии, как подпрограмма. В ТурбоПаскале вводится понятие модуля (unit), значительно расширяющее возможности блочно-иерархического подхода к разработке программ, обеспечиваемые стандартным Паскалем. В настоящей лекции мы рассмотрим способы организации и использования подпрограмм, которые в языке Паскаль подразделяются на процедуры и функции. Подпрограмму можно определить как (относительно обособленную) часть программы, оформленную в виде отдельной синтаксической конструкции и снабженную именем (идентификатором). Вызов подпрограммы (говорят также
46 – обращение к подпрограмме), влекущий за собой выполнение операторов, заданных в описании подпрограммы, может быть произведен в некоторой точке программы посредством указания имени этой подпрограммы и, возможно, списка фактических параметров. Примечание. Кроме того, что подпрограммы позволяют улучшить структуру программы (говорят – структурировать программу), они также освобождают программиста от рутинных действий, связанных с повторением (копированием) “почти” идентичных фрагментов кода в различных местах программы. Структура подпрограммы весьма сходна со структурой программы. Также как и программа, подпрограмма состоит из заголовка и тела подпрограммы. Заголовок подпрограммы зависит от вида подпрограммы – процедура или функция – хотя сразу следует отметить, что сходных моментов здесь больше, чем отличий. Заголовки процедуры и функции имеют следующий вид: procedure имя [(список-формальных-параметров)]; function имя [(список-формальных-параметров)] : тип-результата; где список-формальны-параметров представляет собой описание данных, передаваемых в процедуру или функцию (см. ниже механизмы передачи параметров в подпрограммы); тип-результата − имя типа данных для результата, возвращаемого функцией в точку ее вызова в программе. Телом подпрограммы является блок, который, как известно, состоит из следующих разделов: • раздел описания меток; • раздел определения констант; • раздел определения типов; • раздел описания переменных; • раздел определения процедур и функций; • раздел операторов. Вообще любую подпрограмму можно рассматривать как программу в миниатюре. Она также может иметь собственные разделы описания меток, определения констант, определения типов данных, описания переменных и даже раздел определения собственных процедур и функций. Ниже приведены примеры определения и использования процедур и функций. Процедуры Процедурой в языке Паскаль является совокупность операторов и сопутствующих им локальных описаний объектов данных (с которыми “работают” эти операторы), связанная с именем процедуры. Поясним это положение на следующем простом примере. Пусть в некотором месте программы находится следующий оператор цикла: repeat
47 read (c) until c <> ' ' ; который считывает символы из входного файла до тех пор, пока не встретится символ, отличный от пробела. Этот оператор можно легко оформить в виде процедуры, для которой наиболее подходящим именем будет SkipSpaces (пропустить пробелы), и вызывать эту процедуру в любом месте программы, где требуется выполнить пропуск пробелов перед обработкой значащих символов. Определение этой процедуры запишется следующим образом: procedure SkipSpaces; begin repeat read (c) until c <> ' ' end; После того как в программе определена процедура SkipSpaces, ее вызов в теле программы будет приводить к тому же результату, что и выполнение оператора цикла с постусловием, приведенного выше. Таким образом, используя в программе вызов процедуры SkipSpaces вместо составляющего эту процедуру оператора цикла, мы как бы поднимаемся на более высокий уровень абстракции, так как оперируем “макродействием” “пропустить пробелы”, а не “микродействиями”: “прочитать символ”, “проверить, является ли этот символ пробелом”, “если это пробел, то прочитать следующий символ” и т.д. Рассмотрим более сложный пример программы, использующей вложенные процедуры. Эта программа считывает из файла целые числа и выполняет их суммирование, причем складывает отдельно положительные числа и отдельно отрицательные; одновременно с суммированием она ведет подсчет положительных и отрицательных чисел, содержащихся в файле. program Example; var InFile : file of integer; {Входной файл } CountPos, CountNeg : word;{Счетчики положит. и отрицат. чисел } SumPos, SumNeg : longint; {Суммы положит. и отрицат. чисел } procedure Start; { Начальные действия } begin assign(InFile,'d:\gr711\Numbers.dat'); reset(InFile); CountPos := 0; CountNeg := 0; SumPos := 0; SumNeg :=0 end; procedure Process; { Вычисление сумм и количества чисел } var Val : integer; { Вложенные процедуры }
48 procedure ProcessPos(Item : integer); { Обраб-ка положит. чисел } begin SumPos := SumPos + Item; CountPos := CountPos + 1 end; procedure ProcessNeg(Item : integer); { Обработка отрицат. чисел } begin SumNeg := SumNeg + Item; CountNeg := CountNeg + 1 end; begin { Process } repeat read(InFile,Val); if Val > 0 then ProcessPos(Val) else ProcessNeg(Val) until eof(InFile) end; { Process } procedure Finish; { Завершающие действия } begin writeln('Сумма ',CountPos,' положительных чисел = ',SumPos); writeln('Сумма ',CountNeg,' отрицательных чисел = ',SumNeg); close(InFile) end; begin { Основная программа } Start; Process; Finish end. { Конец программы } Приведенная выше программа содержит три процедуры, каждая из которых решает одну частную подзадачу: • открытие входного файла и инициализация глобальных переменных; • собственно вычисление сумм и подсчет количества чисел; • вывод результатов и закрытие файла. При этом центральная процедура, имеющая имя Process, содержит две вложенные процедуры − ProcessPos и ProcessNeg, каждая из которых выполняет конкретные вычисления, соответственно, для положительных и отрицательных чисел. Сама центральная процедура работает как управляющая по отношению к вложенным процедурам, в цикле передавая управление той или иной процедуре. Аналогичный характер имеет алгоритм основной программы, кото-
49 рый представлен тремя “макрооперациями”, обозначающими последовательные этапы работы. Функции Смысл функции заключается в задании вычисления некоторого значения и организации возврата (передачи) этого значения в точку вызова функции. Возврат вычисленного значения организуется следующим образом. В теле функции должен присутствовать оператор присваивания, в левой части которого должен присутствовать идентификатор, совпадающий с именем функции, а в правой части – выражение, вычисляющее возвращаемое значение. Тип выражения в правой части должен быть совместим с типом функции (т.е. с типом, указанным в ее заголовке после списка параметров). Следует иметь в виду, что функция может возвращать в качестве результата значение простого и ссылочного типа. Ниже приведен пример описания функции целого типа, возвращающей максимальное значение из двух целых значений, переданных ей в качестве параметров. function MaxAB(A,B: integer): integer; begin if A > B then MaxAB := A then MaxAB := B end; Обращение к функции MaxAB может быть использовано в выражении, например: M := MaxAB(X-Y, X+Y)+2*MaxAB(X*Y, X mod Y); На месте обращений к функции MaxAB будут использованы значения, возвращаемые этой функцией. Механизмы передачи параметров в подпрограммы В заголовке процедуры или функции может быть задан список формальных параметров. Этот список заключается в круглые скобки. Описание формальных параметров весьма сходно с описанием переменных в блоке. Каждый параметр, заданный в заголовке подпрограммы, считается локальным в данной подпрограмме так же, как и переменные, описанные в блоке этой подпрограммы. Имена формальных параметров представляют собой условные обозначения в теле подпрограммы тех фактических параметров, которые будут переданы в подпрограмму при ее вызове. Если в подпрограмме необходимо использовать
50 формальный параметр с типом, что следует иметь в виду, что этот тип должен быть задан именем (идентификатором) типа. Например, следующий заголовок процедуры является недопустимым: procedure InCorrect (var A: array [1..10] of byte); а заголовок type MyArray=array [1..10] of byte; ... procedure Correct (var A: MyArray); ... вполне корректен. Существуют три способа задания формальных параметров в списке формальных параметров в заголовке подпрограммы: 1. Параметры, перед которыми отсутствует служебное слово var и за которыми следует имя типа. 2. Параметры, перед которыми указано служебное слово var и за которыми следует имя типа. 3. Параметры, перед которыми указано служебное слово var, а имя типа отсутствует. Эти три способа задания формальных параметров отражают три различных механизма (способа) передачи параметров в подпрограмму: • передача параметров по значению; • передача параметров по ссылке; • передача нетипизированных параметров по ссылке. Одна подпрограмма может получать различные параметры всеми тремя способами одновременно. Мы рассмотрим два первых способа передачи параметров (называемых параметрами-значениями и параметрами-переменными соответственно), которые имеют место в стандартном Паскале. Третий способ передачи параметров (называемых нетипизированными параметрамипеременными) имеется только в Турбо-Паскале и представляет некоторое отступление от принципов строгой типизации, характерных для языка Паскаль. Передача параметров по значению Этот способ является наиболее распространенным и самым простым способом передачи параметров. В данном случае параметр считается обычной локальной переменной в пределах подпрограммы; особенность этой переменной заключается в том, что при вызове подпрограммы начальное значение параметра автоматически устанавливается равным значению соответствующего фактического параметра, заданного при вызове подпрограммы. Этот фактический параметр может быть произвольным выражением того же типа, что и формальный параметр.
51 В теле подпрограммы возможны произвольные действия с данным формальным параметром, но любые изменения его значения никак не отражаются на значениях переменных вне данной подпрограммы. Ниже приводится пример процедуры, использующей механизм передачи параметров по значению. procedure SumSquare (X, Y : real); begin X := X * X; Y := Y * Y; writeln('Сумма квадратов = ', X+Y) end; Вызов этой процедуры может выглядеть следующим образом: var A, B : real; begin A := 1.75; B := 8.25; SumSquare(A,B); ... end. При вызове процедуры SumSquare с фактическими параметрами A и B значения этих параметров (один раз) копируются в соответствующие формальные параметры X и Y, и дальнейшие манипуляции с формальными параметрами в теле этой процедуры никак не влияют на значения переменных A и B. Передача параметров по ссылке Этот способ передачи параметров используется в тех случаях, когда необходимо передать некоторое значение в точку вызова подпрограммы. Поясним это на следующем примере. Пусть необходимо вычислить сумму и разность квадратов и использовать вычисленные значения в вызывающей программе. Для этого можно определить процедуру SumSubSquare следующим образом: procedure SumSubSquare(X, Y : real; var Sum, Sub : real); begin Sum := X*Х+Y*Y; Sub := X*X-Y*Y end; В этом случае формальные параметры Sum и Sub считаются синонимами соответствующих фактических параметров в пределах процедуры SumSubSquare. При этом фактические параметры должны быть переменными (не выражениями) того же типа, что и формальные параметры. Если вызов процедуры SumSubSquare будет выглядеть следующим образом:
52 var A, B: real; SumAB, SubAB: real; begin A := 1.75; B := 8.25; SumABSquare(A, B, SumAB, SubAB); ... end; то присваивание параметрам Sum и Sub внутри тела процедуры будут означать соответствующие присваивания переменным SumAB и SubAB, переданным процедуре по ссылке. Ниже приведен еще один пример процедуры, использующей механизм передачи параметров по ссылке. Эта процедура производит обмен значений между двумя вещественными переменными. procedure Swap(var X, Y: real); var T : real; begin T := X; X := Y; Y := T end; Обмен значений двух переменных, переданных в процедуру Swap по ссылке, выполняется с помощью промежуточной переменной T. Вызов процедуры Swap может быть выполнен следующим образом: var A, B : real; begin A := 1.75; B := 8.25; Swap(A,B); ... end. Примечание. Следует иметь в виду, что переменные файловых типов могут передаваться в подпрограммы только как параметры-переменные. Завершение подпрограмм Работа процедуры или функции завершается после выполнения последнего оператора ее тела. В Паскале отсутствуют специальный оператор возврата из подпрограммы (подобно оператору RETURN в языке ПЛ/1 или ФОРТРАН). Однако в языке Турбо-Паскаль имеется дополнительное средство прерывания выполнения программы в произвольной точке – системная процедура exit, не
53 имеющая параметров, которая немедленно завершает выполнение подпрограммы и возвращает управление в точку вызова. Необходимо помнить, что перед вызовом процедуры exit в теле функции должен обязательно выполниться оператор присваивания с именем функции в левой части. Следующий пример показывает использование системной процедуры exit для возврата управления во внешний – по отношению к приведенной процедуре P – блок. procedure P(X,Y: real; var Res : real); begin if X-Y < 0.0001 then exit; Res := (X*X+Y*Y)/(X*X-Y*Y) end; Если вызов процедуры exit был произведен из блока, соответствующего программе в целом (т.е. из самого внешнего блока), то будет немедленно завершено выполнение всей программы. Предварительное объявление подпрограмм Как правило, телом процедуры является блок. Однако, имеется несколько исключений из этого правила. Мы рассмотрим одно из них, суть которого состоит в следующем. Пусть имеются две процедуры с именами Proc1 и Proc2, которые вызывают друг друга, причем это процедуры одного уровня вложенности: procedure Proc1(x,y: real); begin ... Proc2(1, 2); ... end; procedure Proc2(a,b: integer); begin ... Proc1(0.5, 0.6); ... end; При трансляции процедуры Proc1 компилятор не может правильно обработать вызов процедуры Proc2, так как эта процедура описывается ниже по тексту программы и информация о ней еще неизвестна. Если произвести обмен местами этих двух процедур в программе, то аналогичная проблема возникнет с трансляцией вызова процедуры Proc1 в процедуре Proc2. Чтобы решить подобную проблему (разорвать порочный круг) в Паскале введен механизм предварительного (опережающего) описания подпрограмм.
54 Предварительное описание содержит заголовок подпрограммы, а вместо тела записывается служебное слово forward, которое указывает, что полное описание подпрограммы располагается в тексте далее. В этом случае заголовок полного (определяющего) описания может быть записан в сокращенном виде, без списка параметров и (для функций) без результата. Для приведенного выше примера предварительные и полные описания будут выглядеть следующим образом: procedure Proc1(x, y: real); forward; procedure Proc2(a, b: integer); forward; procedure Proc1; begin ... Proc2(1, 2); ... end; procedure Proc2; begin ... Proc1(0.5, 0.6); ... end; В данном случае при обработке вызова процедуры Proc2(1, 2) в процедуре Proc1 компилятор использует информацию о процедуре Proc2 из заголовка ее предварительного описания. Следует отметить, что в случае предварительного описания подпрограммы далее в тексте должно обязательно содержаться ее определяющее описание, даже если в программе не встречается вызов этой подпрограммы. Блочная структура программ на Паскале Как отмечалось выше, телом подпрограммы является блок, содержащий описания объектов и группу операторов, работающих с этими объектами. Имена объектов, описанных в блоке подпрограммы (это относится и к процедурам, и к функциям), считаются известными только в пределах данного блока. Поскольку среди описаний блока могут содержаться описания процедур и/или функций, допускается наличие в подпрограмме вложенных подпрограмм, которые, в свою очередь, могут содержать свои вложенные подпрограммы. Таким образом, программы на Паскале имеют блочную структуру. Рассмотрим блочную структуру программы Example, рассмотренной выше. Структура этой программы схематично изображена на рис. 5.
55 Ex ample S tart Process ProcessPos ProcessN eg
Finish Рис.5. На приведенной выше схеме блок основной программы (Example) является самым внешним блоком. Блоки подпрограмм Start, Process и Finish описаны в этом внешнем блоке. Блок подпрограммы Process в свою очередь содержит описания двух подпрограмм – ProcessPos и ProcessNeg, т.е. соответствующие им блоки являются вложенными в блок Process. Такая блочная структура программ традиционна для алголоподобных языков (а Паскаль "вырос" из Алгола-60) и требует определенной дисциплины для доступа к объектам, описанным в различных блоках. Эта дисциплина может быть кратко сформулирована в виде следующих правил: 1. Имена объектов, описанных в некотором блоке, считаются известными в пределах данного блока, включая и все вложенные блоки. 2. Имена объектов, описанных в блоке, должны быть уникальными в пределах данного блока и могут совпадать с именами объектов из других блоков. 3. Если в некотором блоке описан объект, имя которого совпадает с именем объекта в объемлющем блоке, то это последнее имя становится недоступным в данном блоке (говорят, что имя, описанное в блоке, экранирует или закрывает одноименные объекты из внешних – по отношению к данному – блоков). Возвращаясь к приведенной выше схеме, можно сказать, что объекты, описанные в блоке основной программы (Example), известны (говорят также – видны), кроме самого блока Example, еще и в блоках Start, Process, ProcessPos, ProcessNeg и Finish. Имена из блоков Start, ProcessPos, ProcessNeg и Finish известны только в пределах соответствующих блоков. Таким образом, для приведенной выше схемы можно составить табл.7, показывающую области видимости (или области действия) имен объектов: Таблица 7. Блок Example
Можно обращаться к объектам Start, Process, Finish, InFile, CountPos, CountNeg, SumPos, SumNeg
56 Start Process ProcessPos ProcessNeg Finish
Process, Finish, InFile, CountPos, CountNeg, SumPos, SumNeg ProcessPos, ProcessNeg, Val, InFile, CountPos, CountNeg, SumPos, SumNeg, Start, Finish Val, InFile, CountPos, CountNeg, SumPos, SumNeg, Process, ProcessNeg, Start, Finish Val, InFile, CountPos, CountNeg, SumPos, SumNeg, Process, ProcessPos, Start, Finish InFile, CountPos, CountNeg, SumPos, SumNeg, Start, Process Вопросы и упражнения для самопроверки
1. Указать ошибки в описании каждой из следующих функций: а) function F(a : ‘A’..’Z’) : integer; begin F := Ord(a)-Ord(‘P’); if F < 0 then F := -F end; б) function G(k : integer) : 0..MaxInt; var i, s : 0..MaxInt; begin s := 0; for i := 1 to k do s := s + sqr(i) end; в) function H(x : integer) : integer; begin H(x) := (sqr(x) + x) / 2 end; 2. Даны следующие описания: var c, d : integer; procedure P(x, y : integer); begin y := x + 1 end; procedure Q(x : integer; var y : integer); begin y := x + 1 end; procedure R(var x, y : integer); begin y := x + 1 end; a) Для каждой из этих процедур указать, какие из ее параметров являются параметрами-значениями, а какие − параметрами-переменными. б) Определить, что будет выведено на экран: c := 2; d := 0; P(sqr(c) + c, d); writeln(d); c := 2; d := 0; Q(sqr(c) + c, d); writeln(d);
57 Почему при изменении в процедуре параметра-значения соответствующий фактический параметр не меняет своего значения? Что сделать, чтобы он менял значение? 3. Пусть процедура MaxMin(x,y) присваивает параметру x большее из вещественных чисел x и y, а параметру y − меньшее. a) Какие из параметров этой процедуры обозначают исходные данные для нее, а какие − результаты? Какие параметры следует объявить параметрамизначениями, а какие − параметрами-переменными? б) Допустимы ли обращения MaxMin(5.75, sin(z)) и MaxMin(z, k), где z − вещественная переменная, а k − целая? в) Описать данную процедуру и использовать ее для перераспределения значений вещественных переменных a, b и c так, чтобы стало a ≥ b ≥ c. Лекция 11. Модульная организация программ на Турбо-Паскале. Понятие модуля. Общая структура модуля. Использование подпрограмм в модулях (пример модуля). Компиляция и использование модулей. Стандартные модули языка Турбо-Паскаль версии 6.0. Вопросы и упражнения для самопроверки. Модульная организация программ на Турбо-Паскале В Турбо-Паскале версии 4.0 введено понятие модуля. Этот решающий шаг превратил язык Турбо-Паскаль в язык профессионального программирования, пригодный для крупных разработок производственного и коммерческого назначения на современном уровне технологии программирования. Никлаус Вирт создал язык Паскаль как средство обучения программированию. Он прекрасно осознавал, что отсутствие модульности в программах на Паскале крупный недостаток. Поэтому в следующем своем языке, названном Модула (от слова Module − модуль), понятие модуля становится одним из базовых понятий языка. Модуль − это независимо разрабатываемая и хранимая, независимо компилируемая и тестируемая программная единица со строго определенным программным интерфейсом. Модуль содержит в себе программные объекты − константы, типы, переменные и подпрограммы, которые используются другими модулями и программами. Важно понимать, что модуль сам по себе не является выполняемой программой − его программные объекты используются другими модулями и программами. Общая структура модуля Все программные объекты модуля можно разделить на две части: объекты, прямо предназначенные для использования другими программами и моду-
58 лями, и объекты, рабочего (внутреннего) характера, играющие вспомогательную роль в самом модуле. В соответствии с этим модуль имеет две основные части, называемые интерфейсом (interface) и реализацией (implementation). Кроме того, модуль может содержать так называемый раздел инициализации, предназначенный для установки начальных значений переменных модуля перед их использованием. Таким образом, модуль имеет следующую структуру: unit Имя_модуля; { Заголовок модуля } interface { Интерфейс } ... implementation { Реализация } ... end; begin { Раздел инициализации } ... end. В интерфейсной части модуля сосредоточены описания программных объектов, доступных из других программных единиц. В части реализации помещаются рабочие объекты, называемые также невидимыми или скрытыми. Использование подпрограмм в модулях (пример модуля) Процедуры и функции могут использоваться в модулях наравне с другими программными объектами. Однако для них имеются особенности, обусловленные их структурой. Поскольку заголовок подпрограммы содержит всю информацию, необходимую для ее вызова (имя, количество и типы параметров, тип результата − для функции), то он должен быть представлен в интерфейсной части модуля. Тело подпрограммы раскрывает ее алгоритм, поэтому его помещают в раздел инициализации и снабжают сокращенным заголовком. В качестве примера рассмотрим небольшой модуль, содержащий средства для работы с комплексными числами. unit CmplVals; interface type Complex = record Re, Im : real end; procedure InitC(C: Complex; R, I: real); procedure AddC(C1, C2: Complex; var R: Complex); procedure MultC(C1, C2: Complex; var R: Complex); procedure DivC(C1, C2: Complex; var R: Complex); procedure WriteC(C: Complex); implementation procedure InitC; { Инициализация комплексного числа }
59 begin with C do begin Re := R; Im := I end end; procedure AddC; { Сложение двух комплексных чисел } begin with R do begin Re := C1.Re + C2.Re; Im := C1.Im + C2.Im end end; procedure MultC; { Умножение двух комплексных чисел } begin with R do begin Re := C1.Re * C2.Re + C1.Im * C2.Im; Im := C1.Im * C2.Re + C1.Re * C2.Im end end; procedure DivC; { Деление двух комплексных чисел } var Tmp : real; begin with C2 do Tmp := Re * Re + Im * Im; with R do begin Re := (C1.Re * C2.Re + C1.Im * C2.Im) / Tmp; Im := (C2.Re * C1.Im + C1.Re * C2.Im) / Tmp end end; procedure WriteC; { Вывод комплексного числа } begin with C do begin write(Re); if Im = 0 then exit; if Im > 0 then write(‘+’); write(Im);
60 write(‘i’) end end; end. Таким образом, механизм модулей позволяет скрыть детали реализации тех или иных программных подсистем, предоставив в распоряжение использующих модуль программ строго определенную совокупность интерфейсных объектов. Пример программы, использующей модуль CmplVals: program Proba; uses CmplVals; var C1, C2, C3 : Complex; begin Init(1,2,C1); Init(3,4,C2); Mult(C1,C2,C3); write(C3); DivC(C1,C2,C3); write(C3) end. Необходимо отметить следующие важные моменты, связанные с использованием модулей: 1. Если имена интерфейсной части модуля частично пересекаются с именами использующей модуль программы, то действует следующее правило видимости имен: интерфейсные имена модуля, указанного в списке uses первым образуют самый внешний блок программы; интерфейсные имена второго модуля образуют блок, вложенный в первый блок, и т.д. Таким образом, если во втором модуле находятся имена, совпадающие с именами первого модуля, то они будут “экранировать” имена первого модуля. Чтобы обеспечить к ним доступ необходимо использовать формат, похожий на формат селектора записи: имя_модуля.имя_программного_объекта 2. Возможны случаи косвенного использования имен. Например, имеются два модуля: unit A; unit B; interface interface ... uses A; end. ... end. Если некоторая программа использует модуль B, то в соответствующей строке uses должна быть указана вся цепочка используемых модулей, даже если программа не использует непосредственно объекты модуля A. 3. Схема использования модулей может образовывать древовидную структуру любой сложности, но при этом недопустимо явное или косвенное об-
61 ращение модуля к самому себе. Так, например, следующие отношения являются ошибочными: unit A; unit B; interface interface uses B; uses A; ... ... end. end. Однако допускается взаимное использование модулей, позволяющее ослабить указанное ограничение. В этом случае список модулей в uses может указываться в разделе реализации. 4. Если в модуле имеется раздел инициализации, то операторы из этого раздела будут выполнены перед началом выполнения программы, в которой используется данный модуль. Если программа использует несколько модулей, то их разделы инициализации будут выполнены в том же порядке, в котором эти модули перечислены в списке uses. Компиляция и использование модулей Заголовок модуля, как правило, совпадает с именем файла, содержащего исходный текст модуля (расширение файла несущественно, но по умолчанию предполагается .PAS). Компилятор помещает код модуля, полученный в результате компиляции, в файл с таким же именем и расширением .TPU. При компиляции программы, использующей этот модуль, компилятор ищет TPU-файл с именем, заданным в спецификации uses, и связывает его с кодом программы. Таким образом, в спецификации uses фактически задаются не имена модулей, а имена файлов, их содержащих. Например, если в программе имеется строка uses MyUnit; компилятор перед компиляцией самой программы должен найти на диске файл с именем MYUNIT.TPU; в этом файле должен находиться код модуля с заголовком unit MyUnit; Если по какой-либо причине необходимо хранить код модуля в файле с другим именем, то можно использовать директиву $U для переопределения имени файла. Эта директива, имеющая параметр, рассматриваемый как “настоящее”имя файла с данным модулем, помещается непосредственно перед именем модуля в спецификации uses. Например, следующая строка: uses {$U MYFILE} MyUnit; приведет к тому, что компилятор будет искать код модуля MyUnit в файле MYFILE.TPU. При компиляции программы или модуля, использующего другие модули, компилятор последовательно отыскивает файлы, содержащие коды используе-
62 мых модулей с тем, чтобы подключить их к компилируемой программе. При этом компилятор работает по следующей схеме: 1. Компилятор просматривает содержимое системного библиотечного файла TURBO.TPL (Turbo Pascal Library). Этот файл библиотеки модулей имеет специальную структуру и предназначен для компактного хранения и быстрого доступа к коду следующих системных модулей: System, Dos, Crt, Printer и Overlay. С помощью утилиты TPUMOVER.EXE можно помещать код необходимых модулей в файл TURBO.TPL и удалять код неиспользуемых модулей. 2. Если искомый модуль в файле TURBO.TPL, то компилятор осуществляет поиск соответствующего TPU-файла в текущем каталоге. 3. Если в текущем каталоге нужный файл не найден, то поиск продолжается в каталогах, заданных в опциях {Options | Directories | Unit Directories} интегрированной среды разработки (ИСР) языка Турбо-Паскаль версии 6.0. 4. Если на предыдущих шагах файл не найден, то компилятор выдает диагностическое сообщение и прекращает работу. 5. Если компилятор активизирован командами {Compile | Make} или {Compile | Build}, то описанные выше шаги проводятся в поисках исходных текстов используемых модулей, которые будут откомпилированы перед компиляцией самой программы. При этом подразумевается, что имя файла с текстом модуля совпадает с именем модуля и имеет расширение .PAS.
Стандартные модули языка Турбо-Паскаль версии 6.0 Язык Турбо-Паскаль версии 6.0 имеет восемь стандартных модулей: SYSTEM, DOS, CRT, PRINTER, OVERLAY, GRAPH, TURBO3, GRAPH3. Каждый стандартный модуль содержит логически связанную совокупность типов, констант, переменных и подпрограмм, относящихся к определенной области применений, и хранится в одноименном TPU-файле в системном каталоге системы Турбо-Паскаль. Модуль System содержит все процедуры и функции стандартного языка Паскаль, а также множество дополнительных подпрограмм общего характера, в частности, ориентированных на конкретную операционную среду. Модуль Dos содержит средства для доступа к возможностям (службам) операционной системы MS-DOS. Модуль Crt содержит средства для управления выводом на экран монитора в текстовом режиме и чтением информации с клавиатуры, а также простейшие средства для управления звуком встроенного динамика компьютера. Модуль Printer содержит единственный интерфейсный элемент − переменную Lst стандартного типа text, системно связанную с логическим устройством PRN (т.е. с печатающим устройством, если оно имеется в системе). Исполь-
63 зование этой переменной в стандартных процедурах Write и WriteLn приводит к выводу информации на печать. Модуль Overlay содержит средства для создания так называемых оверлейных программ (или программ с перекрытиями). Эти средства позволяют создавать большие программные системы, размер которых превышает объем доступной оперативной памяти. Модуль Graph содержит средства для работы в графических режимах, обеспечиваемых наиболее распространенными типами видеоадаптеров: CGA, EGA, VGA и т. п. Модули Turbo3 и Graph3 обеспечивают совместимость программ, разработанных в системе Турбо-Паскаль версии 3.0, с данной версией языка ТурбоПаскаль. Вопросы и упражнения для самопроверки 1. Указать место программного модуля в блочно-иерархической структуре программной системы, написанной на языке Турбо-Паскаль версии 6.0 по отношению: а) к основной программе; б) к подпрограммам, входящим в состав программы и программных модулей; в) к описаниям и операторам, составляющим подпрограммы, модули и программы? 2. Назвать структурные части модуля и указать их назначение и порядок следования в модуле.
3. Какие требования предъявляются к заголовку модуля? Может ли заголовок модуля содержать более восьми символов? Почему? 4. Каким образом указать компилятору на необходимость подключения кода некоторого модуля к коду программы? 5. Какие правила “экранирования” действуют при подключению к программе нескольких модулей? Как предотвратить экранирование программных объектов, описанных в различных модулях? 6. Перечислить стандартные модули системы Турбо-Паскаль версии 6.0 и кратко указать функциональное назначение каждого из них. 7. Что представляет собой файл TURBO.TPL? Какие стандартные модули представлены в этом файле? Какая утилита используется для добавления модулей в файл TURBO.TPL? Удаления модулей из файла TURBO.TPL? 8. Создать свой собственный модуль, содержащий средства для решения задач из некоторой проблемной области (например, процедуры, реализующие численные методы решения уравнений).
64
Содержание Лекция 6. Составные типы данных языка Паскаль. Регулярные типы (массивы). Примеры программ, использующих регулярные типы данных. Строковый тип данных (строки символов). Пример программы, использующей строковый тип данных. Лекция 7. Комбинированный тип данных (записи). Фиксированные записи. Записи с вариантами. Примеры программ, использующих записи. Оператор присоединения (WITH). Пример программы, использующей оператор WITH. Вопросы и упражнения для самопроверки. Лекция 8. Ввод-вывод в программах на языке Паскаль. Файловый тип данных. Операции над файлами. Начальные и завершающие операции ввода-вывода. Операции ввода-вывода. Операции перемещения по файлу. Специальные операции ввода-вывода. Примеры программ, использующих операции ввода-вывода. Вопросы и упражнения для самопроверки. Лекция 9. Текстовые файлы. Процедуры и функции для работы с текстовыми файлами. Примеры программ, использующих текстовые файлы. Обработка ошибок ввода-вывода в программах на ТурбоПаскале. Вопросы и упражнения для самопроверки. Лекция 10. Понятие подпрограммы. Подпрограммы в языке Паскаль: процедуры и функции. Примеры программ, использующих процедуры и функции. Механизмы передачи параметров в подпрограммы. Передача параметров по значению. Передача параметров по ссылке. Завершение подпрограмм. Предварительное объявление подпрограмм. Блочная структура программ на языке Паскаль. Вопросы и упражнения для самопроверки. Лекция 11. Модульная организация программ на Турбо-Паскале. Понятие модуля. Общая структура модуля. Использование подпрограмм в модулях (пример модуля). Компиляция и использование модулей. Стандартные модули языка Турбо-Паскаль версии 6.0. Вопросы и упражнения для самопроверки.
3
14
24
35
45
57
65
Стариков Александр Вениаминович
ИНФОРМАТИКА Основы программирования на языке Паскаль Тексты лекций в 3-х частях Часть 2
Редактор С.О. Петровская
Подписано в печать 29.04.99. Форм. бум. 297×420 1/16. Бум. ZOOM. Усл. п. л. − 3,72. Уч.-изд. л. − 4,58. Тираж 75. Заказ № РИО ВГЛТА. УОП ВГЛТА. 394613, г. Воронеж, ул. Тимирязева, 8.