Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Демидов Основы программирования в примерах на языке ПАСЦАЛ 2010

.pdf
Скачиваний:
128
Добавлен:
16.08.2013
Размер:
1.28 Mб
Скачать

ошибка считается обработанной, после чего можно продолжать операции ввода-вывода.

Основным уязвимым местом в программе при работе с файлами является момент открытия. В частности, ошибка может возникнуть при открытии несуществующего файла. Рассмотрим программу:

var

f: Text; begin

write('Enter file path: '); readln(path);

assign(f, path); {$I-}

reset(f); {$I+}

if IOResult <> 0 then

writeln('Error while opening file '+path); exit;

end;

// штатная работа с файлом

end.

В данной программе при ошибке открытия файла будет выдано сообщение об ошибке и программа завершится.

Задача слияния словарей

В двух текстовых файлах записаны слова – по одному в строке. При этом слова упорядочены по алфавиту. Необходимо получить третий файл, в котором содержатся слова из первого и второго файла без повторений, сохранив алфавитный порядок слов.

Данная задача схожа с задачей слияния массивов. Однако имеются некоторые отличия. Во-первых, количество слов в файлах заранее неизвестно, файлы вообще могут быть пусты. Во-вторых, чтение элементов массивов и их сравнение могут осуществляться в одном выражении, а при работе с файлами требуется сначала считать строки, а лишь затем сравнивать. Поэтому удобнее организовать один сложный цикл, который будет выполняться до тех пор, пока хотя бы один файл не считан до конца:

var

f, g, h: Text; s1, s2: string;

121

fstRead, sndRead: boolean; begin

assign(f, '…'); assign(g, '…'); assign(h, '…');

reset(f);

// первый входной открываем на чтение

reset(g);

// второй входной открываем на чтение

rewrite(h);

// выходной открываем на запись

fstRead := false; // строка из первого файла не считана sndRead := false; // строка из второго файла не считана

// основной цикл слияния двух файлов while not EOF(f) or not EOF(g) do begin

// безопасное считывание

if not fstRead and not EOF(f) then begin readln(f, s1);

fstRead := true; end;

if not sndRead and not EOF(g) then begin readln(g, s2);

sndRead := true; end;

// слияние

if not fstRead then begin

// первый файл кончился, а второй еще нет writeln(h, s2);

sndRead := false; end

else if not sndRead then begin

// второй файл кончился, а первый еще нет writeln(h, s1);

fstRead := false; end

else begin

// оба файла еще не кончились if s1 < s2 then begin

writeln(h, s1); fstRead := false;

end

else if s1>s2 then begin writeln(h, s2); sndRead := false;

end

else begin

// при s1=s2 строка записывается только один раз

122

writeln(h, s1); fstRead := false; sndRead := false;

end; end;

end;

close(f);

close(g);

close(h);

end.

Основная идея этого решения заключается в разделении процессов чтения строк из файлов и слияния. Для этого вводятся две дополнительные булевы переменные – fstRead и sndRead, каждая из которых указывает на то, что из соответствующего файла была считана строка. При слиянии эти переменные играют важнейшую роль.

123

Глава 12. Записи и типизированные файлы

Записи и массивы записей

Представьте, что в программе требуется описать некоторый объект реального мира, например запись в телефонной книжке. Это возможно только с помощью совокупности переменных. Для работы с этой совокупностью, как с единым целым, в языке Паскаль вводится тип-запись, в Си – тип-структура. Для объявления типазаписи следует использовать ключевое слово record и далее описать составляющие запись переменные, которые называются полями записи. Далее можно объявлять переменные нового сложного типа. Например:

type

phone_rec = record fio: string[60]; phone: string[20]; e_mail: string[50];

end;

var

pr1, pr2: phone_rec;

Здесь в типе phone_rec объявлено три поля fio, phone, e_mail строковых типов.

Для обращения к конкретному полю записи необходимо после имени переменной-записи через '.' указать имя поля:

