Глава 31 Финал журнальной истории



В предыдущей главе мы поклялись восстановить съеденную мышами программу и отчасти сдержали клятву. Нами решена упрощенная задача – обработка журнала без фамилий учеников, то есть, мы исполнили вычислительную часть проекта. Теперь завершим его, добившись обработки настоящего классного журнала. Требуется, казалось бы, пустяк – прочесть фамилии учеников. Но воспользоваться процедурой Readln, как мы поступили в программе шифрования текста, здесь не получится, – она прочитает всю строку целиком, включая и оценки (которые станут как бы частью фамилии!).

Буква за буквой

Славный литературный герой Остап Бендер по поводу желанного миллиона сказал так: «Я бы взял частями, но мне нужно сразу!». Увы! При чтении фамилий надо проявить терпение. Если не получается сразу, возьмем по частям. Ведь строка фамилии состоит из отдельных букв, – так прочитаем фамилию по буквам! Прочитать букву может все та же процедура Read, например:


var sym : char;

...

    Read(InFile, sym);     { чтение одного символа }


А фамилию S склеим из отдельных букв:


    S:= S + sym;


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



Рис.71 – Упрощенный алгоритм побуквенного чтения фамилии
Нелишняя предосторожность

Людям свойственно ошибаться, – даже учителям! В строках журнала (а это текстовый файл) могут оказаться лишние пробелы – как между оценками, так и в начале строки, перед фамилией. И что тогда? – проверьте на практике. При чтении чисел процедура Read «не заметит» лишних пробелов, – она достаточно «умна». Другое дело – показанная выше блок-схема: если перед фамилией обнаружится пробел, то чтение слова завершится досрочно. Стало быть, для правильного чтения фамилии надо пропустить стоящие перед нею пробелы (если они есть). Это улучшение слегка усложнит блок-схему (рис. 72).



Рис.72 – Усовершенствованный алгоритм побуквенного чтения фамилии
Достройка программы

В основу новой версии программы «P_31_1» положим программу «P_30_1». Вам следует, прежде всего, открыть её и сохранить под новым именем. Готово? Тогда приступаем к правке.

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


    Assign(InFile,'Journal2.in');     Reset(InFile);

    Assign(OutFile,'Journal2.out'); Rewrite(OutFile);


Позаботьтесь о том, чтобы файл «Journal2.in» был похож на настоящий классный журнал с фамилиями, как о нём сказано в начале 30-й главы.

Второе изменение внесем в процедуру обработки строки HandleString. Здесь объявим ещё одну переменную строкового типа, назовем её Fam, она будет вмещать фамилию ученика.


    Fam:= ReadFam; { читаем фамилию }


Разумеется, оператор печати строки тоже будет изменен.


    Writeln(OutFile, Counter:3, Fam:18, Cnt:8, Sum:14, Rating:11:1);


Осталось выяснить, что такое ReadFam? Это функция чтения фамилии, которую мы напишем по рассмотренному чуть выше алгоритму (рис. 72). Мой вариант функции таков.


function ReadFam: string;

var sym: char; { очередной символ }

S : string; { накопитель строки }

begin

S:=''; { очистка накопителя строки }

{ чтение символов до первой буквы }

repeat Read(InFile, sym); until Ord(sym)>32;

{ чтение последующих символов }

repeat

s:= s+sym;

if Eoln(InFile) then Break;

Read(InFile, sym);

until not ((Ord(sym)>32));

ReadFam:= S; { возвращаемый результат }

end;


Обратите внимание на сравнение введенного символа с пробелом. Это сравнение можно было бы записать так:


    sym <> ’ ’


Но пробел в кавычках трудно разглядеть. Лучше сравнивать код символа с кодом пробела (который равен 32), что и сделано внутри функции.

Испытание

Теперь все готово, запустите программу. Что оказалось в выходном файле «Journal2.out»? Наверное, вот это.


Номер Фамилия     Количество     Сумма     Средний

ученика     оценок     баллов     балл

1     Акулова     3     12     4.0

2     Быков     4     20     5.0

3     Волков     4     18     4.5

4     Галкина     3     10     3.3

