- •11. Записи
- •Текст программы:
- •11.1. Задания для самостоятельного выполнения
- •2. Сведения об ученике состоят из его имени, фамилии и названия класса (года обучения и буквы), в котором он учится. Дан файл f, содержащий сведения об учениках школы.
- •3. Дан файл f, содержащий те же самые сведения об учениках школы, что и в предыдущей задаче, и дополнительно отметки, полученные учениками в последней четверти.
- •12 Учеников не имеют оценок ниже 4
- •4. Сведения об автомобиле состоят из его марки, номера и фамилии владельца. Дан файл f, содержащий сведения о нескольких автомобилях. Найти:
- •5. Дан файл f, содержащий различные даты. Каждая дата - это число, месяц (номер месяца в году) и год. Найти:
- •6. Дан файл f, содержащий сведения о книгах. Сведения о каждой из книг - это фамилия автора, название и год издания.
- •7. Дан файл f1, который содержит номера телефонов сотрудников учреждения: указывается фамилия сотрудника, его инициалы и номер телефона. Найти телефон сотрудника по его фамилии и инициалам.
- •9. Дан файл f, содержащий сведения о веществах: указывается название вещества, его удельный вес и проводимость (проводник, полупроводник, изолятор).
11. Записи
Задание 11.1.
Постановка задачи
Написать для факультетской отборочной комиссии программу, которая бы расставляла абитуриентов, успешно сдавших экзамены, в порядке убывания суммы баллов по четырем экзаменам: физике, математике, информатике и русскому языку.
Выбор метода решения и проектирование
Правила приема известны всем, поэтому на них останавливаться не будем1. Тем не менее, договоримся о некоторых аспектах, которые могут оказаться существенными для нашей задачи. Во-первых, договоримся, что знания абитуриентов по предметам оцениваются целыми натуральными числами. Шкала (пятибальная или «богзнаетсколькибальная») в данном случае значения не имеет, т.к. оцениваются абсолютные значения сумм. Во-вторых, претендентов сотни, следовательно, данные о них и результаты расчетов должны храниться в файле. В исходном файле должна содержаться следующая информация:
ФИО абитуриента (string);
Оценка по физике (Integer);
Оценка по математике (Integer);
Оценка по информатике (Integer);
Оценка по русскому языку (Integer).
Основная проблема, возникающая в этой задаче – как хранить исходные данные. В данных явно присутствуют элементы, по крайней мере, двух разных типов: string и Integer и поэтому для работы с ними нельзя использовать ни один из рассмотренных нами выше типизированных нетекстовых файлов. С другой стороны, данные об абитуриентах, все-таки, имеет четко выраженную повторяющуюся структуру, а для таких файлов, как известно, рекомендуется использовать именно типизированные файлы. Получается замкнутый круг, дилемма: либо рекомендация не верна, либо мы не все знаем о файловых типах. В предположении, что не верна, все-таки, рекомендация попробуем определить некий гибрид: «текстовый файл с четко выраженной повторяющейся структурой», а именно, договоримся, что он будет иметь следующий формат:
Соответственно
для размещения этой же информации в
памяти машины будем использовать пять
массивов:
,
,
,
и
.
Индекс в каждом из этих массивов
однозначно идентифицирует абитуриента.
Можно, конечно, организовать двумерный
массив оценок, в котором первый индекс
будет определять номер абитуриента, а
второй – предмет, но в рассматриваемом
нами контексте это не принципиально.
Алгоритмически
задача не представляет особой сложности.
Информацию надо прочитать, вычислить
для каждого абитуриента сумму баллов,
а затем выполнить сортировку данных по
убыванию суммарного балла и сохранить
результаты в файле для последующего
использования. Принципиально задача
не отличается от 8.3, где мы сортировали
тех же абитуриентов, но безуспешно
сдавших вступительные экзамены и по
ключу, не связанному с IQ. Если и возникают
какие-то проблемы, то только из-за того,
что в отличие от ротного старшины
приемную комиссию интересует несколько
больше параметров и сортировать придется
не только суммарные баллы и
,
но и
,
,
и даже
– т.е. всю совокупность сведений об
абитуриенте.1
Текст программы:
Приведенная ниже программа использует следующие переменные:
FIO – строковый массив, предназначенный для хранения паспортных данных об абитуриенте;
Phis – целочисленный массив, предназначенный для хранения оценок, полученных абитуриентами за экзамен по физике;
Inf – целочисленный массив, предназначенный для хранения оценок, полученных абитуриентами за экзамен по информатике;
Mat – целочисленный массив, предназначенный для хранения оценок, полученных абитуриентами за экзамен по математике;
Rus – целочисленный массив, предназначенный для хранения оценок, полученных абитуриентами за экзамен по русскому языку;
Res – целочисленный массив, предназначенный для хранения суммарной оценки, полученных абитуриентами по результатам вступительных экзаменов.
program AbituraV1;
var
I,J,N,Max:Integer;
Phis,Inf,Mat,Rus,Res:array[1..200] of Integer;
FIO:array[1..200] of string;
C0:Integer;
F0:string;
F,T:Text;
begin
Assign(F,'abitur.txt');
ReSet(F);
N:=0;
while not EOF(F) do
begin
N:=N+1;
ReadLn(F,FIO[N]);
ReadLn(F,Phis[N],Inf[N],Mat[N],Rus[N]);
Res[N]:=Phis[N]+Inf[N]+Mat[N]+Rus[N];
end;
Close(F);
for I:=1 to N-1 do
begin
Max:=I;
for J:=I+1 to N do
if Res[J]>Res[Max]
then Max:=J;
C0:=Res[I];
Res[I]:=Res[Max];
Res[Max]:=C0;
F0:=FIO[I];
FIO[I]:=FIO[Max];
FIO[Max]:=F0;
C0:=Phis[I];
Phis[I]:=Phis[Max];
Phis[Max]:=C0;
C0:=Inf[I];
Inf[I]:=Inf[Max];
Inf[Max]:=C0;
C0:=Mat[I];
Mat[I]:=Mat[Max];
Mat[Max]:=C0;
C0:=Rus[I];
Rus[I]:=Rus[Max];
Rus[Max]:=C0;
end;
Assign(T,'stud.txt');
ReWrite(T);
for I:=1 to N do
begin
WriteLn(FIO[I]);
WriteLn(T,FIO[I]);
WriteLn(T,Phis[I]:3,Inf[I]:3,Mat[I]:3,Rus[I]:3);
end;
Close(T);
WriteLn(‘Расчет окончен. Нажмите клавишу Еnter’);
ReadLn;
end.
В общем-то, ничего страшного, пока не представишь, что эти студенты к концу пятого курса сдадут (?) около 40 экзаменов и столько же зачетов, курсовых. И как их тогда прикажете сортировать? Как вообще хранить и обрабатывать подобную информацию, когда один объект характеризуется десятками, а то и сотнями параметров? Элементарные манипуляции с данными становятся громоздкими и неуклюжими и никакие процедуры/функции от этого не спасут. Все было бы гораздо проще, если бы у нас была возможность создавать произвольные совокупные структуры данных, охватывающие всю информацию об объекте. Такие структуры принято называть записями. Pascal предоставляет возможность определить в рамках типа запись произвольное число полей (ячеек) различного типа, характеризующих объект.
Обращение к отдельным полям осуществляется с помощью уточненных идентификаторов (см. раздел «Лексемы»).
В качестве базового типа может использоваться любой предопределенный или объявленный в разделе TYPE тип данных (в том числе и запись).
Рисунок
11.1 - Размещение записей в ОЗУ
а)
2
5.2
б)
1
2.5
запись
А'
1
2.5
запись
В'
в)
5.2
4
6.2
3
1.2
2
9.5
2
5.1
г)
5
7.1
запись
в
записи
№ п.п. |
Фрагмент программы |
Комментарии |
1. |
var A:record X:Integer; Y:Real; end; |
Во фрагменте приведено описание переменной A типа запись, которая включает в себя два поля: A.X – целое и A.Y –действительное (рисунок 11.1а). |
2. |
var A,B:record X:Integer; Y:Real; Z:string; end; begin … A.X:=1; A.Y:=2.5; B:=A; А.Z:=’запись A’; B.Z:=’запись B’; … end. |
Во фрагменте приведено описание двух переменных A и B типа запись, каждая из которых включает в себя три поля: X – целое, Y –действительное и Z – строковое (рисунок 11.1б). |
3. |
type TMyRec= record X:Integer; Y:Real; end; var A:TMyRec; F:file of TMyRec; begin … ReadLn(A.X,A.Y); Write(F,A); … end. |
Во фрагменте приведено описание простой переменной A типа запись и файла F, каждый элемент которого является записью. Обратите внимание: прочитать целиком (например, с помощью оператора ReadLn(A)) такую запись с клавиатуры не представляется возможным, т.к. клавиатуре поставлен в соответствие текстовый буфер. Возникает ошибка несоответствия типов: буфер текстовый, а мы пытаемся прочитать информацию типа запись. Поэтому ввод значений записи с клавиатуры (или вывод их на экран дисплея) осуществляется по полям. |
4. |
type TMyRec= record X:Integer; Y:Real; end; var C:array[1..5] of TmyRec; |
Во фрагменте приведено описание массива C, каждый элемент которого представляет собой запись, состоящую из двух полей: X – целое, и Y - действительное (рисунок 11.1в). |
5. |
type TMyRec= record X:Integer; Y:Real; end; var A: record S:string; L:TMyRec; end; begin … A.S=:’запись в записи’; A.L.X:=5; A.L.Y:=7.1; … end. |
Во фрагменте приведено описание простой переменной A типа запись, поле L которой в свою очередь имеет сложную структуру и является записью (рисунок 11.1г). |
Тип запись позволяет решить множество проблем с использованием информации со сложной структурой, с частью из которых мы столкнулись в задаче 11.1. В самом деле, версия этой программы с записями выглядит гораздо эффектнее за счет упрощения обработки информации имеющей сложную периодическую структуру и возможности использования типизированных файлов1:
program AbituraV2;
type
TAbit= record
FIO:string;
Phis,Inf,Mat,Rus,Sum,Res:Integer;
end;
var
I,J,N,Max:Integer;
Abit:array[1..200] of TAbit;
C0:TAbit;
F:Text;
T:file of TAbit;
begin
Assign(F,'abitur.txt');
ReSet(F);
N:=0;
while not EOF(F) do
begin
N:=N+1;
ReadLn(F,Abit[N].FIO);
ReadLn(F,Abit[N].Phis,Abit[N].Inf,Abit[N].Mat,
Abit[N].Rus);
Abit[N].Res:=Abit[N].Phis+Abit[N].Inf+
Abit[N].Mat+Abit[N].Rus;
end;
Close(F);
for I:=1 to N-1 do
begin
Max:=I;
for J:=I+1 to N do
if Abit[J].Res>Abit[Max].Res
then
Max:=J;
C0:=Abit[I];
Abit[I]:=Abit[Max];
Abit[Max]:=C0;
end;
Assign(T,'stud.dat');
ReWrite(T);
for
I:=1 to N do
begin
WriteLn(Abit[I].FIO);
Write(T,Abit[I]);
end;
Close(T);
WriteLn(‘Расчет окончен. Нажмите клавишу Еnter’);
ReadLn;
end.
Единственное, пожалуй, неудобство заключается в удлинении идентификаторов при обращении к полям записи. Но и эта проблема вполне разрешима за счет использования оператора присоединения, который позволяет вывести идентификатор записи за пределы блока, содержащего несколько уточненных идентификаторов:
Работа оператора присоединения продемонстрирована в программе AbituraV3, которая с точностью до протоколов прохождения контрольных расчетов идентична программе AbituraV2:
program AbituraV3;
type
TAbit= record
FIO:string;
Phis,Inf,Mat,Rus,Res:Integer;
end;
var
I,J,N,Max:Integer;
Abit:array[1..200] of TAbit;
C0:TAbit;
F:Text;
T:file of TAbit;
begin
Assign(F,'abitur.txt');
ReSet(F);
N:=0;
while not EOF(F) do
begin
N:=N+1;
with Abit[N] do
begin
ReadLn(F,FIO);
ReadLn(F,Phis,Inf,
Mat,Rus,Rus);
Res:=Phis+Inf+Mat+Rus;
end;
end;
Close(F);
for I:=1 to N-1 do
begin
Max:=I;
for J:=I+1 to N do
if Abit[J].Res>Abit[Max].Res
then Max:=J;
C0:=Abit[I];
Abit[I]:=Abit[Max];
Abit[Max]:=C0;
end;
Assign(T,'stud.dat');
ReWrite(T);
for I:=1 to N do
begin
WriteLn(Abit[I].FIO);
Write(T,Abit[I]);
end;
ReadLn;
Close(T);
end.
Использование оператора присоединения не ведет ни к повышению быстродействия программ, ни к уменьшению размера исполняемого кода, но делает их более читаемыми, особенно, если речь идет о больших программных разработках, использующих структуры данных со множеством полей. При этом следует иметь в виду, что злоупотребление им может иногда приводить к неоднозначным ситуациям, возникающим при обработке разных записей, имеющих одинаковые названия полей, как это показано на следующих примерах.
program Example1;
var
X1:record
A,B:Integer;
end;
X2:record
A,X:Real;
end;
begin
X1.A:=1; X1.B:=2;
X2.A:=0; X2.B:=0;
with X1,X2 do
begin
A:=A+1;
X:=B+1;
end;
WriteLn(X1.A,X1.B);
WriteLn(X2.A,X2.X);
ReadLn;
e
nd.
program Example2;
var
X1:record
A,B:Integer;
end;
X2:record
A,X:Real;
end;
begin
X1.A:=1; X1.B:=2;
X2.A:=0; X2.B:=0;
with X2,X1 do
begin
A:=A+1;
X:=B+1;
end;
WriteLn(X1.A,X1.B);
WriteLn(X2.A,X2.X);
ReadLn;
end.
Не смотря на кажущуюся идентичность программ, результаты их работы существенно отличаются1, причем, разные версии одного и того же языка могут выдавать различные результаты. Подобные ситуации принято называть конструкциями с неочевидной семантикой. При профессиональном подходе к программированию ситуаций с неочевидной семантикой следует избегать всеми доступными средствами2. Подробнее об этом см. главу 13 «Разработка больших программных комплексов».
