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

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

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

end; Close(pb);

end.

Другие процедуры и функции для работы с типизированными файлами:

procedure Seek(var F; N: Longint) – позиционирует курсор на компоненте с номером N. Процедура служит для организации прямого доступа к компонентам файла.

function FilePos(var F): Longint – возвращает номер компонен-

та, перед которым в данный момент стоит курсор. Если курсор указывает на конец файла, содержащего N компонентов, то возвращается число N, так как последний компонент имеет номер N–1.

function FileSize(var F): Integer – возвращает число компонен-

тов файла.

procedure Truncate(F) – укорачивает файл с конца до текущего положения курсора, так что вызов EOF(F) после Truncate вернет истину.

Функции EOLN, SeekEOF, SeekEOLN не применимы к типизированным файлам.

Организация простой базы данных на одном типизированном файле

Для организации простейшей базы данных телефонных записей требуется реализация вспомогательных сервисов: функции поиска записи (select) по фамилии, добавления новой записи (insert), изменение записи (update) и удаление записи (delete).

Поиск записи в телефонной книге по фамилии

Функцию поиска можно реализовать аналогично поиску в массиве. Найдя запись с нужной фамилией, функция возвращает номер записи в файле и саму запись. Номер записи может понадобиться в дальнейшей работе, например для её обновления или удаления из файла:

function Select(

f: phone_book; fio: string; var N: longint;

var rec: phone_rec): boolean;

var

131

found: Boolean; r: phone_rec;

begin

found := false; reset(f);

while not eof(f) and not found do begin read(f, r);

if r.fio = fio then begin

N := FilePos(f); rec := r;

found := true; end;

end;

Select := found; end;

Добавление записи в телефонную книгу

Для простоты новые записи будут добавляться в конец файла:

procedure Insert(f: phone_book; p: phone_rec); begin

reset(f);

seek(f, FileSize(f)); // ставим курсор на конец файла write(f,p);

close(f); end;

Обновление записи в телефонной книге под номером N

Обновление записи тривиально: курсор позиционируется на нужный компонент и осуществляется запись поверх него:

procedure Update( f: phone_book; N: integer;

rec: phone_rec);

begin reset(f); seek(f, N);

write(f, rec); // перезапись компоненты под номером N close(f);

end;

Удаление из телефонной книги записи под номером N

К сожалению, в стандартной библиотеке отсутствуют процедуры удаления записей из произвольного места файла. Эту процедуру можно было бы реализовать следующим способом:

132

1)создать новый типизированный файл g, задав ему некоторое имя;

2)скопировать в него записи файла f, начиная с 0 до N–1;

3)скопировать в него записи файла f, начиная с N+1 до

FileSize(f);

4)удалить прежний файл f;

5)переименовать новый файл g, дав ему имя прежнего файла;

6)связать f с g.

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

1)считать последнюю запись;

2)записать её на место N (она гарантированно имеет тот же размер);

3)поместить курсор на последнюю запись;

4)укоротить файл.

procedure Delete(f: phone_book; N: integer); var

rec: phone_rec; begin

reset(f);

seek(f, FileSize(f)-1);

read(f, rec);

// читаем последнюю запись

seek(f, N);

// записываем на позицию N

write(f, rec);

seek(f, FileSize(f)-1);

truncate(f);

// укорачивание файла

close(f);

 

end;

В основной программе необходимо связать файловую переменную с нужным файлом и предоставить средства для работы пользователя с одной записью. Работу с файлом возьмут на себя сервисные функции.

133

Глава 13. Нетипизированные файлы

Для работы с бинарными файлами общего вида в Паскале имеется встроенный тип данных – нетипизированный файл. Файловые переменные для работы с такими файлами описываются следующим образом:

var f: file;

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

В операциях ввода-вывода также используется буферизация, причем размер буфера ввода-вывода равен по умолчанию 128 байтам. Иной размер буфера можно задать при открытии файла с помощью второго параметра процедур reset и rewrite:

reset(f[,size]);

rewrite(f[,size]);

Размер буфера size должен находиться в пределах от 1 байта до

64 Кб.

Чтение и запись нетипизированных файлов

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

procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred: Integer]) – считывает в переменную Buf количество блоков Count 1. В необязательном параметре AmtTransferred возвращается количество реально прочитанных блоков.

procedure BlockWrite(var f: File; var Buf; Count: Integer [; var AmtTransferred: Integer]) – выполняет запись в файл Сount блоков из области памяти Buf. В параметре AmtTransferred возвращается число успешно записанных блоков.

134

В процедурах BlockRead и BlockWrite параметр Buf должен являться переменной, однако тип её не указан. Это означает, что в качестве параметра можно передать переменную любого типа.

Процедуры и функции Seek, FilePos, FileSize за размер одной компоненты берут размер блока (буфера), указанный при открытии нетипизированного файла.

Задание 1. Написать программу чтения вещественных чисел из текстового файла и записи их в нетипизированный файл блоками по N чисел. Для определения размера в байтах вещественного числа использовать функцию SizeOf:

function SizeOf(X): Integer – возвращает размер в байтах области памяти, занимаемый значением переменной X произвольного типа.

Решение может выглядеть так:

const N = 8; var

buf: array[1..N] of real;

f:text;

g:file;

i, k: integer; begin

assign(f, '…');

reset(f); // входной файл assign(g, '…');

