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 );