Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

565

template < int _val > class less_equal_value { public:

bool operator() ( int val ) { return val <= _val; }

};

Вот как надо было бы вызвать такой класс для подсчета числа элементов, меньших или равных 25:

count_if( vec.begin(), vec.end(), less_equal_value<25>());

(Другие примеры определения собственных объектов-функций можно найти в Приложении.)

Упражнение 12.4

Используя предопределенные объекты-функции и адаптеры, создайте объекты-функции для решения следующих задач:

(a)Найти все значения, большие или равные 1024.

(b)Найти все строки, не равные "pooh".

(c)Умножить все значения на 2.

Упражнение 12.5

Определите объект-функцию для возврата среднего из трех объектов. Определите функцию для выполнения той же операции. Приведите примеры использования каждого объекта непосредственно и путем передачи его функции. Покажите, в чем сходство и различие этих решений.

12.4. Еще раз об итераторах

Следующая реализация шаблона функции не компилируется. Можете ли вы сказать,

// в таком виде это не компилируется template < typename type >

int

count( const vector< type > &vec, type value )

{

int count = 0;

vector< type >::iterator iter = vec.begin(); while ( iter != vec.end() )

if ( *iter == value ) ++count;

return count;

почему?

}

Проблема в том, что у ссылки vec есть спецификатор const, а мы пытаемся связать с ней итератор без такого спецификатора. Если бы это было разрешено, то ничто не помешало бы нам модифицировать с помощью этого итератора элементы вектора. Для

С++ для начинающих

566

предотвращения подобной ситуации язык требует, чтобы итератор, связанный с const-

// правильно: это компилируется без ошибок

вектором, был константным. Мы можем сделать это следующим образом: vector< type>::const_iterator iter = vec.begin();

Требование, чтобы с const-контейнером был связан только константный итератор, аналогично требованию о том, чтобы const-массив адресовался только константным указателем. В обоих случаях это вызвано необходимостью гарантировать, что содержимое const-контейнера не будет изменено.

Операции begin() и end() перегружены и возвращают константный или неконстантный итератор в зависимости от наличия спецификатора const в объявлении контейнера. Если

vector< int > vec0;

дана такая пара объявлений: const vector< int > vec1;

то при обращениях к begin() и end() для vec0 будет возвращен неконстантный, а для

vector< int >::iterator iter0 = vec0.begin();

vec1 константный итератор:

vector< int >::const_iterator iter1 = vec1.begin();

Разумеется, присваивание константному итератору неконстантного разрешено всегда.

// правильно: инициализация константного итератора неконстантным

Например:

vector< int >::const_iterator iter2 = vec0.begin();

12.4.1. Итераторы вставки

Вот еще один фрагмент программы, в котором есть тонкая, но серьезная ошибка. Видите

int ia[] = { 0, 1, 1, 2, 3, 5, 5, 8 }; vector< int > ivec( ia, ia+8 ), vres;

//...

//поведение программы во время выполнения не определено

ли вы, в чем она заключается?

unique_copy( ivec.begin(), ivec.end(), vres.begin() );

С++ для начинающих

567

Проблема вызвана тем, что алгоритм unique_copy() использует присваивание для копирования значения каждого элемента из вектора ivec, но эта операция завершится неудачно, поскольку в vres не выделено место для хранения девяти целых чисел.

Можно было бы написать две версии алгоритма unique_copy(): одна присваивает элементы, а вторая вставляет их. Эта последняя версия должна, в таком случае, поддерживать вставку в начало, в конец или в произвольное место контейнера.

Альтернативный подход, принятый в стандартной библиотеке, заключается в определении трех адаптеров, которые возвращают специальные итераторы вставки:

back_inserter() вызывает определенную для контейнера операцию вставки

push_back() вместо оператора присваивания. Аргументом back_inserter()

//правильно: теперь unique_copy() вставляет элементы с помощью

//vres.push_back()...

unique_copy( ivec.begin(), ivec.end(),

является сам контейнер. Например, вызов unique_copy() можно исправить, написав: back_inserter( vres ) );

front_inserter() вызывает определенную для контейнера операцию вставки push_front() вместо оператора присваивания. Аргументом front_inserter() тоже

является сам контейнер. Заметьте, однако, что класс vector не поддерживает

//увы, ошибка:

//класс vector не поддерживает операцию push_front()

//следует использовать контейнеры deque или list

unique_copy( ivec.begin(), ivec.end(),

push_front(), так что использовать такой адаптер для вектора нельзя: front_inserter( vres ) );

inserter() вызывает определенную для контейнера операцию вставки insert() вместо оператора присваивания. inserter() принимает два аргумента: сам

unique_copy( ivec.begin(), ivec.end(),

контейнер и итератор, указывающий позицию, с которой должна начаться вставка: inserter( vres ), vres.begin() );

Итератор, указывающий на позицию начала вставки, сдвигается вперед после каждой вставки, так что элементы располагаются в нужном порядке, как если бы мы

vector< int >::iterator iter = vres.begin(), iter2 = ivec.begin();

for ( ; iter2 != ivec.end() ++ iter, ++iter2 )

написали:

vres.insert( iter, *iter2 );

С++ для начинающих

568

12.4.2. Обратные итераторы

Операции begin() и end() возвращают соответственно итераторы, указывающие на первый элемент и на элемент, расположенный за последним. Можно также вернуть обратный итератор, обходящий контейнер от последнего элемента к первому. Во всех

контейнерах для поддержки такой возможности используются операции rbegin() и

vector< int > vec0; const vector< int > vec1;

vector< int >::reverse_iterator r_iter0 = vec0.rbegin();

rend(). Есть константные и неконстантные версии обратных итераторов: vector< int >::const_reverse_iterator r_iter1 = vec1.rbegin();

Обратный итератор применяется так же, как прямой. Разница состоит в реализации операторов перехода к следующему и предыдущему элементам. Для прямого итератора оператор ++ дает доступ к следующему элементу контейнера, тогда как для обратного к

// обратный итератор обходит вектор от конца к началу vector< type >::reverse_iterator r_iter;

for ( r_iter = vec0.rbegin();

// r_iter указывает на последний элемент

r_iter != vec0.rend();

// пока не достигли элемента перед первым

r_iter++ )

// переходим к предыдущему элементу

предыдущему. Например, для обхода вектора в обратном направлении следует написать:

{ /* ... */ }

Инвертирование семантики операторов инкремента и декремента может внести путаницу,

но зато позволяет программисту передавать алгоритму пару обратных итераторов вместо прямых. Так, для сортировки вектора в порядке убывания мы передаем алгоритму

//сортирует вектор в порядке возрастания sort( vec0.begin(), vec0.end() );

//сортирует вектор в порядке убывания

sort() пару обратных итераторов:

sort( vec0.rbegin(), vec0.rend() );

12.4.3. Потоковые итераторы

Стандартная библиотека предоставляет средства для работы потоковых итераторов чтения и записи совместно со стандартными контейнерами и обобщенными алгоритмами.

Класс istream_iterator поддерживает итераторные операции с классом istream или одним из производных от него, например ifstream для работы с потоком ввода из файла. Аналогично ostream_iterator поддерживает итераторные операции с классом ostream или одним из производных от него, например ofstream для работы с потоком вывода в файл. Для использования любого из этих итераторов следует включить

заголовочный файл