Продолжение, начало см. в МК №46, 51-52, 4, 6-7, 10, 12-13, 16-18, 22, 24, 29, 34, 41, 46, 4, 6, 17, 21, 23, 28, 30, 32, 39, 42 (165, 170-171, 175, 177-178, 181, 183-184, 187-189, 193, 195, 200, 205, 212, 217, 227, 229, 240, 244, 246, 251, 253, 255, 262, 265).
Выражения
Как и в любом другом языке программирования, в Паскале выражения играют важнейшую роль при вычислении значений переменных. Обычно выражение строится из операндов, знаков операций и круглых скобок.
В качестве операндов могут быть константы, переменные, элементы массивов, поля записей, разыменованные указатели, вызовы функций — короче, любой скалярный тип, строки в случаях конкатенации (сложения строк) и множества.
Каждая операция предполагает некоторые действия по калькуляции новых значений из значений операндов. Операции могут обозначаться специальными комбинациями символов или специальными зарезервированными служебными словами. Операции разделяются на унарные и бинарные. Унарные операции применяются к одному операнду, например, –X, где знак операции ставится перед операндом. Бинарные операции для выполнения некоторого действия требуют указания двух операндов, между которыми и указывается обозначение операции — например, A–B.
При выполнении выражения очень важна очередность выполнения операций. Как и в математике, операции разделяются по приоритетам очередности.
В первую очередь выполняются унарные операции с наивысшим приоритетом:
– — унарное изменение знака операнда;
@ — определение адреса переменной;
not — логическая операция инвертирования (отрицание).
Вторым приоритетом обладают бинарные операции из группы «умножение»:
*, / — умножение и деление;
div, mod — деление нацело, взятие остатка от деления нацело;
and — логическое умножение («И»);
shr, shl — поразрядный логический сдвиг вправо и влево соответственно.
Третий приоритет имеют операции группы «сложение»:
+, – — сложение и вычитание;
or, xor — логическое сложение («ИЛИ») и сложение по модулю два («исключающее ИЛИ»).
И самый низший приоритет имеют бинарные операции отношения:
=, <> сравнение на равенство и неравенство;
<, >, <=, >= — строгие и нестрогие сравнения;
in — проверка принадлежности множеству.
Чтобы правильно определить порядок калькуляции выражения, необходимо следовать правилам:
операнд, который указан между двумя операциями с разными приоритетами, связывается с операцией, обладающей более высоким приоритетом;
операнд, который указан между двумя операциями с равными приоритетами, связывается с операцией, которая находится слева от него. Таким образом, операции с одинаковыми приоритетами выполняются слева направо.
фрагмент выражения, заключенный в круглые скобки, интерпретируется как отдельный операнд. То есть, предварительно будут выполнены все операции внутри данного подвыражения в круглых скобках, а затем и все остальные операции слева и справа от круглых скобок.
Для изменения очередности выполнения операций могут служить круглые скобки. Выражения могут использоваться в качестве операндов процедур и функций.
Файловые типы и ввод-вывод
Любая программа призвана что-то выполнять, обрабатывать какие-то данные и сохранять результат обработки на носитель информации (диск). Отсутствие поддержки работы с файлами затмило бы все преимущества любого языка программирования и сделало бы язык бесполезным. Это, конечно же, понимали разработчики пакета Turbo Pascal фирмы Borland. Поэтому они постарались встроить в язык достаточно простые и в тоже время мощные механизмы работы с файлами.
Для работы с любым файлом необходимо объявить специальную переменную файлового типа, которая будет служить неким указателем на файл. Как несколько обычных указателей могут ссылаются на один и тот же массив в памяти, так и несколько файловых переменных могут указывать на один и тот же файл. При этом любой массив рассматривается как область данных, имеющая свое окончание. В отличие от массивов, файлы считаются бесконечными, и данные в них представлены как бесконечно длинный список элементов определенного размера. То, что работа с любым файлом рассматривается как работа с последовательными порциями (записями, элементами) единиц данных, является характерной чертой языков высокого уровня. При этом такие порции могут иметь постоянный размер на протяжении всего файла, и тогда они следуют одна за другой без разделителя. Если же записи в файле имеют разный размер, то они разделяются, например, парой возврат каретки/перевод строки, как в текстовых файлах. Например, для работы с файлом, в котором данные представлены как список значений типа Word, можно описать следующую файловую переменную, используя служебные слова file of:
var FileWord : file of word;
В таком файле все элементы будут пронумерованы с нуля, и чтение или запись будет производиться начиная с некоторого элемента поочередно, то бишь последовательно. Таким образом за одно чтение/запись происходит обмен с файлом только одним элементом, в данном случае с базовым типом файла Word. При объявлении базового типа файла можно использовать любой тип кроме файлового, а также комбинированного, одно из полей которого имеет файловый тип.
Любой файл, как и массив, можно интерпретировать по-разному. Стало быть, нам ничто не мешает записать на диск файл со значениями типа Word, а затем обратиться к нему же как файлу file of longint. Результат, конечно, получим немного другой, но мало ли какие махинации могут понадобиться на нескучной стезе программиста! Еще примеры:
type TRec = record X, Y : Single; end; var FileRec : file of TRec; FileStr : file of string;
Операции подготовки и завершения работы с файлами
Как и любой указатель следует установить так, чтобы он ссылался на некоторый массив в памяти, так и файловую переменную необходимо связать с определенным файлом. Для этого служит процедура Assign(var f; name : string), первым параметром которой должна быть файловая переменная, а вторым — символьная строка, содержащая полный путь к файлу:
Assign( FileWord, ’c:\myprogs\words.dat’ );
если искомый файл находится в текущей папке с запущенной программой, то путь к файлу можно опустить, сделав так:
Assign( FileWord, ’words.dat’ );
Помимо файла на диске, вторым параметром данной процедуры может быть псевдоним стандартного устройства:
CON — при выводе информации на данное устройство данные будут отображаться в символьной форме на экране, а при вводе информации с данного устройства данные будут поступать с клавиатуры;
LPT1, LPT2, LPT3 — предполагаются печатающими устройствами (одновременно не более трех) и доступны только для вывода;
PRN — синоним LPT1;
COM1, COM2 — предполагаются последовательными коммуникационными портами и доступны как для ввода, так и для вывода информации;
AUX — синоним COM1;
NUL — пустое устройство; может пригодиться для указания некоторого файла, в который можно выводить данные, — но они никуда не поступят.
После того как файловая переменная связана с определенным файлом, его следует открыть для предстоящих операций чтения/записи процедурой Reset(var f : file [; recsize : word]) и в качестве единственного параметра указать связанную файловую переменную (второй параметр пока опустим). При этом предполагается, что файл уже существует, иначе произойдет ошибка. Если файл необходимо очистить и заполнить наново, или если такой файл отсутствует, его можно создать процедурой Rewrite(var f : file [; recsize : word]), указав в качестве параметра файловую переменную (второй параметр пока опустим). В результате выполнения любой из этих двух стандартных процедур операционная система организует буферы обмена для данного файла и устанавливает текущую позицию файла на нулевой элемент, то есть на начало файла. После этого файл готов к операциям чтения/записи.
При выводе данных в файл все пересылки кэшируются в буфере обмена. Представьте себе ситуацию: программа выполнила вывод важных данных в файл, но при этом операционная система помедлила с переброской их из буфера обмена в файл, и тут, как говорится, «во всей деревне свет погас». Останется лишь развести руками. Чтобы предотвратить такую неприятность, достаточно вызвать процедуру Flush(var f : text) с файловой переменной в качестве параметра, и все данные, находящиеся в буфере обмена, будут сброшены в файл. Перед завершением работы с файлом или самой программы вызывать данную процедуру не имеет смысла, так как завершающая процедура закрытия файла Close(var f) сделает аналогичную операцию.
В конце концов, следует корректно завершить сеанс работы с файлом, закрыв его процедурой Close с файловой переменной вместо параметра. При этом операционная система освободит все буферы обмена, образованные при открытии данного файла. Теперь файловая переменная не связана ни с одним файлом, ее можно привязывать к другому файлу посредством процедуры Assign.
Конечно, при завершении программы происходит автоматическое закрытие всех файлов. И все же использование процедуры Close — признак хорошего стиля программирования.
Операции последовательного чтения/записи
Так как следующие операции ввода-вывода не позволяют читать/записывать более одного элемента (например, в некоторый массив), их называют операциями последовательного ввода-вывода. Речь пойдет об универсальных процедурах Read и Write. Утешает, что данные процедуры могут содержать несколько параметров и осуществлять обмен сразу несколькими записями файла. При этом указанные параметры могут иметь различные типы.
Процедура Read предназначается для чтения из файла, имеет файловую переменную на месте первого параметра, а также перечень переменных базового типа открытого файла, в которые и будет производиться загрузка значений из файла. Действие данной процедуры таково: если после файловой переменной указать N переменных базового типа, то с текущей позиции указателя файла будет последовательно прочитано N значений. При этом в первую переменную будет загружен элемент файла из текущей позиции файла. Затем указатель файла будет передвинут на следующую запись, и во вторую переменную будет загружен следующий элемент. Потом снова происходит смещение указателя на следующий элемент, и так далее до N-й переменной. После этого позиция указателя файла будет указывать на элемент, следующий за последним прочитанным элементом, то есть, прочитав N записей из файла, его указатель автоматически сдвигается на N записей в сторону хвоста файла.
В ходе чтения из файла может наступить такая ситуация, когда после или во время выполнения очередной процедуры Read указатель файла будет указывать на несуществующую запись, то есть на окончание файла — тогда попытка чтения из файла несуществующей записи приведет к возникновению ошибки Error 100: Disk read error (Ошибка чтения с диска). Чтобы предупредить возникновение такой ситуации, следует использовать функцию EoF(var f):boolean, которая позволяет определить, достигнут ли конец файла после очередной операции чтения. Примеры:
var A, B, C : longint; begin … Read( FileLongint, A, B, C ); … end.
или
var W : word; begin Assign( FileWord, 'words.dat' ); Reset( FileWord ); while not EoF( FileWord ) do begin Read( FileWord, W ); writeln( 'Value= ', W ); end; Close( FileWord ); end.
Параметры процедуры Write для записи в файл могут заполняться так же, как и для процедуры Read, то есть длинным списком переменных базового типа для открытого файла. Действие процедуры Write сводится к записи значения очередной переменной в текущую позицию файла, затем происходит смещение указателя файла на один элемент. Если при выполнении очередной операции записи будет достигнут конец файла, при следующей операции записи в хвост файла будет добавлен еще один элемент, после чего размер файла увеличится. Следует учесть, что формат процедуры Write в случае работы с файлами исключает использование выражений в качестве записываемых значений, в отличие от текстового вывода на дисплей. Примеры:
var W : word; begin Assign( FileWord, 'words.dat' ); Rewrite( FileWord ); W := random(65535); Write( FileWord, W ); W := random(65535); Write( FileWord, W ); Close( FileWord ); end.
Продолжение следует.