rewrite(g, sizeof(real) * 4); // выходной файл

i := 0;

while not eof(f) do begin i := i+1;

read(f, buf[i]); if i = N then begin

blockwrite(g, buf, 1); i := 0;

end;

end;

// дополним нулями последнюю N-ку, если нужно if i > 0 then begin

for k := i + 1 to N do buf[k] := 0; blockwrite(g, buf, 1);

end;

close(f);

close(g);

135

end.

Задание 2. Написать программу шифрования файла путем наложения ключа с помощью операции xor. Программа должна запрашивать имена файлов с экрана.

Пусть имеется файл размером N байт и ключ – строка длиной М. Задача шифрования состоит в разбиении файла на последовательные участки длиной M и наложения ключа на каждый участок. Наложение ключа осуществляется с помощью битовой операции исключающего или для каждой пары соответствующих символов из очередного участка файла и ключа. Расшифровка файла осуществляется с помощью повторного наложения того же ключа:

const

Size = 8; var

f, g: file; Path: string;

Key: string[Size];

buf: array [1..Size] of char; Transferred: longint;

begin

write('Введите ключ длины ', Size, ': '); readln(Key);

write('Введите имя входного файла: '); readln(path);

Assign(f, Path); {$I-}

reset(f, 1); {$I+}

if IOResult <>0 then begin writeln('Файл ', path,' не найден'); exit;

end;

write('Введите имя выходного файла: '); readln(path);

Assign(g, path);

Rewrite(g, 1);

// шифрование

while not EOF(f) do begin

BlockRead(f, buf, Size, Transferred);

136

// наложение ключа на очередной участок for k := 1 to Transferred do

buf[k] := buf[k] xor key[k]; BlockWrite(g, buf, Transferred);

end;

Close(f);

Close(g); end.

Если длина файла кратна длине ключа, то переменная Transferred всегда будет равна Size, иначе на последнем витке цикла переменная Transferred будет меньше Size.

Задание 3. Написать программу расчета энтропии файла. Энтропия рассчитывается по формуле

H pi

log(pi ),

 

i

 

где pi

Ni

– доля вхождений (вероятность появления) симво-

Ni

i

ла i в файле, а Ni – число вхождений символа i в файле.

Поскольку в компьютерах используется двоичная система счисления и общее количество ASCII-символов равно 256, то основание логарифма имеет смысл выбрать равным двум, а пределы суммирования – от 0 до 255. Основание логарифма одновременно даёт название единицам измерения энтропии (2 – биты, 10 – диты, e – наты).

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

1)сканирование файла и подсчет числа вхождений символов. Чтобы узнать общий размер файла в байтах, необходимо будет открыть его на чтение, указав размер буфера равным 1 байту;

2)расчет энтропии на основе полученного массива по формуле. В стандартной библиотеке языка Паскаль имеется функция ln(x), возвращающая натуральный логарифм. Для получения двоичного логарифма необходимо

воспользоваться соотношением loga(b) = ln(b)/ln(a). Вариант реализации программы выглядит следующим образом:

137

const

size = 1024; type

// статистика вхождений символов symbols = array[0..255] of longint;

var

f: file; Path: string;

total: longint; stats: symbols; b: byte;

buf: array [1..size] of char; Transferred: longint;

k: integer; H: real;

begin

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

Assign(F, Path); {$I-}

Reset(F, 1); // буфер в 1 байт, чтобы узнать размер файла в байтах

{$I+}

if IOResult <>0 then begin

Writeln('File not found'); exit;

end;

Total := FileSize(F);

for b:=0 to 255 do stats[b] := 0;

// чтение файла и подсчет вхождений символов while not EOF(F) do begin

BlockRead(F, buf, size, Transferred); for k := 1 to Transferred do

stats[Ord(buf[k])] := stats[Ord(buf[k])] + 1;

end; Close(F);

// расчет энтропии

H := 0;

for b := 0 to 255 do begin if stats[b] > 0 then

H := H - (stats[b] * ln(stats[b]/Total));

end;

// разделим на общий знаменатель слагаемых

138

H := H / Total / ln(2);

// вывод результата и статистики вхождений

Writeln('H = ', H: 10: 10, ' bit'); for b := 0 to 255 do

writeln('N(', b:3, ')= ', stats[b]);

end.

Условие stats[b] > 0 необходимо для того, чтобы исключить выполнение функции ln(0) = - , которое приведёт к переполнению вещественного типа.

Для сжатых файлов, к которым относятся архивные файлы

(*.zip, *.rar), мультимедиа файлы (*.mp3, *.avi, *.jpg), энтропия близка к 8. Это не случайно. Число 8 – максимальная энтропия для 256 символов, кодируемых восемью битами. Эта величина достижима только в том случае, если символы, как бы хаотично, встречаются с одинаковой частотой:

 

 

1

255

1

 

 

1

 

 

1

 

pi

 

H

log2

(

) log

2 (

) 8.

 

256

8

8

 

256

i 0

 

2

 

 

2

 

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

139

Глава 14. Динамические переменные, линейные списки

Динамические переменные и их особенности

При запуске программы для нее выделяется определенная область в оперативной памяти. Выделенный ресурс памяти делится на несколько областей (сегментов):

сегмент кода;

сегмент данных и стека;

куча (динамическая память).

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

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

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

Понятно, что при такой схеме работы с памятью статические переменные для работы с динамическими данными применять

140

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