1. Чтение данных из текстового файла.
2. Запись данных в текстовый файл.
3. Преобразование из текста в соответствующий тип в структуре типа запись (для хранения в процессе работы программы).
4. Преобразование из соответствующего типа в текст (для вывода на экран или в текстовый файл).
5. Обработка информации, получаемой от клавиатуры, так как в задании предусмотрено изменение содержимого таблицы.
На этапе предварительного анализа необходимо учесть, что в процессе дальнейшей разработки или эксплуатации могут потребоваться те или иные изменения в программе. Поэтому, с целью унификации программы или ее отдельных модулей необходимо учесть возможные варианты модификации. Некоторые из возможных вариантов изменения программы - хранение таблицы в файле другого типа, другая форма таблицы, другие типы полей таблицы.
4.3. Проектирование программы
Так как язык Паскаль структурирован, то не будем на этапе проектирования использовать какой-либо промежуточный язык проектирования, тем более что даже многие операторы языков проектирования совпадают с операторами языка Паскаль. Больше внимание в этой части уделим не форме, а сути задачи.
Последовательность проектирования большинства программ практически полностью совпадает с этапами выполнения самих программ. Более того, для всех программ можно определить три основных этапа работы: начальные подготовительные операции (инициализация), основной цикл программы (следует отметить, что это чаще всего действительно цикл), завершающий этап работы программы.
Исходя из этих соображений, общую структуру программы представим в виде:
Листинг 1
procedure Init; { Инициализация }
begin
end;
procedure Run; { Основной цикл }
begin
end;
procedure Done; { Завершение работы }
begin
end;
begin { Начало основной программы }
Init; {Инициализация }
Run; { Основной цикл }
Done; { Завершение работы }
end; { Конец основной программы }
Далее обычно последовательно выполняются перечисленные этапы, в которых, в свою очередь, также можно выделить отдельные, относительно независимые части.
На этапе инициализации необходимо выполнить:
1) считывание и распечатку на экране формы таблицы;
2) установку начальных значений для глобальных переменных;
3) считывание данных из таблицы и размещение их в одномерном массиве записей.
Первое действие при работе по первому пункту состоит только лишь в чтении из текстового файла и распечатки его содержимого на экране:
Листинг 2
uses Crt;
procedure Init; { Инициализация }
var F:Text; S:string;
begin
ClrScr; { Очистить экран }
{ Открыть файл для чтения и проверить правильность открытия }
Assign(F,'Tab_Form.txt');
{$I-} { Убрать автоматический контроль за открытием файла }
Reset(F);
{$I+} { Установить автоматический контроль за открытием файла }
if IOResult<>0 then
begin
writeln('Не найден файл Tab_Form.txt.'); writeln('Программа завершает работу.');
writeln('Для продолжения нажмите любую клавишу.'); ReadKey; Halt(1); end;
{ Распечатать форму таблицы на экране }
repeat
ReadLn(F,S); WriteLn(S); until EOF(F);
end;
procedure Run; { Основной цикл }
begin
repeat
until KeyPressed;
end;
procedure Done; { Завершение работы }
begin
ClrScr; end;
begin { Начало основной программы }
Init; { Инициализация }
Run; { Основной цикл }
Done; { Завершение работы }
end. { Конец основной программы }
Учитывая, что будем открывать не только файл формы таблицы, но и файл таблицы, а также то, что необходимо будет не только чтение, но и запись в файл таблицы, изменим процедуру инициализации. При этом однотипные действия объединим в одну процедуру. Одной из таких процедур оказывается процедура открытия текстового файла OpenTextFile. Вводим также тип, который определяет направление пересылки данных: чтение - из файла в программу, запись - из программы в файл. При этом изменится процедура Init и будет завершен первый пункт этапа инициализации (считывание и распечатка на экране формы таблицы). Теперь, чтобы выделить изменения, листинг не будет приводиться целиком, а будет содержать измененные части (пропуски текста программы будут обозначены многоточием).
Листинг 3
type Directions=(ReadFile,WriteFile); { Направление чтение/запись }
...
{ Процедура открытия текстового файла }
procedure OpenTextFile( FileName:String; { Имя файла }
var F:Text; { Файловая переменная }
Direction:Directions); { Направление }
var OK:boolean; { Открытие файла прошло успешно }
begin { OpenTextFile }
Assign(F,FileName); OK:=True;
case Direction of
ReadFile: { Открытие файла для чтения }
begin
{$I-} Reset(F); {$I+}
if IOResult<>0 then
begin
ClrScr; writeln('Не найден файл ',FileName,'.'); OK:=False;
end;
end;
WriteFile: { Открытие файла для записи }
begin
{$I-} ReWrite(F); {$I+}
if IOResult<>0 then
begin
ClrScr; writeln('Ошибка открытия ',FileName,'.'); OK:=False; end;
end;
end; { Case }
if not OK then
begin
writeln('Программа завершает работу.');
writeln('Для продолжения нажмите любую клавишу.'); ReadKey; Halt(1); end;
end; { OpenTextFile }
...
{ Процедура инициализации }
procedure Init;
{ Процедура чтения формы таблицы из файла }
procedure ReadTableForm;
var F:Text; S:string;
begin
OpenTextFile('Tab_Form.txt',F,ReadFile);
repeat
readln(F,S); writeln(S); { Чтение и распечатка очередной строки }
until EOF(F);
Close(F);
end;
begin { Init }
ClrScr; { Очистить экран }
ReadTableForm; { Чтение формы таблицы из файла }
end; { Init } …
Заметьте, что в приведенном листинге процедура чтения формы таблицы из файла (ReadTableForm) локальная (она не должна вызываться где-либо кроме процедуры Init). Хотя многие из уже перечисленных процедур (как и ReadTableForm ) вызываются всего в одном месте и всего один раз, использование их оправдано, так как в этом случае программа становится более наглядной и понятной (выполняется условие читабельности программы).
Здесь же начинаем выполнять другое полезное условие - условие применения параметров в процедуре Init. Теперь процедура Init не будет зависеть от глобальных переменных, и ее легко, при необходимости, можно будет выделить в отдельный модуль.
В процедуре ReadtableForm можно определить начальные значения глобальных переменных, перечисленных ниже, по виду линий в форме таблицы.
Обратите также внимание, что имя той или иной переменной говорит о ее назначении (данное замечание относится и ко всем остальным идентификаторам). И даже при этом программа должна обязательно содержать комментарии:
Nrow - Количество строк в таблице.
Ncol - Количество столбцов в таблице.
InitRow - Начальная строка данных в таблице.
ColPos - Массив положений столбцов на экране.
Листинг 4
{ Глобальные константы, определяющие максимальный размер таблицы }
const
MaxRows=20;{ Максимальное количество строк }
MaxCols=10; { Максимальное количество столбцов }
type
{ Тип для массива местоположений столбцов таблицы на экране }
ColumnsPositionsArray=array[1..MaxCols] of byte;
{ Перечисляемый тип для описания фирмы-изготовителя }
Corporations=(Intel,AMD,Cyrix);
{ Массив имен фирм-изготовителей }
const CorpName:array[Intel..Cyrix] of string=('Intel','AMD','Cyrix');
type
Processor=record { Тип записи в строке таблицы }
N:byte; { Номер по порядку }
Corp:Corporations; { Наименование фирмы }
Freq:word; { Частоты }
Notice:string; { Замечания }
Count:word; { Количество }
Price:real; { Цена }
Summ:real; { Сумма }
end;
{ Тип массива записей в таблице }
ProcessorArray=array[1..MaxRows] of Processor;
...
{ Процедура подготовки программы к работе (инициализации) }
procedure Init( { П а р а м е т р ы п р о ц е д у р ы }
var X, { Номер столбца в таблице }
Y, { Номер строки в таблице }
Nx, { Количество строк в таблице }
Ny, { Количество столбцов в таблице }
InitY:byte; { Номер начальной строки в таблице на экране}
var Xpos:ColumnsPositionsArray; { Массив координат столбцов}
var P:ProcessorArray); { Массив записей в таблице }
{ Процедура чтения формы таблицы из файла }
procedure ReadTableForm;
var F:Text; S:string; TableEnd:boolean; X,Y:byte;
{ Процедура определения координаты начала столбца }
procedure ColumnsPositions;
var Xp:byte;
begin { ColumnsPositions }
InitY:=Y; Nx:=1; Xpos[Nx]:=2;
for Xp:=2 to length(s) do
if s[Xp]=chr(197) { Признак конца очередного столбца +}
then begin inc(Nx); Xpos[Nx]:=Xp+1; end;
Xpos[Nx+1]:=Xp+1; { Начало следующего столбца }
end; { ColumnsPositions }
begin { ReadTableForm }
Ny:=0; Nx:=0; Y :=0; X :=0;
InitY:=0; TableEnd:=False;
OpenTextFile('Tab_Form.txt',F,ReadFile);
repeat
readln(F,S); writeln(S); { Чтение и распечатка очередной строки }
if s[1]=chr(192) then TableEnd:=True; { Признак конца таблицы }
if (InitY<>0)and(not TableEnd) then inc(Ny); { Счет строк таблицы }
if s[1]=chr(195) then ColumnsPositions; {Определение координат столбцов}
until EOF(F);
Close(F);
end; { ReadTableForm }
...
{ Глобальные переменные }
var
Nrow, { Количество строк в таблице }
Ncol, { Количество столбцов в таблице }
InitRow {Координата начальной строки данных таблицы на экране}
:byte;
ColPos: { Положение столбца на экране }
ColumnsPositionsArray;
begin { Основная программа }
{ Инициализация }
Init(Ncol,Nrow,InitRow,ColPos);
Run; { Основной цикл }
Done; { Завершение работы }
end. { Основная программа }
Теперь можно перейти к последнему пункту инициализации - чтению данных из файла таблицы, преобразованию данных из строки в соответствующий тип, размещению данных в одномерном массиве и на экране. Этим в процедуре Init будет заниматься локальная процедура ReadTable. Для начала она будет только выводить на экран считанную из файла строку данных в поля таблицы, используя процедуру перемещения курсора по клеткам таблицы (MoveCursor, которую также добавим на этом этапе).
Листинг 5
{ Процедура перемещения курсора по клеткам таблицы }
procedure MoveCursor
({ П а р а м е т р ы п р о ц е д у р ы }
X, { Номер столбца в таблице }
Y, { Номер строки в таблице }
InitY { Номер начальной строки в таблице на экране }
:byte;
Xpos { Массив координат столбцов }
:ColumnsPositionsArray);
begin
GotoXY(Xpos[X]+1,Y+InitY); end;
{ Процедура подготовки программы к работе }