Глава 02
Очереди и стеки имеют общие черты.
Во первых как очереди, так и стеки имеют очень строгие правила ссылок на хранящиеся в них данные, а именно доступ к очереди определяется правилом FIFO, а для стека правилом LIFO.
Во вторых в обоих случаях операции извлечения данных по своей природе являются разрушающими.
Иными словами, для того, чтобы получить доступ к элементу, хранящемуся в очереди или стеке, этот элемент необходимо удалить. Если элемент больше нигде не хранится, он будет удален.
Следующий контейнерный класс, который мы изучим свободен от этих ограничений. В отличие от стека или очереди связный список допускает гибкие методы доступа, поскольку каждый элемент данных содержит ссылку на следующий элемент данных в цепочке. Кроме того, для связных списков операция извлечения элемента не приводит к его удалению из списка о потере его данных. Для фактического удаления элемента связного списка требуется определить специальную операцию удаления.
Вверх
Построение параметризированного списка с двойными связями
Для построения параметризированного списка с двойными связями используем
//перегрузка << для указателя на объект типа listob
//перегрузка >> для ссылки на объект типа listob
Кроме того, вместе с классом listob определен ряд операций, которые могут
и модифицировать информацию,ассоциированную с объектом,
а так же получить указатели на следующий и предыдущий элементы.
Помимо этого, объекты типа listob могут вводиться и выводиться с помощью
Имейте в виду то, что операции, определенные внутри listob, независимы
При построении каждого из объектов, поля prior и next инициализируются
Функция getnext() возвращает указатель на следующий элемент списка.
В данном примере эти функции технически не являются необходимыми,
Обратите внимание что оператор << перегружается как для объектов
Хотя listob определяет природу списка с двойными ссылками, он сам не создает
template <class DataT>
class dllist : public listob<DataT>
Класс dllist поддерживает два указателя: один на начало списка, а другой
listob
dllist()
{start=end=NULL;} //конструктор
Вверх
Функция store()
Добавление новых элементов в список производится с помощью функции store().
if(!p)
Для того, чтобы добавить в список новый элемент, необходимо создать новый
После выделения памяти для объекта listob
после чего добавляет объект в конец списка.
Обратите внимание на то, что указатели start и end обновляются по мере
Поскольку объекты всегда добавляются в конец списка, список будет
Вверх
Функция remove()
Функция remove() удаляет объект из списка.
//Удаление элемента из списка с обновлением указателей
if(ob->next) //не последний элемент
Как и функция store(), функция remove() никак не зависит от типа данных,
Вверх
Отображение списка
Функции frwdlist() и bkwdlist() отображают содержимое списка в направлении
template <class DataT>
void dllist<DataT>::frwdlist()
//просмотр списка в обратном направлении
template <class DataT>
void dllist<DataT>::bkwdlist()
Вверх
Поиск объекта в списке
Ниже приведена функция find(), которая возвращает указатель на объект
{
temp=start;
Вверх
Полный листинг параметризованного класса
Ниже приведено полное определение параметризованного класса связного
#include<iostream.h>
template <class DataT> class listob
//перегрузка << для указателя на объект типа listob
//параметризованный класс объекта списка
template <class DataT> class listob
//параметризованный класс списка
template <class DataT> class dllist:public listob<DataT>
//деструтоор dllist
template <class DataT> dllist<DataT>::~dllist()
//добавление нового элемента
template <class DataT> void dllist<DataT>::store(DataT c)
p=new listob<DataT>
template <class DataT> void
//просмотр списка от начала к концу
template <class DataT> void dllist<DataT>::frwdlist()
//просмотр списка в обратном направлении
template <class DataT> void dllist<DataT>::bkwdlist()
//Поиск объекта, содержащего информацию, совпадающую с указанной
template <class DataT> lictob<Data> *dllist<DataT>::find(DataT c)
{
while(temp)
return NULL;//совпадений не найдено
//Ручной просмотр списка
cout << "Ручной просмотр списка: ";
cout << endl << endl;
//удаление элемента
p->getinfo(i);
cout << endl;
//добавление нового элемента
cout << "Dobavlenie novogo eleventa \n";
//изменение информации
p=list.find(1);
p->getinfo(i);
cout << endl;
//Демонстрация << и >>
cin >> *p;
cout << Spisok ot nachala k kontsu :";
//удалим начальный элемент списка
cout << " Posle udaleniya nachalnogo elementa spiska :';
cout << endl;
//удалим конечный элемент списка
cout << "Posle udaleniya konechnogo elementa spiska :";
cout<
dllist<double>dlist;
//использование функций-членов для отображения списка
cout << "Spisok elementov tipa double ot nachala k kontsu:";
return 0;
Результат:
Spisok tselyh ot nachala k kontsu:1 2 3
Ruchnoy prosmotr spiska: 1 2 3
Poisk 2
Udalenie eleventa 2
Dobavlenie novogo eleventa
Izmeneniye 1na 5.
Vvedite informaciyu: 3
Posle udaleniya nachalnogo elementa spiska :3 4
Posle udaleniya konechnogo elementa spiska :3
Spisok elementov tipa double ot nachala k kontsu:
Анализ:
Этот пример создает связный список с двойными ссылками и демонстрирует
Назад |
Начало урока |
Вверх |
Вперед
простую иерархию классов. Один из классов, называемый listob, определяет
природу объектов, которые будут храниться в списке. Этот класс затем
наследуется другим классом, dllist, который фактически и реализует
механизм списка с двойными связями.
Класс listob показан ниже:
template <class DataT>
class listob
{
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект
listob()
{
next=NULL;
prior=NULL;
listob<DataT> *getnext()
{return next;}
listob<DataT> *getprior()
{return prior;}
void getinfo(DataT &c)
{c=info;} //извлечение элемента
void change (DataT c)
{info=c;}//изменение элемента
friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator >> (istream &stream,listob<DataT> &o);
//перегрузка << для объекта типа listob
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> o)
{
return stream;
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> *o)
{
return stream;
template <class DataT>
istream &operator >> (istream &stream,listob<DataT> &o)
{
cout << "Vvedite informaciyu: ";
stream >> o.info;
return stream;
Как видно из этого листинга, listob имеет три члена.
Член info содержит
информацию, хранящуюся в списке, определяемую родовым типом DataT.
Указатель next будет указывать на следующий элемент списка,
а указатель
prior -на предыдущий.
DataT info ; //информация
Обратите внимание на то, что в этом примере
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект
все члены объекта listob декларируются как public. Это сделано только в целях
наглядности для упрощения демонстрации всех аспектов связного списка.
При разработке собственных приложений, программист может сделать их
закрытыми(private) или защищенными (protected).
выполняться над объектами класса listob.
В частности можно извлечь
void getinfo(DataT &c)
{c=info;} //извлечение элемента
void change (DataT c)
{info=c;}//изменение элемента
listob<DataT> *getnext()
{return next;}
listob<DataT> *getprior()
{return prior;}
перегруженных операторов << и >>.
friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator >> (istream &stream,listob<DataT> &o);
от механизма поддержки списка.listob определяет только природу данных,
которые должны храниться в списке.
значением NULL. Эти указатели сохранят значение NULL до тех пор, пока
в список не будет введен первый элемент. Если включен инициализатор,
то его значение будет скопировано в поле info, в противном случае
значение info инициализируется нулем.
listob()
{
next=NULL;
prior=NULL;
Если достигнут конец списка, то это значение будет NULL.
Функция getprior() возвращает указатель на предыдущий элемент списка
в том случае, если он существует, в противном случае - значение NULL.
listob<DataT> *getnext()
{return next;}
listob<DataT> *getprior()
{return prior;}
так как указатели next и prior декларировались как public. Однако
они понадобятся, если декларировать next и prior закрытыми или
защищенными.
типа listob так и для указателей на объекты этого типа.
Причина этого в том ,что при использовании связных списков,
широко распространена практика получения доступа к элементам списка
через указатель. Поэтому оператор << полезно перегружать, с тем чтобы
он мог оперировать с переданным ему указателем на объект. Однако,
поскольку нет причин полагать ,что будет использоваться только
этот метод, включена и вторая форма доступа, оперирующая непосредственно
с самим объектом. Как альтернативу можно использовать и перегрузку <<
для использования ссылок на объект типа listob.
этого списка. Механизм построения связного списка реализуется классом
dllist, приведенным ниже. Как можно видеть из приведенного листинга,
он наследует listob и оперирует с объектами этого типа.
{
~dllist();//деструктор для списка
void store(DataT c); //добавление нового элемента в список
void remove(listob
void frwdlist();//отображение списка сначала
void bkwdlist();//отображение списка с конца
listob
listob
listob
на его конец. Оба эти указателя являются указателями на объекты типа listob.
При создании списка оба указателя инициализируются значением NULL.
Класс dllist поддерживает целый ряд операций над списком с двойными
ссылками, в том числе:
Далее подробно рассмотрим эти процедуры.
Посмотрите пример реализации этой функции:
//добавление нового элемента
template <class DataT>
void dllist<DataT>::store(DataT c)
{
p=new listob<DataT>
{
exit(1);
p ->info=c;
if(start==NULL)
{
end=start=p;
else
{
end->next=p;
end=p;
объект типа listob. Поскольку связные списки представляют собой
динамические структуры, для функции store() логично будет получать объект
динамически, используя new.
listob
p=new listob
функция store() присваивает информацию, передаваемую в переменной с
члену info нового объекта,
p ->info=c;
if(start==NULL)
{
end=start=p;
else
{
p->prior=end;
end->next=p;
end=p;
надобности. Таким образом, start и end всегда будут указывать на
начало и конец списка.
неотсортированным. Однако при желании вы можете модифицировать
функцию store() таким образом, чтобы она поддерживала отсортированный
список.
Связный список, управляемый классом dllist, поддерживает список объектов
типа listob.
Тип данных, сохраняемых в объекте типа listob, не зависит
от функции store().
Посмотрите программу:
//на начало и конец
template <class DataT>
void dllist<DataT>::remove(listob<DataT> *ob)
{
{
else //удаляется первый элемент списка
{
{
start=ob->next;
else //теперь список пуст
Функция remove() удаляет из списка объект, на который указывает ее параметр
ob (при этом параметр функции ob должен представлять собой допустимый
указатель на объект типа listob). Удаляемый из списка объект может занимать
одну из трех характерный позиций в списке, - а именно в начале, в конце или
в середине списка. Функция remove() обрабатывает все три ситуации.
Следует иметь в виду, что хотя функция remove() и удаляет объект из списка,
этот объект физически не разрушается. Уничтожаются только ссылки,
и объект просто выпадает из цепочки. Разумеется объект при необходимости
можно и уничтожить. Для этого предназначается функция delete().
фактически хранящихся в списке.
от начала к концу и обратно. Эти функции включены чтобы нагляднее
проиллюстрировать работу класса dllist. Кроме того эти функции представляют
собой довольно удобные средства отладки.
//просмотр списка от начала к концу
{
temp=start;
while(temp)
{
temp=temp->getnext();
{
temp = end;
while(temp)
{
temp=temp->getprior();
cout<
списка, который содержит информацию, совпадающую с информацией, указанной
параметром функции. Если совпадений не найдено, функция возвращает NULL.
//Поиск объекта, содержащего информацию, совпадающую с указанной
template <class DataT>
lictob<Data> *dllist<DataT>::find(DataT c)
while(temp)
{
temp=temp->getnext();
return NULL;//совпадений не найдено
связного списка с двойными ссылками
списка с двойными ссылками и пример функции main(), демонстрирующей
его возможности. Обратите внимание на использование параметризованных данных
при определении функций. Во всех случаях данные, над которыми осуществляются
операции, обозначаются родовым типом DataT. Конкретизация типа данных
осуществляется только в функции main().
//Параметризованный класс связного списка с двойными ссылками
#include<string.h>
#include<stdlib.h>
{
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> o)
{
return stream;
template <class DataT>
ostream &operator << (ostream &stream,listob
{
return stream;
//перегрузка >> для ссылки на объект типа listob
template <class DataT>
istream &operator << (istream &stream,listob<DataT> &o)
{
cout<<"Vvedite informaciyu: ";
stream << o.info << endl;
return stream;
{
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект
listob()
{
next=NULL;
prior=NULL;
listob<DataT> *getnext()
{
return next;}
listob<DataT> *getprior()
{return prior;}
void getinfo(DataT &c)
{c=info;}
void change (DataT c)
{info=c;}//изменение элемента
friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator << (istream &stream,listob<DataT> &o);
{
public:
dllist()
{start=end=NULL;}
~dllist();//деструктор для списка
void store(DataT c);
void remove(listob<DataT> *ob);//удаление элемента
void frwdlist();//отображение списка сначала
void bkwdlist();//отображение списка с конца
listob<DataT> *find(DataT c);//указатель на найденное совпадение
listob<DataT> *getstart()
{return start;}
listob<DataT> *getend()
{return end;}
{
//освобождаем все элементы списка
p=start;
while(p)
{
delete p;
p=p1;
{
if(!p)
{
exit(1);
p ->info=c;
if(start==NULL)
{
end=start=p;
else //put on end
{
end->next=p;
end=p;
//Удаление элемента из списка с обновлением указателей
//на начало и конец
dllist
{
{
if(ob->next) //не последний элемент
ob->next->prior=ob->prior;
else //в противном случае удаляется последний элемент
end=ob->prior //обновление указателя на конец списка
else //удаляется первый элемент списка
{
{
start=ob->next;
else //теперь список пуст
start = end =NULL;
{
temp=start;
while(temp)
{
temp=temp->getnext();
cout<
{
temp = end;
while(temp)
{
cout << temp->info << " ";
temp=temp->getprior();
cout<
temp=start;
{
temp=temp->getnext();
main()
{
dllist<int> list;
int i;
listob<int> *p;
list.store(1);
list.store(2);
list.store(3);
//использование функций-членов для отображения списка
cout << "Список целых от начала к концу:";
list.frwdlist();
cout << "Список целых от конца к началу: ";
list.bkwlist();
cout << endl;
p=list.getstart();
while(p)
{
cout << i << " ";
p=p->getnext();//следующий элемент
cout << "Udalenie eleventa " << i << "\n";
list.remove(p);
cout << "Spisok posle udaleniya:";
list.frwdlist();
list.store(4);
cout << "Spisok posle dobavleniya: ";
list.frwdlist();
cout << endl;
if(!p)
{
return 1;
cout << "Izmeneniye "<< i << "na 5. \n";
p->change(5);
cout << "Spisok posle izmeneniya : ";
list.frwdlist();
cout << "Spisok v obratnom poryadke : ";
list.bkwdlist();
cout << "Tepery element soderghit :" << *p;
list.frwdlist();
cout << endl;
p=list.getstart();
list.remove(p);
list.frwdlist();
p=list.getend();
list.remove(p);
list.frwdlist();
dlist.store(98.6);
dlist.store(212.0);
dlist.store(88.9);
cout << "i ot kontsa r nachalu :";
dlist.frwdlist();
dlist.bkwdlist();
Spisok tselyh ot kontsa k nachalu: 3 2 1
Nayden element 2
Spisok posle udaleniya:1 3
Spisok posle dobavleniya: 1 3 4
Spisok posle izmeneniya : 5 3 4
Spisok v obratnom poryadke : 4 3 5
Tepery element soderghit :3
Spisok ot nachala k kontsu :3 3 4
98.6 212 88.9
i ot kontsa k nachalu:
88.9 212 98.6
различные функции, при помощи которых с этим списком можно оперировать.
После этого программа строит такой же список объектов типа double,
с тем чтобы продемонстрировать родовые свойства класса связных списков.
Попытайтесь самостоятельно создать разные списки, в которых хранятся
данные различных типов. В списке можно хранить любые сколь угодно
сложные составные структуры, например почтовые адреса.
Содержание