12.6.2. Операции ввода-вывода в текстовые файлы
Ввод и вывод числовой и текстовой информации в Турбо Паскале осуществляется операторами:
ввод – Read( f, X ) или Read( f, X1,X2,...,Xn ) и
ReadLn( f, X ) или ReadLn( f, X1,X2,…,Xn );
вывод – Write( f, X ) или Write( f, X1,X2,…Xn ) и
WriteLn( f, X ) или WriteLn( f, X1, X2,…,Xn ).
Если в операторе ввода-вывода первым параметром стоит логическое имя файла, то это означает, что поток данных будет приниматься (Read) или направляться (Write) на конкретное физическое устройство компьютера, связанное в данный момент с логическим именем этого файла.
Если операторы содержат один лишь список ввода-вывода, то считается, что ввод сопряжен со стандартным логическим файлом Input (под ним подразумевается клавиатура с «эхом» ввода на экране), а вывод — с логическим файлом Output (что соответствует выводу на экран дисплея).
Имена Input и Output являются предопределенными в системной библиотеке (модуле System). Напомним, что в стандартном Паскале любая программа, использующая ввод-вывод, должна начинаться со слов
PROGRAM имя( Input, Output);
что, по сути, открывает каналы ввода-вывода. В Турбо Паскале можно смело опускать описание PROGRAM и не надо описывать имена Input и Output.
Таким образом, оператор Read( x1, x2) полностью эквивалентен оператору Read( Input, x1, x2 ), а оператор Write( х3, х4) — оператору Write( Output, х3, х4 ).
12.6.2.1. Операторы Read/ReadLn. Рассмотрим сначала операторы ввода информации — Read и ReadLn. Их аргументами должен быть список переменных, значения которых будут считаны (введены). Тип переменных при вводе из текстового файла (в том числе и
- 239 -
с клавиатуры) может быть только целым, вещественным, символьным (Char), строковым или совместимым с ними. Сложные структурированные типы (такие, как массивы, множества, записи и др.) могут быть введены только по элементам (по полям для записей). Например:
| VAR
| i : Word
| l : Longint;
| r : Real;
| Rec RECORD { запись }
| x, у : Real
| ch : Char
| END;
| Dim : Array [0...99] of Byte; { массив }
| S : String;
| BEGIN
{ . . . ЧИТАЮТСЯ С КЛАВИАТУРЫ: . . . }
| Read( i, l); { два целых числа,}
| Read( l, r, s); { целое, вещественное число и строка,}
| Read( Rec.x, Rec.у, Rec.ch ); { запись по полям,}
| for i:=0 to 99 do
| Read( Dim[i] ); { ввод массива }
| END.
Всякие попытки вставить «сокращения» типа Read (Rec) или Read (Dim) вызовут понятное возмущение компилятора. Такое возможно лишь при вводе из типизированного файла (см. разд. 12.7).
При вводе из текстового файла, будь то файл на диске или клавиатура, необходимо помнить правила чтения значений переменных. Когда вводятся числовые значения, два числа считаются разделенными, если между ними есть хотя бы один пробел, или символ(ы) табуляции (#9), или символ(ы) конца строки (#13). Так, при выполнении оператора Read( i, r ) можно ввести значения с клавиатуры несколькими способами:
123 1.23 [Клавиша ввода]
или
123 [Клавиша ввода] 1.23 [Клавиша ввода]
При вводе с клавиатуры последней всегда должна нажиматься клавиша ввода, ибо именно она заставляет программу принять введенные перед этим буквы и цифры. При чтении из других текстовых файлов символ конца строки (код клавиши ввода), вообще говоря, ничем не лучше пробела или табуляции.
- 240 -
Если читается символьное значение, то в соответствующую переменную запишется очередной символ за последним введенным до этого. Здесь уже нет никаких разделителей:
VAR
ch1, ch2 : Char;
...
Read( ch1, ch2 );
В этом случае надо не спешить нажать клавишу ввода на клавиатуре:
аб[ВВОД] --> ch1 = 'а', ch2 = 'б'
а[ВВОД]б[ВВОД] --> ch1 = 'а', ch2 = #13
[ВВОД][ВВОД] --> ch1 = #13, ch2 = #13
И, наконец, ввод строк. Начало строки идет сразу за последним введенным до этого символом (с первой позиции, если соответствующая строчная переменная стоит первой в списке ввода). Считывается количество символов, равное объявленной длине строки. Но если во время считывания попался символ #13, то чтение строки прекращается. Сам символ #13 (конец строки) служит разделителем строк и в переменную никогда не считывается.
Рассмотрим пример комплексного списка ввода:
Read( realVar, intVar, Ch, String11, String88).
При вводе последовательность символов будет разбита на разнотипные части следующим образом ([#13] обозначает один символ с кодом 13):
Обратите внимание, что символьная переменная Ch здесь может заполучить себе только пробел (или #13 и #9), иначе, если следом сразу начнется строка, произойдет сбой при чтении целого значения! Строка StringSS будет заполнена не целиком, а на ту часть, которая успела считаться до символа #13 (нажатия клавиши ввода или конца строки в файле).
Понятно, что лучшим способом избежать заложенных во вводе текстовой информации подвохов будет отказ от смешанных списков ввода, по крайней мере при чтении с клавиатуры.
- 241 -
Спецификация формата ввода чисел как таковая отсутствует, и единственное требование состоит в том, чтобы написание числовых значений соответствовало типам переменных в списке ввода.
При вводе с клавиатуры («с экрана») особой разницы между Read и ReadLn нет. Процедура ReadLn (Read Line) считывает значения в текущей строке и переводит позицию на начало следующей строки, даже если в текущей строке остались непрочитанные данные. Так, при чтении в текстовом файле f строки:
12.3 13.4 14.5 15.6
оператором ReadLn( f, r1, r2) вещественные переменные r1 и r2 получат значения 12.3 и 13.4, после чего произойдет переход на другую строку, и следующие два числа (14.5 и 15.6) будут проигнорированы. Вызов ReadLn ( f ) вообще пропустит строку в файле f. Вызов ReadLn без указания файла сделает паузу до нажатия клавиши ввода.
Символ-признак конца текста #26 также является разделителем и ограничивает строку, но за ним чтение уже невозможно. Файл на нем кончается! Конец файла может быть считан в символьную переменную, в строчную он не войдет (как не входит символ #13), а чтение #26 вместо ожидаемых числовых значений эквивалентно прочтению 0.
12.6.2.2. Операторы Write/WriteLn. Операторы Write и WriteLn выводят значение X или список значений X1, Х2,..., Хn в текстовый файл f. Если файл не указан, то считается, что вывод направлен в файл Output (на дисплей). Значения, как и при вводе, могут иметь лишь целые, вещественные, символьные и строковые типы, а также производные от них. Всевозможные структуры (записи, массивы) должны выводится по их полям или элементам. Множества, указатели (Pointer), файловые переменные также не могут быть выведены без предварительного их преобразования в выводимые составляющие. Исключение составляет лишь тип Boolean:
CONST
tr : Boolean = True;
fa : Boolean = False;
...
Write( tr, ' ... ' , fa );
Оператор Write напечатает на экране: 'TRUE ... FALSE'. Предостерегаем от попыток прочитать эти значения из файла в том же виде. Из этого ничего не выйдет. Чтобы получить из файла логическое значение, закодируйте его байтом:
0 = False
1 = True
- 242 -
и считайте в байтовую переменную, а затем преобразуйте в логическое значение:
VAR
by : Byte; { байтовое значение }
boo : Boolean absolute by; { логическое значение }
...
Read( by ); { вводится значение-байт 0 или 1 }
if boo then ... ; {и считается логическим значением }
Вернемся, однако, к выводу данных. Процедура Write выводит данные в текущую строку и не закрывает ее, т.е. следующие данные запишутся в ту же строку. Формально во внешнем файле размер строки не ограничен. Исключение составляет вывод на дисплей. Если выводимый текст «уперся» в правую границу окна экрана, то он на этом месте разрывается и продолжается с начала следующей строки. Кроме того, вывод символа в нижний правый угол окна автоматически сдвинет изображение вверх на строку, т.е. все-таки совершит переход на следующую строку.
Процедура WriteLn (Write Line) выводит именно строку данных и закрывает ее: приписывает символ #13 в ее конец (точнее, символы #13 и #10, но последний как бы «сливается» с основным кодом). Это автоматически открывает следующую строку, а на экране возвращает курсор в крайнюю левую позицию и опускает его на строку вниз.
Оператор WriteLn или WriteLn( f ), где f — имя логического файла, данный без списка вывода, создает пустую строку, содержащую один только признак конца.
Список вывода Write и WriteLn может содержать константы, переменные, выражения, вызовы функций — лишь бы они имели соответствующие типы и были разделены запятыми:
Write( RealVar, ' номер ', intVar, #10#10'сумма=' );
WriteLn( RealVar+IntVar, '+', Cos(5*5) );
Все, что стоит в кавычках или является строковыми (символьными) константами и переменными, выведется в том виде, в каком подставлено, и лишнего места не займет. Но числовые значения будут выводится по-разному. Целые — как пробел или знак '-', а затем число, вещественные — как пробел или знак и затем экспоненциальная запись числа. Имеется возможность управлять форматом вывода данных.
12.6.2.3. Форматы вывода данных. При выводе значений в текстовые файлы или на экран можно указывать формат, т.е. отводить поле для размещения этих значений. Для строчных и символьных значений формат задается одним числом, отделенным от значения двоеточием:
- 243 -
Write( Ch : 2, St : 20 );
Это число показывает, сколько позиций отводится под значение. Так, значение Ch (символ) будет размещено в двух позициях, хотя реально займет лишь одну, а строка St — в 20 позициях. Если реальное значение «короче» формата, излишек будет заполнен пробелами. Но если наоборот (формат «мал»), то значение будет выводиться, игнорируя спецификацию. Ошибки при этом не возникает. Выравнивание значения в поле формата здесь происходит по правому краю. На этом можно сыграть следующим образом. Часто надо выводить значения с середины строки. При выводе на экран можно использовать специальную процедуру из модуля CRT для установки курсора, но при выводе в файл на диске это работать не будет. А как передвинуть значение? Решение очевидно:
Write( ' ', ChapterNameStr );
Но так же очевидно, что это неэффективно (в программе будет попусту «болтаться» столько пробелов!). Разумное решение таково:
Write( ' ' : 25, ChapterNameStr );
Один пробел, но в поле из 25 символов. Эффект будет тот же. Можно даже выкинуть пробел и поставить пустую строку .
Логические значения False и True выводятся как строковые константы и могут быть помещены в заданном поле.
Формат целочисленных значений задается почти так же, как и для строковых — размером поля за значением:
Write( intVar:5, 123:4, (6*8):10 );
Целое число, включая знак минус, если нужен, будет размещено в заданном числе позиций и выровнено по правому краю. Излишки заполнятся пробелами, а если формата не хватит, то он проигнорируется. Формат удобно использовать для вывода таблиц. Пусть надо красиво вывести 10 столбцов целых значений в 80 колонок экрана. Для этого можно задать формат
WriteLn( х1:7, х2:7, х3:7,...х10:7 );
Сложнее формат для вещественных значений. Для записи числа в дробной форме используется удвоенное описание формата: сначала, как обычно, указывается общий размер поля под значение, а затем, снова через двоеточие, число знаков после запятой:
Write( RealVar : 12 : 3, 123.456 : 8 : 1 );
- 244 -
Реальная длина числа равна сумме одной позиции под знак, числа знаков до десятичной точки, одной позиции под точку и значения второго параметра формата. Поэтому бессмысленны форматы типа ':4:3'. В примере переменная RealVar на экране будет иметь три знака после точки, и если полная ее длина не превысит двенадцать позиций, то она будет выровнена по правому краю. Оставшееся место будет пусто. При некорректном задании формата игнорируется только первый его параметр, а число знаков после точки устанавливается всегда корректно, но не превышает точности типа.
В том же примере второе значение будет выведено как 123.5, потому что при форматировании дробная часть округляется до заданного числа знаков. Само значение переменной при этом, конечно, не изменяется.
Можно выводить вещественные числа без дробной части. Для этого следует задать второй параметр равным 0:
Write( 123.456 : 6 : 0 ); { ' 123' }
При необходимости вывода вещественных значений в экспоненциальном формате надо задавать вновь только одно поле. Это поле указывает число позиций, в которых надо разместить число. Само число будет иметь вид -5.5678Е+00 или 0.0012Е-20. При задании подобного формата надо учесть место под знак числа, одну цифру до точки, саму точку, хотя бы одну цифру после точки, и четыре знака под степень — всего восемь позиций.
При формате, меньшем чем восемь позиций, он устанавливается автоматически равным восьми:
Write( 123.456 : 8, ' ', 123.456 : 6 ); { одно и то же }
Увеличивая формат, мы тем самым увеличиваем число значащих цифр после запятой. Максимальное их число определяется типом вещественного числа, и дальнейшее увеличение формата эффекта не даст.
Ряд проблем вызывает использование сопроцессора. В этом случае все вещественные типы при выводе в экспоненциальном формате показывают степень в виде Е+0000, т.е. минимальный формат становится равным ':10', и чтобы сохранить равное число знаков в самом числе при разных режимах работы с сопроцессором, надо менять форматы. Этот момент подробно рассмотрен при описании строковой процедуры Str (разд. 8.3.2.1).
Подобные проблемы переменных форматов, вообще говоря, решаются легко. В Турбо Паскале разрешено задавать форматы целочисленными переменными или константами:
- 245 -
VAR
F, n : ShortInt;
BEGIN
F:=8; n:=3;
WriteLn( 123.456 : F : n );
END.
До сих пор мы рассматриваем форматы, размещающие и форматирующие одновременно. Но когда длина значения заранее неизвестна, размещение-выравнивание по правому краю может дать некрасивые форматы:
Короткое число в формате 10 = 12
Длинное число в формате 10 = 12345678
Можно задать выравнивание по левому краю. В этом случае значение форматируется (если оно числовое) и пишется без предшествующих пробелов: сразу с текущей позиции. При этом занимается поле, равное длине значения. Никаких пробелов справа уже не дописывается:
Короткое число в формате -10 = 12
Длинное число в формате -10 = 12345678
Для задания такого режима надо ближнюю к значению спецификацию формата задавать отрицательной:
Write( 123.456 : 6 : 1, 22 : 4 ); { ' 123.5' и ' 22' }
Write( 123.456 :-6 : 1, 22 :-4 ); { '123.5' и '22' }
Несмотря на некоторое отсутствие гибкости в способе задания формата (нельзя задавать форматы-шаблоны — как в Фортране, Бейсике, а надо описывать каждое значение), механизм форматированного вывода текстовой информации Турбо Паскаля достаточно мощный. Помните только, что форматы имеют смысл лишь при работе с текстовыми файлами. Во всех остальных случаях они неприменимы.