Выбрать главу

void ilist::insert_all ( const ilist rhs )

{

ilist_item *pt = rhs._at_front;

while ( pt ) {

insert_end( pt-value() );

pt = pt-next();

}

}

после чего копирующий конструктор и оператор присваивания можно реализовать так:

inline ilist::ilist( const ilist rhs )

: _at_front( 0 ), _at_end( 0 )

{ insert_all ( rhs ); }

inline ilist

ilist::operator=( const ilist rhs ) {

remove_all();

insert_all( rhs );

return *this;

}

Теперь осталось обеспечить пользователя возможностью путешествовать по списку, например с помощью доступа к члену _at_front:

ilist_item *ilist::front() { return _at_front(); }

После этого можно применить ilist_item::next(), как мы делали в функциях-членах:

ilist_item *pt = mylist.front();

while ( pt ) {

do_something( pt-value() );

pt = pt-next();

}

Хотя это решает проблему, лучше поступить иначе: реализовать общую концепцию прохода по элементам контейнера. В данном разделе мы расскажем об использовании цикла такого вида:

for ( ilist_item *iter = mylist.init_iter();

iter;

iter = mylist.next_iter() )

do_something( iter-value() );

(В разделе 2.8 мы уже касались понятия итератора. В главах 6 и 12 будут рассмотрены итераторы для имеющихся в стандартной библиотеке контейнерных типов и обобщенных алгоритмов.)

Наш итератор представляет собой несколько больше, чем просто указатель. Он должен уметь запоминать текущий элемент, возвращать следующий и определять, когда все элементы кончились. По умолчанию итератор инициализируется значением _at_front, однако пользователь может задать в качестве начального любой элемент списка. next_iter() возвращает следующий элемент или 0, если элементов больше нет. Для реализации пришлось ввести дополнительный член класса:

class ilist {

public:

// ...

init_iter( ilist_item *it = 0 );

private:

//...

ilist_item *_current;

};

init_iter() выглядит так:

inline ilist_item*

ilist::init_iter( i1ist_item *it )

{

return _current = it ? it : _at_front;

}

next_iter() перемещает указатель _current на следующий элемент и возвращает его адрес, если элементы не кончились. В противном случае он возвращает 0 и устанавливает _current в 0. Его реализацию можно представить следующим образом:

inline ilist_item*

ilist::

next_iter()

{

ilist_item *next = _current

? _current = _current-next()

: _current;

return next;

}

Если элемент, на который указывает _current, удален, могут возникнуть проблемы. Их преодолевают модификацией кода функций remove() и remove_front(): они должны проверять значение _current. Если он указывает на удаляемый элемент, функции изменят его так, чтобы он адресовал следующий элемент либо был равен 0, когда удаляемый элемент – последний в списке или список стал пустым. Модифицированная remove_front() выглядит так:

inline void

ilist::remove_front()

{

if ( _at_front ) {

ilist_item *ptr = _at_front;

_at_front = _at_front-next();

// _current не должен указывать на удаленный элемент

if ( _current == ptr )

_current = _at_front;

bump_down_size();

delete ptr;

}

}

Вот модифицированный фрагмент кода remove():

while ( plist ) {

if ( plist-value() == value )

{

prev-next( plist-next() );

if ( _current == plist )

_current = prev-next();

Что произойдет, если элемент будет вставлен перед тем, на который указывает _current? Значение _current не изменяется. Пользователь должен начать проход по списку с помощью вызова init_iter(), чтобы новый элемент попал в число перебираемых. При инициализации списка другим и при присваивании значение _current не копируется, а сбрасывается в 0.

Тестовая программа для проверки работы копирующего конструктора и оператора присваивания выглядит так::

#include iostream

#include "ilist.h"

int main()

{

ilist mylist;

for ( int ix = 0; ix 10; ++ix ) {

mylist.insert_front( ix );

mylist.insert_end( ix );

}

cout "\n" "Применение init_iter() и next_iter()"

"для обхода всех элементов списка:\n";

ilist_item *iter;

for ( iter = mylist.init_iter();

iter; iter = mylist.next_iter() )

cout iter-value() " ";

cout "\n" "Применение копирующего конструктора\n";

ilist mylist2( mylist );