begin

pr1.fio := 'Ivanov Ivan'; pr1.phone := '89169110203'; pr1.e_mail := 'ivanov91@mail.ru';

writeln('fio: ' + pr1.fio); writeln('phone: ' + pr1.phone); writeln('e-mail: ' + pr1.e_mail);

end;

Кроме того, для обращения к полю записи может использоваться оператор with <имя записи> do. Тогда при обращении к полю имя записи можно не указывать:

124

begin

with pr1 do begin writeln('fio: ' + fio); writeln('phone: ' + phone); writeln('e-mail: ' + e_mail);

end;

end;

При этом текст программы немного сокращается, однако программисту следует позаботиться об исключении конфликтов имен переменных.

Для записей определена операция присваивания, при этом копируются все значения полей записи. Однако для записей не определены операции сравнения. Для сравнения записей необходимо попарно сравнить значения одноименных полей.

function Equal(pr1, pr2: phone_rec): boolean;

begin

= pr1.fio) and

Equal := (pr2.fio

(pr2.phone

=

pr1.phone) and

(pr2.e_mail

=

pr1.e_mail);

end.

 

 

Пример: описать телефонную книжку на 200 номеров. Это можно сделать с помощью массива однотипных записей. В следующей программе на экран выводится содержимое телефонной книги, хранящейся в массиве:

type

phone_book = array [1..200] of phone_rec; var

pb: phone_book; pr: phone_rec; i: integer;

begin

for i:=1 to 200 do begin pr := pb[i]; writeln('№', i);

writeln('Name: ' + pr.fio); writeln('Phone: ' + pr.phone); writeln('E-mail: ' + pr.e_mail); writeln;

125

end; end.

Вообще, записи и массивы можно комбинировать для создания сколь угодно сложных структур данных, вложенных друг в друга. Например:

type

passport_rec = record series: string[4]; number: string[6]; when: DateTime; whom: string[100];

end;

person = record fio: string;

birthdate: DateTime; passport: passport_rec;

end; var

p: person; begin

p.fio := 'Smith'; p.passport.series := '4700';

with p.passport do writeln(series, ' ', number); end.

Записи с вариантной частью

В примере с телефонной книгой одна запись содержала три поля. Однако часто в разных случаях уместен разный набор полей. Так, формат библиографической записи различается в зависимости от типа издания:

для книги следует указывать автора, название, год издания, издательство;

для журнала – название, год и месяц издания, номер, издательство;

для газеты – название, дату выхода (день, месяц, год),

номер, издательство.

Конечно, можно определить универсальную структуру данных, содержащую все возможные поля, и заполнять поля по мере надобности. Например:

126

type

biblio = record bibtype: char; author: string[40]; name: string[40]; year: integer; month: byte;

day: byte; number: integer;

publisher: string[40]; end;

Но такая структура неэкономна с точки зрения расхода памяти. На одну запись расходуется 1+40+40+4+1+1+4+40 = 131 байт. При этом общими для всех трех типов изданий являются название, год, издательство, а остальная информация может различаться или вовсе отсутствовать. Для создания более компактных структур данных в языке Паскаль реализована возможность определения записей с вариантной частью.

В таких записях после постоянной части, которая может и отсутствовать, указывается вариантная часть с помощью ключевого слова case. Общий вид записи с вариантной частью:

type

<имя типа> = record <поле>: <тип>;

<поле>: <тип>;

case <тег>: <перечислимый тип> of <список констант>:

(

<поле>: <тип>;

<поле>: <тип>; );

<список констант>:

(

<поле>: <тип>;

<поле>: <тип>; );

end;

Финальное слово end закрывает и запись, и вариантную часть.

127

Каждый список констант соответствует одному варианту, состоящему из одного и более полей. Содержимое списков констант для компилятора не важно, однако списки не должны пересекаться между собой. Для полей вариантов выделяется один и тот же участок памяти, т.е. варианты являются его различными представлениями и как бы наложены друг на друга. Таким образом, общий размер записи равен сумме размера постоянной части и размера самого ёмкого варианта.

Тег в вариантной части, вообще говоря, необязателен, однако его перечислимый тип следует указывать всегда. Если же тег указан, то он считается дополнительным полем постоянной части и может использоваться для анализа того, какой вариант из вариантной части уместен. В вариантной части (так же как и в постоянной) не должно быть одноименных полей. Это ограничение гарантирует однозначное распознавание варианта и последующее вычисление относительной позиции поля в памяти.

С помощью вариантной части запись biblio можно сделать более компактной:

type

biblio = record name: string[40]; year: integer;

publisher: string[40]; case bibtype: char of 'b': (author: string[40]); 'j': (

jmonth: byte; jnumber: integer );

'n': (

nmonth: byte; day: byte; nnumber: integer );

end;

В данном случае размер записи равен (40+4+40+1) + (40) = 125. Схема распределения памяти для структуры с вариантной частью показана на рис. 9.

128

name

year

publisher

bibtype

 

author (40)

 

(40)

(4)

(40)

(1)

jmonth (1)

 

jnumber (4)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nmonth

 

day

number

 

 

 

 

 

(1)

(1)

(4)

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 9. Схема распределения памяти для структуры с вариантной частью

Обращение к полям вариантной записи ничем не отличается от обращения к полям обычной записи. Однако программист должен сам определять, с каким из вариантов вариантной части работать в данный момент. Например, с помощью тега вариантной части:

procedure Print(bib: biblio); begin

with bib do begin writeln(name); writeln(year); writeln(publisher) case bibtype of

'b': writeln(author);

'j': writeln(jmonth, ' ', jnumber);

'n': writeln(nmonth, ' ', day, ' ', nnumber); end;

end; end;

Поскольку все варианты накладываются на один и тот же участок в памяти, то можно, например, записать в этот участок фамилию автора книги author, а затем обратиться к номеру журнала jnumber. При этом байты со 2 по 5, занимаемые author, будут расценены как число типа integer.

Типизированные файлы

Понятно, что для долгосрочного хранения совокупности записей, например телефонной книги, оперативная память непригодна, так как при завершении работы программы все данные будут потеряны. Для долгосрочного пользования телефонной книгой следует использовать механизм работы с файлами. Телефонная книга представляет собой совокупность записей типа phone_rec, т.е. последовательность однотипных компонентов, поэтому разумно использовать типизированные файлы.

129

Для работы с типизированным файлом следует объявить файловую переменную, указав тип компонентов файла с помощью ключевых слов file of:

var

pb: file of phone_rec;

Или же ввести новый файловый тип phone_book, а затем объявить файловую переменную:

type

phone_book = file of phone_rec; var

pb: phone_book;

В отличие от массива, на число компонентов файла не накладывается ограничений. Компоненты типизированного файла всегда нумеруются с 0.

Для открытия типизированных файлов используют знакомые процедуры rewrite и reset. Процедура rewrite открывает типизированный файл в режиме перезаписи, а процедура reset – в режиме чтения и записи. Следует напомнить, что текстовые файлы открываются процедурой reset только на чтение.

Для выполнения операций чтения и записи используются процедуры Read и Write. Однако, в отличие от текстовых файлов, процедуры работают с целыми компонентами файла: Read считывает компонент целиком, а Write записывает компонент целиком. В следующей программе на экран выводится содержимое телефонной книги, хранящейся в файле 'c:\dev\phones.bin' (предполагается, что файл уже существует):

var

pb: phone_book; pr: phone_rec; i: integer;

begin

Assign(pb, 'c:\dev\phones.bin'); Reset(pb);

while not EOF(pb) do begin

Read(pb, pr); // чтение одного компонента writeln('Name: ' + pr.fio); writeln('Phone: ' + pr.phone); writeln('E-mail: ' + pr.e_mail);

130

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]