Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Срауструп CPP.doc
Скачиваний:
4
Добавлен:
05.11.2018
Размер:
4.95 Mб
Скачать

8.3.3 Реализация списка

Реализация функций slist_base очевидна. Единственная трудность

связана с обработкой ошибок. Например, что делать если пользователь

с помощью функции get() пытается взять элемент из пустого списка.

Подобные ситуации разбираются в функции обработки ошибок

slist_handler(). Более развитый метод, рассчитанный на особые

ситуации, будет обсуждаться в главе 9.

Приведем полное описание класса slist_base:

class slist_base {

slink* last; // last->next является началом списка

public:

void insert(slink* a); // добавить в начало списка

void append(slink* a); // добавить в конец списка

slink* get(); // удалить и возвратить

// начало списка

void clear() { last = 0; }

slist_base() { last = 0; }

slist_base(slink* a) { last = a->next = a; }

friend class slist_base_iter;

};

Чтобы упростить реализацию обеих функций insert и append, хранится

указатель на последний элемент замкнутого списка:

void slist_base_insert(slink* a) // добавить в начало списка

{

if (last)

a->next = last->next;

else

last = a;

last->next = a;

}

Заметьте, что last->next - первый элемент списка.

void slist_base::append(slink* a) // добавить в конец списка

{

if (last) {

a->next = last->next;

last = last->next = a;

}

else

last = a->next = a;

}

slist* slist_base::get() // удалить и возвратить начало списка

{

if (last == 0)

slist_handler("нельзя взять из пустого списка");

slink* f = last->next;

if (f== last)

last = 0;

else

last->next = f->next;

return f;

}

Возможно более гибкое решение, когда slist_handler - указатель на

функцию, а не сама функция. Тогда вызов

slist_handler("нельзя взять из пустого списка");

будет задаваться так

(*slist_handler)(" нельзя взять из пустого списка");

Как мы уже делали для функции new_handler ($$3.2.6), полезно

завести функцию, которая поможет пользователю создавать свои

обработчики ошибок:

typedef void (*PFV)(const char*);

PFV set_slist_handler(PFV a)

{

PFV old = slist_handler;

slist_handler = a;

return old;

}

PFV slist_handler = &default_slist_handler;

Особые ситуации, которые обсуждаются в главе 9, не только дают

альтернативный способ обработки ошибок, но и способ реализации

slist_handler.

8.3.4 Итерация

В классе slist_base нет функций для просмотра списка, можно только

вставлять и удалять элементы. Однако, в нем описывается как друг

класс slist_base_iter, поэтому можно определить подходящий для

списка итератор. Вот один из возможных, заданный в том стиле, какой

был показан в $$7.8:

class slist_base_iter {

slink* ce; // текущий элемент

slist_base* cs; // текущий список

public:

inline slist_base_iter(slist_base& s);

inline slink* operator()()

};

slist_base_iter::slist_base_iter(slist_base& s)

{

cs = &s;

ce = cs->last;

}

slink* slist_base_iter::operator()()

// возвращает 0, когда итерация кончается

{

slink* ret = ce ? (ce=ce->next) : 0;

if (ce == cs->last) ce = 0;

return ret;

}

Исходя из этих определений, легко получить итераторы для Slist и

Islist. Сначала надо определить дружественные классы для итераторов

по соответствующим контейнерным классам:

template<class T> class Islist_iter;

template<class T> class Islist {

friend class Islist_iter<T>;

// ...

};

template<class T> class Slist_iter;

template<class T> class Slist {

friend class Slist_iter<T>;

// ...

};

Обратите внимание, что имена итераторов появляются без определения

их шаблонного класса. Это способ определения в условиях взаимной

зависимости шаблонов типа.

Теперь можно определить сами итераторы:

template<class T>

class Islist_iter : private slist_base_iter {

public:

Islist_iter(Islist<T>& s) : slist_base_iter(s) { }

T* operator()()

{ return (T*) slist_base_iter::operator()(); }

};

template<class T>

class Slist_iter : private slist_base_iter {

public:

Slist_iter(Slist<T>& s) : slist_base_iter(s) { }

inline T* operator()();

};

T* Slist_iter::operator()()

{

return ((Tlink<T>*) slist_base_iter::operator()())->info;

}

Заметьте, что мы опять использовали прием, когда из одного базового

класса строится семейство производных классов (а именно, шаблонный

класс). Мы используем наследование, чтобы выразить общность классов

и избежать ненужного дублирования функций. Трудно переоценить

стремление избежать дублирования функций при реализации таких простых

и часто используемых классов как списки и итераторы. Пользоваться

этими итераторами можно так:

void f(name* p)

{

Islist<name> lst1;

Slist<name> lst2;

lst1.insert(p);

lst2.insert(p);

// ...

Islist_iter<name> iter1(lst1);

const name* p;

while (p=iter1()) {

list_iter<name> iter2(lst1);

const name* q;

while (q=iter2()) {

if (p == q) cout << "найден" << *p << '\n';

}

}

}

Есть несколько способов задать итератор для контейнерного класса.

Разработчик программы или библиотеки должен выбрать один из них

и придерживаться его. Приведенный способ может показаться слишком

хитрым. В более простом варианте можно было просто переименовать

operator()() как next(). В обоих вариантах предполагается взаимосвязь

между контейнерным классом и итератором для него, так что можно

при выполнении итератора обработать случаи, когда элементы добавляются

или удаляются из контейнера. Этот и некоторые другие способы задания

итераторов были бы невозможны, если бы итератор зависел от функции

пользователя, в которой есть указатели на элементы из контейнера.

Как правило, контейнер или его итераторы реализуют понятие "установить

итерацию на начало" и понятие "текущего элемента".

Если понятие текущего элемента предоставляет не итератор, а сам

контейнер, итерация происходит в принудительном порядке по отношению

к контейнеру аналогично тому, как поля связи принудительно хранятся

в объектах из контейнера. Значит трудно одновременно вести две

итерации для одного контейнера, но расходы на память и время при такой

организации итерации близки к оптимальным. Приведем пример:

class slist_base {

// ...

slink* last; // last->next голова списка

slink* current; // текущий элемент

public:

// ...

slink* head() { return last?last->next:0; }

slink* current() { return current; }

void set_current(slink* p) { current = p; }

slink* first() { set_current(head()); return current; }

slink* next();

slink* prev();

};

Подобно тому, как в целях эффективности и компактности программы

можно использовать для одного объекта как список с принудительной

связью, так и список без нее, для одного контейнера можно

использовать принудительную и непринудительную итерацию:

void f(Islist<name>& ilst)

// медленный поиск имен-дубликатов

{

list_iter<name> slow(ilst); // используется итератор

name* p;

while (p = slow()) {

ilst.set_current(p); // рассчитываем на текущий элемент

name* q;

while (q = ilst.next())

if (strcmp(p->string,q->string) == 0)

cout << "дубликат" << p << '\n';

}

}

Еще один вид итераторов показан в $$8.8.