P^.mNext:= List; { Цепляем предыдущую запись к новой записи }
List:= P; { Теперь голова указывает на новую запись }
end;
{ Распечатка списка }
procedure PrintList;
var P : PRec;
begin
P:= List;
while Assigned(P) do begin
Writeln(P^.mNumber, '':3, P^.mFam);
P:= P^.mNext;
end;
end;
begin { Главная программа }
List:= nil;
AddToList(10, 'Иванов');
AddToList(20, 'Петров');
AddToList(30, 'Сидоров');
PrintList;
Readln;
end.
Основу программы составляют процедуры вставки в список и его распечатки. В процедуре AddToList – добавить в список – первые строки вам знакомы: после создания динамической переменной P^ данные копируются в её поля. Обратите внимание на то, что указатель P – это локальная переменная, которая исчезнет после выхода из процедуры. И тогда, если не сохранить этот указатель, адрес новой переменной будет утерян, что приведет к утечке памяти. Поэтому после создания новой переменной P^ адрес из головы списка List копируется в поле mNext вновь созданной записи, и адрес новой записи P помещается в голову списка. Вот эти операторы.
P^.mNext:= List; { Цепляем предыдущую запись к новой записи }
List:= P; { Теперь голова указывает на новую запись }
Все просто, но если эти строки поменять местами, катастрофа неминуема!
Следующие рисунки показывают порядок наращивания списка. Здесь локальная переменная P обведена пунктиром, что отмечает её временный характер. Пунктирные стрелки с цифрами отмечают порядок выполняемых действий.
На рис. 121 и рис. 122 выполняется вставка первого элемента. В начальный момент, когда список ещё пуст, его голова – глобальная переменная List – содержит заглушку NIL, помещенную туда главной программой.
В результате присваивания оператором
P^.mNext:= List;
значение NIL попадает в поле новой переменной P^.mNext (после создания переменной это поле было не определено и содержало мусор).
Следующий затем оператор
List:= P;
сохраняет указатель на вновь созданную переменную в голове списка List. Теперь, перед выходом из процедуры, на эту переменную ссылаются уже два указателя (рис. 122). Но поскольку P – это локальная переменная, которая вскоре исчезнет, то после выхода из процедуры единственной ссылкой на первый элемент останется голова списка List.
Следующая пара рисунков показывает вставку второго и последующих элементов. Сначала адрес бывшего первого элемента копируется из головы списка List в поле mNext вновь созданной переменной. Теперь доступ ко всем последующим элементам возможен через это поле. А следующий оператор ставит во главе списка вновь созданную переменную. Так новичок возглавляет список, оттесняя старожилов на последующие места.
Теперь рассмотрим процедуру распечатки списка PrintList. Кто скажет, что она проста? Не проста, а очень проста! Подобные ей процедуры обработки списков строятся на основе цикла While. Скелет такой типовой процедуры показан ниже.
P:= List;
while Assigned(P) do begin
{ Здесь обрабатывается элемент списка }
P:= P^.mNext; { переход к следующему элементу }
end;
На входе в цикл указатель P загружается из головы списка. Внутри цикла, после обработки очередного элемента, переходим по указателю mNext к следующему. Напомню, что функция Assigned(P)равнозначна выражению P<>NIL. Таким образом, цикл завершится после обработки последнего элемента списка, поскольку его поле mNext содержит NIL. А если список пуст (голова содержит NIL), цикл не выполнится ни разу.
Программа «P_54_1» покажет на экране следующий результат.
30 Сидоров
20 Петров
10 Иванов
Как и следовало ожидать, порядок элементов в списке оказался обратным порядку вставки в него. Этим можно воспользоваться для построения стека, что мы и сделаем в свое время.
Углубившись в списки, мы чуть не забыли о нашем пользователе – полицейском. А ведь ему придется искать в базе данных информацию об угнанных автомобилях. Дополнив предыдущую программу функцией поиска, получим представленную ниже программу «P_54_2». Для экономии бумаги я не показал здесь процедуры вставки и распечатки списка.