Доступ к элементам коллекций
Доступ к элементам коллекций
Итак, оператором
BookList := New(PCollection, Init(50,10));
мы объявили о создании коллекции, а операторами
Insert(New(PBook, Init(...)))
наполнили эту коллекцию нужными элементами. Как осуществить доступ к элементам коллекции? Для этого можно использовать несколько способов.
Во-первых, к любому элементу коллекции можно обратиться по его порядковому номеру (индексу). В отличие от массивов Турбо Паскаля, индексы которых могут иметь произвольные границы, коллекции индексируются целыми числами в диапазоне от 0 до Count-l (Count - общее количество элементов в коллекции). Любая коллекция имеет поле Count, которое указывает текущую длину коллекции. Чтобы по индексу получить доступ к нужному элементу, используется метод At, который возвращает указатель на элемент.
Например, нам требуется вывести на экран содержимое тех записей нашего каталога, которые соответствуют 1991 году издания. Тогда вместо точек в конце программы, показанной на с.426, следует вставить оператор
PrintYear(BookList,1991);
Кроме того, в раздел описаний программы добавим две новых процедуры:
Procedure PrintItem(A,T,PB: String; Y,P: Word);
{Выводит на экран элемент коллекции}
begin
WriteLn(A); WriteLn(' ',Т):
WriteLnC ',РВ,', ',Y,', ',Р)
end; {PrintItem}
Procedure PrintYear(BookList: PCollection; Y: Word);
{Выводит на экран издания нужного года выпуска}
var
Book: PBook;
k: Integer;
begin
WriteLn;
for k := 0 to pred(BookList.Count) do
begin
Book := BookList.At(k);
with Book do if Year = Y then
PrintItem(Autor,Title,PubHouse,Year,Pages)
end
end; {PrintYear}
В процедуре PrintYear организуется счетный цикл от 0 до pred (TCollection.Count). С помощью оператора
Book := BookList.At(k);
в переменную Book помещается указатель на элемент коллекции с индексом k. Именно здесь обеспечивается полиморфизм коллекций: метод At возвращает нетипизированный указатель, который Вы можете интерпретировать нужным Вам образом. Однако здесь же таится источник трудно обнаруживаемых ошибок: в левую часть оператора присваивания можно поместить указатель любого типа и тип этого указателя может не соответствовать типу k-гo элемента коллекции.
Отметим, что обращение к методу At с индексом, выходящим за границы коллекции, активизирует вызов метода TCollection.Error, который по умолчанию аварийно завершает исполнение программы (подробнее см. п. 17.6).
Помимо использования метода At коллекции обеспечивают доступ к трем итерационным методам, которые могут оказаться весьма удобными. Метод ForEach осуществляет некоторую заранее заданную операцию сразу над всеми элементами коллекции, а методы FirstThat и LastThat отыскивают в коллекции первый элемент, удовлетворяющий некоторому опять же заранее заданному критерию поиска: FirstThat ищет от начала коллекции к ее концу, a LastThat - в обратном направлении.
Чтобы воспользоваться методом ForEach мы должны сначала создать процедуру без параметров, которая осуществляет нужные действия над всеми элементами коллекции, и передать адрес этой процедуры как параметр обращения к ForEach. Например, для того чтобы вьшести на экран содержимое всего нашего каталога, мы должны внести в программу следующие изменения. Поместите в раздел описаний следующий текст процедуры PrintAll:
Procedure PrintAll (Book: PCollection) ;
{Вывод всех элементов коллекции}
Procedure PrintBook(P: PBook) ; far;
begin
with Р do
PrintItem (Autor, Title, PubHouse, Year, Pages)
end; {PrintBook}
begin {PtintAll}
WriteLn;
Book .ForEach (PrintBook) ;
end; {PrintAll}
Как видите, эта процедура содержит внутреннюю процедуру PrintBook, в которой осуществляется нужное нам действие - вывод очередного элемента каталога на экран. Этот вывод достигается с помощью вызова уже использованной нами ранее процедуры PrintItem. Таким образом, описание процедуры PrintAll должно следовать после описания PrintItem, чтобы этот вызов был синтаксически правильным. Далее, вывод всех элементов коллекции в процедуре PrintAll осуществляется оператором
Book. ForEach (PrintBook) ;
который обращается к методу TCollection.ForEach, передавая ему в качестве параметра адрес процедуры PrintBook. Чтобы программа успешно выполнила нужные действия, процедура, адрес которой передается методу ForEach, должна удовлетворять двум условиям:
- она должна быть рассчитана на дальнюю модель вызова (для этих целей мы указали директиву far сразу за заголовком PrintBook);
- она должна быть локальной для процедуры, в которой реализуется вызов ForEach, именно поэтому мы разместили ее в теле процедуры PrintAll, Осталось в тело главной программы поместить оператор
PrintAll (BookList) ;
перед оператором уничтожения коллекции. Если Вы запустите таким образом подготовленную программу на счет, на экран будет выведено:
Джордейн Р .
Справочник программиста персональных компьютеров типа IBM PC,
XT и AT
Финансы и статистика, 1991, 544
Шелдон
Язык Си для профессионалов
И. В. К. -СОФТ, 1991, 383
Скэнлон Л.
Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера
Радио и связь, 1991, 336
Йенсен К., Вирт Н.
Паскаль . Руководство для пользователя и описание языка
Финансы и статистика, 1982, 151
Все действия по выводу содержимого коллекции реализуются методом ForEach, который для собственно вывода каждого очередного элемента обращается к процедуре PrintBook.
Точно таким же образом реализуется обращение к методам FirstThat и LastThat. Например, если нам потребуется отыскать в каталоге запись, содержащую слово «Вирт» в noлe Autor, можно добавить в программу следующие строки
Procedure SearchAutor(BookList: Pcollection; A: String);
Function FindAutor(P: PBook): Boolean; far;
begin
FindAutor := pos(А, Р.Autor) <>0
end; {FindAutor}
var
Book: PBook;
begin {SearchAutor}
Book := BookList.FirstThat(FindAutor);
if Book = NIL then
WriteLn('Heт автора ',А)
else with Book do
begin
WriteLn;
PrintItern(Autor,Title,PubHouse,Year,Pages)
end
end; {SearchAutor}
В тело главной программы следует добавить оператор
SearchAutor(BookList,'Вирт');
Собственно поиск элемента коллекции реализуется оператором
Book := BookList.FirstThat(FindAutor);
который для этих целей обращается к методу TCollection.FirstThat. В этом методе реализуется последовательный анализ всех элементов коллекции, начиная с самого первого (с индексом 0), причем для анализа используется вызов функции FindAutor. Как видим, эта функция нужным образом анализирует очередную запись и возвращает True, если условие поиска удовлетворено. Метод FirstThat возвращает указатель на элемент коллекции, для которого удовлетворено условие поиска, или NIL, если этому условию не отвечает ни один элемент. Таким образом, оператор
if Book = NIL then
.....
else
.....
проверяет результат поиска и выводит на печать найденный элемент коллекции или сообщение «Нет автора ...», если условие поиска не удовлетворено.
Как и в случае метода ForEach, функция, передаваемая методу FirstThat, должна транслироваться в расчете на дальнюю модель памяти и должна локализоваться в теле процедуры, в которой осуществляется вызов метода.
Любой элемент коллекции можно удалить или заменить новым. Для удаления используется метод AtFree, которому в качестве параметра передается индекс удаляемого элемента. При удалении элемента предполагается, что коллекция содержит указатели на объекты, порожденные от TObect и размещенные в куче, поэтому автоматически вызывается метод TObject.Done. Индексы всех элементов, размещенных в коллекции после удаляемого элемента, уменьшаются на 1.
С помощью метода DeleteAll удаляются все элементы из коллекции, но сама коллекция при этом сохраняется, т.е. очищенная коллекция будет иметь Count = 0. Для очистки коллекции вызывается AtFree для каждого элемента.
Чтобы заменить существующий элемент новым, используется метод AtPut (Index, Item), где Index - индекс заменяемого элемента, a Item - указатель на новый элемент.
Метод Atlnsert (Index, Item) вставляет новый элемент в коллекцию в позицию Index и увеличивает индексы всех ранее существовавших в коллекции элементов от элемента Index до конца коллекции на единицу, т.е. «раздвигает» коллекцию.