5     Крокодилкин     2     7     3.5


Если не считать кривых колонок, неплохо. Кривизну даёт разная длина фамилий учеников. Можно выровнять колонки, вычисляя спецификатор ширины в зависимости от длины фамилии. Или поступить иначе, – дополнить фамилии до одинаковой длины пробелами справа, например:


    while Length(Fam) < 12 do Fam:= Fam + Char(32);


Этот оператор уместен после чтения фамилии. Окончательный вариант программы со всеми дополнениями и уточнениями представлен ниже.


    { P_31_1 – Обработка классного журнала, второй этап }


var InFile, OutFile : text; { входной и выходной файлы }

  Counter: integer;     { счетчик строк в файле }


{----- Функция чтения фамилии -----}


function ReadFam: string;

var sym: char;

S : string;

begin

s:=''; { очистка накопителя строки }

{ чтение символа до первой буквы }

repeat Read(InFile, sym); until Ord(sym)>32;

{ чтение последующих символов }

repeat

s:= s+sym;

if Eoln(InFile) then Break;

Read(InFile, sym);

until not ((Ord(sym)>32));

ReadFam:= S;

end;

    {----- Процедура обработки строки -----}


procedure HandleString;

var N : integer; { оценка, прочитанная из файла }

    Cnt: integer; { количество оценок }

    Sum: integer; { сумма баллов }

    Rating: Real; { средний балл }

    Fam: string; { фамилия }

begin

    Fam:= ReadFam; { читаем фамилию }

    { для выравнивания столбцов добавляем пробелы }

    while Length(Fam) < 12 do Fam:= Fam + ' ';

    Sum:=0; Cnt:=0; { очищаем накопитель и счетчик оценок }

    While not Eoln(InFile) do begin { пока не конец строки }

    Read(InFile, N); { читаем оценку в переменную N }

    Sum:= Sum+N;     { накапливаем сумму баллов }

    Cnt:= Cnt+1;     { наращиваем счетчик оценок }

    end;

    if Cnt>0

    then begin     { если оценки в четверти были }

    Rating:= Sum / Cnt; { вычисляем и печатаем ср. балл }

    Writeln(OutFile, Counter:3, Fam:18, Cnt:8,

    Sum:14, Rating:11:1);

    end

    else     { а если оценок не было }

    Writeln(OutFile, Counter:3, Fam:18,' : Ученик не аттестован');

end;


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

    Counter:= 0;     { обнуляем счетчик строк }

    { открываем входной файл }

    Assign(InFile,'Journal2.in'); Reset(InFile);

    { создаем выходной файл }

    Assign(OutFile,'Journal2.out'); Rewrite(OutFile);

    { выводим шапку таблицы }

    Writeln(OutFile, 'Номер Фамилия Количество Сумма Средний');

    Writeln(OutFile, '     оценок     баллов балл');

    { пока не конец входного файла… }

    while not Eof(InFile) do begin

    Counter:= Counter+1; { наращиваем счетчик строк }

    HandleString;     { обрабатываем строку }

    { переход на следующую строку }

    if not Eof(InFile) then Readln(InFile);

    end;

    { закрываем оба файла }

    Close(InFile); Close(OutFile);

end.


Итоги

• Для чтения отдельного слова в строке файла не годятся ни оператор Readln (он прочитает всю строку), ни оператор Read, который не видит конца строки. Слово читается посимвольно оператором Read с отслеживанием признака окончания строки и других условий.

• Строку выходного файла можно формировать порциями, применяя несколько вызовов процедуры Write. Каждый такой вызов формирует часть строки и продвигает позицию записи, оставляя её в текущей строке. Для перехода к следующей строке вызывается процедура Writeln.

А слабо?

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

Б) Файл с физическими данными старшеклассников содержит три колонки: фамилия, рост и вес ученика. Создайте программы для решения следующих задач:

• отбор кандидатов для занятий баскетболом, – рост кандидата должен составлять не менее 175 см;

• поиск учеников с избыточным весом, для которых разница между ростом ученика (см) и его весом (кг) составляет менее 100.

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

Загрузка...