Глава 29 Читайте по-новому



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

Полицейская база данных, версия 1

Одна из первых наших программ исполняла должность электронного часового, охранявшего секретный объект. Теперь поможем другой силовой структуре – дорожной полиции.

Долг автоинспектора – в числе прочего – поиск угнанных автомобилей. Работа нехитрая: заприметив подозрительный автомобиль, инспектор сверяет его номер со своей картотекой. И, если номер в картотеке найдется, принимает меры к задержанию автомобиля и поимке преступника. В отличие от часового, который помнит всего один пароль, полицейский роется в пухлой картотеке с тысячами номеров. В этом и состояла его главная трудность, пока не явились компьютерные базы данных.

Базы данных (сокращенно БД) – кто не слышал о них? На ум приходят базы данных Пентагона, ЦРУ и налоговой инспекции. Да, эти чудовищные БД впечатляют! Впрочем, чтобы увидеть базу данных, не спешите потрошить Пентагон. Вот расписание поездов, программа телепередач или классный журнал, – все это простые базы данных. В конце концов, любая БД – это организованное хранилище данных, приспособленное для удобного поиска информации. Нам тоже по силам создать несложную полицейскую базу данных.

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


123

325

234

11


Такой файл можно напечатать любым редактором текста, в том числе и встроенным в IDE. Мы так и поступим: создайте новый файл, впечатайте в него десяток-другой пришедших на ум номеров и сохраните в рабочей папке под именем «Police.txt». Окно с этим файлом пока не закрывайте, оно ещё пригодится.

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

Итак, приступим к программе «P_29_1». Схема главной программы ясна из вышесказанного. Выносить заключение об автомобиле будет функция булевого типа, принимающая два параметра: файловую переменную, связанную с нашей БД, и номер искомого автомобиля. Если номер в базе данных обнаружится, функция вернет значение TRUE. Ввиду простоты алгоритма не буду рисовать блок-схему. Если чуете в себе силу, напишите программу сами, и, после некоторых мучений, сравните с тем, что показано ниже.


{ P_29_1 – Полицейская база данных, версия 1 }


function FindNumber(var aFile: text; aNumber: integer): boolean;

var N: integer; { текущий номер в БД }

begin

    FindNumber:= false; { на случай, если файл пуст }

    Reset(aFile); { позицию чтения устанавливаем в начало файла }

    N:=0;     { в начале цикла задаем несуществующий номер }

    { читаем номера из файла, пока НЕ конец файла И номер НЕ найден }

    while not Eof(aFile) and (N<>aNumber) do begin

    Readln(aFile, N);

    FindNumber:= (N=aNumber); { true, если номер нашелся }

    end;

end;

var F: text; Num: integer;

begin {----- Главная программа -----}

    Assign(F, 'Police.txt');

    repeat

    Write('Укажите номер автомобиля: '); Readln(Num);

    if FindNumber(F, Num)

    then Writeln('Эта машина в розыске, хватайте его!')

    else Writeln('Пропустите его');

    until Num=0; { 0 – признак завершения программы}

    Close(F);

end.


Поясню некоторые моменты. В начале главной программы файловая переменная F связывается с файлом «Police.txt». Далее следует хорошо знакомая конструкция REPEAT–UNTIL с проверкой условия в конце цикла.

Самое интересное скрыто внутри функции FindNumber (от Find – «искать», Number – «номер»). Туда передаются два параметра, один из которых – файловая переменная. Обратите внимание на способ её передачи: файловая переменная передается по ссылке (в заголовке указано слово VAR). И никак иначе такую переменную не передают! Со временем узнаете причину, а пока просто запомните: файловые переменные передают внутрь процедур и функций только по ссылке! Следовательно, параметр aFile ссылается на глобальную переменную F.

А к чему здесь приставки «a» перед именами параметров: aFile, aNumber? Или это тоже правило языка? Нет, друзья, это всего лишь уловка программистов, которую полезно перенять. Чем сложнее будут ваши программы, тем гуще будут заселены разного рода переменными и параметрами. Во избежание путаницы лучше учредить разумную систему обозначений. Большинство программистов используют для систематизации имен так называемые префиксы или приставки. Например, для параметров (аргументов) процедур и функций применяют префикс «a» (от слова «argument»). Помеченные таким образом параметры уже не спутаешь с локальными или глобальными переменными.

