ilist() : _at_front( 0 ),
_at_end( 0 ), _size( 0 ) {}
// ...
private:
ilist_item *_at_front;
ilist_item *_at_end;
int _size;
};
Теперь мы можем определять объекты типа ilist, например:
ilist mylist;
но пока ничего больше. Добавим возможность запрашивать размер списка. Включим объявление функции size() в открытый интерфейс списка и определим эту функцию так:
inline int ilist::size() { return _size; }
Теперь мы можем использовать:
int size = mylist.size();
Пока не будем позволять присваивать один список другому и инициализировать один список другим (впоследствии мы реализуем и это, причем такие изменения не потребуют модификации пользовательских программ). Объявим копирующий конструктор и копирующий оператор присваивания в закрытой части определения списка без их реализации. Теперь определение класса ilist выглядит таким образом:
class ilist {
public:
// определения не показаны
ilist();
int size();
// ...
private:
// запрещаем инициализацию
// и присваивание одного списка другому
ilist( const ilist );
ilist operator=( const ilist );
// данные-члены без изменения
};
Обе строки следующей программы вызовут ошибки компиляции, потому что функция main() не может обращаться к закрытым членам класса ilist:
int main()
{
ilist yourlist( mylist ); // ошибка
mylist = mylist; // ошибка
}
Следующий шаг – вставка элемента, для представления которого мы выбрали отдельный класс:
class ilist_item {
public:
// ...
private:
int _value;
ilist_item *_next;
};
Член _value хранит значение, а _next – адрес следующего элемента или 0.
Конструктор ilist_item требует задания значения и необязательного параметра – адреса существующего объекта ilist_item. Если этот адрес задан, то создаваемый объект ilist_item будет помещен в список после указанного. Например, для списка
0 1 1 2 5
вызов конструктора
ilist_item ( 3, pointer_to_2 );
модифицирует последовательность так:
0 1 1 2 3 5
Вот реализация ilist_item. (Напомним, что второй параметр конструктора является необязательным. Если пользователь не задал второй аргумент при вызове конструктора, по умолчанию употребляется 0. Значение по умолчанию указывается в объявлении функции, а не в ее определении; это поясняется в главе 7.)
class ilist_item {
public:
ilist_item( int value, ilist_-item *item_to_link_to = 0 );
// ...
};
inline
ilist_item::
ilist_item( int value, ilist_item *item )
: _value( value )
{
if ( item )
_next = 0;
else {
_next = item-_next;
item-_next = this;
}
Операция insert() в общем случае работает с двумя параметрами – значением и адресом элемента, после которого производится вставка. Наш первый вариант реализации имеет два недочета. Сможете ли вы их найти?
inline void
ilist::
insert( ilist_item *ptr, int value )
{
new ilist_item( value, ptr );
++_size;
}
Одна из проблем заключается в том, что указатель не проверяется на нулевое значение. Мы обязаны распознать и обработать такую ситуацию, иначе это приведет к краху программы во время исполнения. Как реагировать на нулевой указатель? Можно аварийно закончить выполнение, вызвав стандартную функцию abort(), объявленную в заголовочном файле cstdlib:
#include cstdlib
// ...
if ( ! ptr )
abort();
Кроме того, можно использовать макрос assert(). Это также приведет к аварийному завершению, но с выводом диагностического сообщения:
#include cassert
// ...
assert( ptr != 0 );
Третья возможность – возбудить исключение:
if ( ! ptr )
throw "Panic: ilist::insert(): ptr == O";
В общем случае желательно избегать аварийного завершения программы: в такой ситуации мы заставляем пользователя беспомощно сидеть и ждать, пока служба поддержки обнаружит и исправит ошибку.
Если мы не можем продолжать выполнение там, где обнаружена ошибка, лучшим решением будет возбуждение исключения: оно передает управление вызвавшей программе в надежде, что та сумеет выйти из положения.
Мы же поступим совсем другим способом: рассмотрим передачу нулевого указателя как запрос на вставку элемента перед первым в списке:
if ( ! ptr )