12. Обобщенные алгоритмы
12. Обобщенные алгоритмы
В нашу реализацию класса Array (см. главу 2) мы включили функции-члены для поддержки операций min(), max() и sort(). Однако в стандартном классе vector эти, на первый взгляд фундаментальные, операции отсутствуют. Для нахождения минимального или максимального значения элементов вектора следует вызвать один из обобщенных алгоритмов. Алгоритмами они называются потому, что реализуют такие распространенные операции, как min(), max(), find() и sort(), а обобщенными (generic) – потому, что применимы к различным контейнерным типам: векторам, спискам, массивам. Контейнер связывается с применяемым к нему обобщенным алгоритмом посредством пары итераторов (мы говорили о них в разделе 6.5), указывающих, какие элементы следует посетить при обходе контейнера. Специальные объекты-функции позволяют переопределить семантику операторов в обобщенных алгоритмах. Итак, в этой главе рассматриваются обобщенные алгоритмы, объекты-функции и итераторы.
12.1. Краткий обзор
Реализация обобщенного алгоритма не зависит от типа контейнера, поэтому одна основанная на шаблонах реализация может работать со всеми контейнерами, а равно и со встроенным типом массива. Рассмотрим алгоритм find(). Если коллекция не отсортирована, то, чтобы найти элемент, требуются лишь следующие общие шаги:
По очереди исследовать каждый элемент. Если элемент равен искомому значению, то вернуть его позицию в коллекции. В противном случае анализировать следующий элемент Повторять шаг 2, пока значение не будет найдено либо пока не будет просмотрена вся коллекция. Если мы достигли конца коллекции и не нашли искомого, то вернуть некоторое значение, показывающее, что нужного элемента нет. Алгоритм, как мы и утверждали, не зависит ни от типа контейнера, к которому применяется, ни от типа искомого значения, однако для его использования необходимы:
способ обхода коллекции: переход к следующему элементу и распознавание того, что достигнут конец коллекции. При работе с встроенным типом массива мы решаем эту проблему, передавая два аргумента: указатель на первый элемент и число элементов, подлежащих обходу (в случае строк символов в стиле C передавать второй аргумент необязательно, так как конец строки обозначается двоичным нулем); умение сравнивать каждый элемент контейнера с искомым значением. Обычно это делается с помощью оператора равенства, ассоциированного со значениями типа, или путем передачи указателя на функцию, осуществляющую сравнение; некоторый обобщенный тип для представления позиции элемента внутри контейнера и специального признака на случай, если элемент не найден. Обычно мы возвращаем индекс элемента либо указатель на него. В ситуации, когда поиск неудачен, возвращается –1 вместо индекса или 0 вместо указателя. Обобщенные алгоритмы решают первую проблему, обход контейнера, с помощью абстракции итератора – обобщенного указателя, поддерживающего оператор инкремента для доступа к следующему элементу, оператор разыменования для получения его значения и операторы равенства и неравенства для определения того, совпадают ли два итератора. Диапазон, к которому применяется алгоритм, помечается парой итераторов: first адресует первый элемент, а last – тот, который следует за последним. К самому элементу, адресованному итератором last, алгоритм не применяется; он служит стражем, прекращающим обход. Кроме того, last используется как возвращаемое значение с семантикой “отсутствует”. Если же значение получено, то возвращается итератор, помечающий позицию найденного элемента.
Имеется по две версии каждого обобщенного алгоритма: в одной для сравнения применяется оператор равенства, а в другой – объект-функция или указатель на функцию, реализующую сравнение. (Объекты-функции рассматриваются в разделе 12.3.) Вот, например, реализация обобщенного алгоритма find(), в котором используется оператор сравнения для типов хранимых в контейнере элементов:
template class ForwardIterator, class Type
ForwardIterator
find( ForwardIterator first, ForwardIterator last, Type value )
{
for ( ; first != last; ++first )
if ( value == *first )
return first;
return last;
}
ForwardIterator (однонаправленный итератор) – это один из пяти категорий итераторов, предопределенных в стандартной библиотеке. Он поддерживает чтение и запись адресуемого элемента. (Все пять категорий рассматриваются в разделе 12.4.)
Алгоритмы достигают независимости от типов за счет того, что никогда не обращаются к элементам контейнера непосредственно; доступ и обход элементов осуществляются только с помощью итераторов. Неизвестны ни фактический тип контейнера, ни даже то, является ли он контейнером или встроенным массивом. Для работы со встроенным типом массива обобщенному алгоритму можно передать не только обычные указатели, но и итераторы. Например, алгоритм find() для встроенного массива элементов типа int можно использовать так:
#include algoritm
#include iostream
int main()
{
int search_value;
int ia[ 6 ] = { 27, 210, 12, 47, 109, 83 };
cout "enter search value: ";
cin search_value;
int *presult = find( &ia[0], &ia[6], search_value );
cout "The value " search_value
( presult == &ia[6]
? " is not present" : " is present" )
endl;
}
Если возвращенный указатель равен адресу &ia[6] (который расположен за последним элементом массива), то поиск оказался безрезультатным, в противном случае значение найдено.
Вообще говоря, при передаче адресов элементов массива обобщенному алгоритму мы можем написать
int *presult = find( &ia[0], &ia[6], search_value );
или
int *presult = find( ia, ia+6, search_value );
Если бы мы хотели ограничиться лишь отрезком массива, то достаточно было бы модифицировать передаваемые алгоритму адреса. Так, при следующем обращении к find() просматриваются только второй и третий элементы (напомним, что элементы массива нумеруются с нуля):
// искать только среди элементов ia[1] и ia[2]
int *presult = find( &ia[1], &ia[3], search_value );
А вот пример использования контейнера типа vector с алгоритмом find():
#include algorithm
#include vector
#include iostream
int main()
{
int search_value;
int ia[ 6 ] = { 27, 210, 12, 47, 109, 83 };
vectorint vec( ia, ia+6 );
cout "enter search value: ";
cin search_value;
vectorint::iterator presult;
presult = find( vec.begin(), vec.end(), search_value );
cout "The value " search_value
( presult == vec.end()
? " is not present " : " is present" )
endl;
}
find() можно применить и к списку:
#include algorithm
#include list
#include iostream
int main()
{
int search_value;
int ia[ 6 ] = { 27, 210, 12, 47, 109, 83 };
listint ilist( ia, ia+6 );
cout "enter search value: ";
cin search_value;
listint::iterator presult;
presult = find( ilist.begin(), ilist.end(), search_value );
cout "The value "search_value
( presult == ilist.end()
? " is not present" : " is present" )
endl;
}
(В следующем разделе мы обсудим построение программы, в которой используются различные обобщенные алгоритмы, а затем рассмотрим объекты-функции. В разделе 12.4 мы подробнее расскажем об итераторах. Развернутое введение в обобщенные алгоритмы – предмет раздела 12.5, а их детальное обсуждение и иллюстрация применения вынесено в Приложение. В конце главы речь пойдет о случаях, когда применение обобщенных алгоритмов неуместно.)
Упражнение 12.1
Обобщенные алгоритмы критикуют за то, что при всей элегантности дизайна проверка корректности возлагается на программиста. Например, если передан неверный итератор или пара итераторов, помечающая неверный диапазон, то поведение программы не определено. Вы согласны с такой критикой? Следует ли оставить применение обобщенных алгоритмов только наиболее квалифицированным специалистам? Может быть, нужно запретить использование потенциально опасных конструкций, таких, как обобщенные алгоритмы, указатели и явные приведения типов?
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Обобщенные алгоритмы
Обобщенные алгоритмы В заголовочном файле <QtAlgorithms> объявляются глобальные шаблонные функции, которые реализуют основные алгоритмы для контейнеров. Большинство этих функций работают с итераторами в стиле STL.Заголовочный файл STL <algorithm> содержит более полный набор
Что такое обобщенные указатели и почему они полезны
Что такое обобщенные указатели и почему они полезны Представим себе некий объект, который имеет перегруженную операцию operator->(). Мы можем его представить себе как некий обобщенный указатель, который не является указателем в полном смысле этого слова, но «прикидывается»
Алгоритмы
Алгоритмы В начале главы 1 я упоминал о том, что львиная доля репутации STL связана с контейнерами, и это вполне объяснимо. Контейнеры обладают массой достоинств и упрощают повседневную работу бесчисленных программистов С++. Но и алгоритмы STL тоже по-своему замечательны и в
АЛГОРИТМЫ
АЛГОРИТМЫ Все алгоритмы отделены от деталей реализации структур данных и используют в качестве параметров типы итераторов. Поэтому они могут работать с определяемыми пользователем структурами данных, когда эти структуры данных имеют типы итераторов, удовлетворяющие
Обобщённые численные операции (Generalized numeric operations)
Обобщённые численные операции (Generalized numeric operations) Накопление (Accumulate) template ‹class InputIterator, class T›T accumulate(InputIterator first, InputIterator last, T init);template ‹class InputIterator, class T, class BinaryOperation›T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation binary_op);accumulate подобен оператору APL reduction и функции Common Lisp reduce, но он
6.6.3. Обобщенные алгоритмы
6.6.3. Обобщенные алгоритмы Операции, описанные в предыдущих разделах, составляют набор, поддерживаемый непосредственно контейнерами vector и deque. Согласитесь, что это весьма небогатый интерфейс и ему явно не хватает базовых операций find(), sort(), merge() и т.д. Планировалось
12.5. Обобщенные алгоритмы
12.5. Обобщенные алгоритмы Первые два аргумента любого обобщенного алгоритма (разумеется, есть исключения, которые только подтверждают правило) – это пара итераторов, обычно называемых first и last, ограничивающих диапазон элементов внутри контейнера или встроенного массива,
12.6. Когда нельзя использовать обобщенные алгоритмы
12.6. Когда нельзя использовать обобщенные алгоритмы Ассоциативные контейнеры (отображения и множества) поддерживают определенный порядок элементов для быстрого поиска и извлечения. Поэтому к ним не разрешается применять обобщенные алгоритмы, меняющие порядок, такие,
21. Обобщенные алгоритмы в алфавитном порядке
21. Обобщенные алгоритмы в алфавитном порядке В этом приложении мы рассмотрим все алгоритмы. Мы решили расположить их в алфавитном порядке (за небольшими исключениями), чтобы проще было найти нужный. Каждый алгоритм представлен в следующем виде: сначала описывается
7.3.5 Обобщенные Классы
7.3.5 Обобщенные Классы Очевидно, можно было бы определить списки других типов (classdef*, int, char* и т.д.) точно так же, как был опредлен класс nlist: простым выводом из класса slist. Процесс оределения таких новых типов утомителен (и потому чреват ошиками), но с помощью макросов его можно
Обобщенные типы
Обобщенные типы Обобщенные типы: обзор Обобщенным типом (generic) называется шаблон для создания класса, записи или интерфейса, параметризованный одним или несколькими типами. Класс (запись, интерфейс) образуется из шаблона класса (записи, интерфейса) подстановкой
Обобщенные типы: обзор
Обобщенные типы: обзор Обобщенным типом (generic) называется шаблон для создания класса, записи или интерфейса, параметризованный одним или несколькими типами. Класс (запись, интерфейс) образуется из шаблона класса (записи, интерфейса) подстановкой конкретных типов в
Обобщенные подпрограммы в качестве параметров
Обобщенные подпрограммы в качестве параметров Обобщенная подпрограмма может выступать в качестве формального параметра другой обобщенной подпрограммы.Например, в классе System.Array имеется несколько статических обобщенных методов с обобщенными подпрограммами в качестве