Теперь заглянем внутрь функции FindNumber. В первой строке результату функции присваивается значение FALSE. И это оправдано, поскольку значение функции обязательно должно быть определено, а в случае, если файл БД окажется пустым, этого не случится, поскольку следующий далее цикл WHILE не будет выполняться.

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

Теперь взгляните на условие цикла WHILE, – оно чуть сложнее тех, к которым мы привыкли.


    while not Eof(aFile) and (N<>aNumber)


Наряду с признаком конца файла проверяется и условие несовпадения искомого номера с номером, прочитанным из файла. Значит, цикл будет продолжаться, пока НЕ достигнут конец файла И НЕ найден искомый номер.

Внутри цикла находим непривычный оператор присваивания.


    FindNumber:= (N=aNumber); { true, если номер нашелся }


Левая его часть – это идентификатор функции, а правая (в скобках) – оператор сравнения двух чисел. Оператор сравнения дает булев результат, равный TRUE, если числа совпадают. Скобки в правой части здесь не нужны, но я поставил их для наглядности. Приведенный выше оператор можно было бы заменить таким.


    if N=aNumber

    then FindNumber:= true

    else FindNumber:= false;


Но, согласитесь, первый вариант наглядней и короче.

Итак, прежде чем двинуться дальше, не поленитесь проверить эту программу.

Полицейская база данных, версия 2

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


123 234 325

223 240

845 431 205


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

Переключитесь в окно нашей базы данных «Police.txt» и внесите необходимые изменения. Сохранить файл не забыли? Теперь запустите программу и проверьте её на номерах из этого файла. При должном внимании вы обнаружите, что программа правильно находит только числа, начинающие строку, например 123, 223 и 845. Все последующие номера в строке программа не замечает, хотя и аварийных сообщений не выдает. В чем же дело?

Причина – в процедуре Readln. До сих пор мы пользовались ею для чтения строк и горя не знали. Но числа – иное дело. Приглашаю вас мысленно проследить за позицией чтения в ходе просмотра нашей БД (рис. 66). Невидимые признаки конца строки обозначены на рисунке условными символами Eoln (это пара символов с кодами 13 и 10).



Рис.66 – Продвижение позиции чтения процедурой Readln

После того, как Reset установит позицию чтения в начало файла, процедура Readln прочитает первое число. Чтение идет цифра за цифрой, пока не встретится любой символ, отличный от неё, например, пробел или конец строки. Проглотив таким образом первое число, процедура Readln продвинет позицию чтения в начало следующей строки, пропуская при этом все, что расположено до конца текущей. Вот в чем дело! Не зря к названию процедуры прилепился суффикс «LN» (сокращенное от Line – «строка»). Источник проблемы ясен: процедура Readln не подходит для чтения нескольких чисел в строке. Где же выход?

Спокойно, друзья, в Паскале заготовлено все! Познакомьтесь с процедурой Read (без суффикса), которая почти не отличается от своей «сестренки» – принимает те же параметры и читает те же данные. Но при этом самовольно не продвигает позицию чтения в начало следующей строки. А нам того и нужно! Продвижение позиции чтения процедурой Read показано на рис. 67.



Рис.67 – Продвижение позиции чтения процедурой Read

После чтения каждого числа позиция продвигается за это число и остается там. Следующий вызов процедуры прочитает очередное число в этой же строке и так далее, пока не будет достигнут конец строки. А потом? Потом позиция сдвинется в начало следующей строки, и чтение продолжится тем же чередом до конца файла.

Итак, решение найдено, и теперь для правильной работы программы надо лишь удалить суффикс в имени процедуры, то есть заменить вызов


    Readln(aFile, N);


на вызов


    Read(aFile, N);


Внесите это исправление в программу, сохраните её под именем «P_29_2» и проверьте, работает ли она.

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

Итоги

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

• Перед поиском данных в текстовом файле, надо установить позицию чтения в начало файла процедурой Reset.

• Процедура Readln после чтения затребованных данных продвигает позицию чтения в начало следующей строки. При этом все непрочитанные данные в текущей строке пропускаются.

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

А слабо?

А) Напишите программу для преобразования второго варианта базы данных «Police.txt» (с несколькими числами в строке) в первый вариант (по одному числу в строке). Или слабо?

Б) Можно ли в решении предыдущей задачи назначить одно и то же имя как входному, так и выходному файлам? Испытайте на практике.

Загрузка...