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

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 )