Содержание
Предисловие к русскому изданию
Предисловие
Как читать эту книгу
Кому стоит читать эту книгу
Как эта книга появилась на свет
Благодарности
Часть 1. Введение в обобщенное программирование
1.2. Заключение
2. Алгоритмы  и диапазоны
2.1.2. Диапазоны
2.1.3. Линейный поиск в С++
2.2. Концепции и моделирование
2.3. Итераторы
2.3.2. Итераторы вывода
2.3.3. Однонаправленные итераторы
2.3.4. Двунаправленные итераторы
2.3.5. Итераторы произвольного доступа
2.4. Развитие концепций
2.5. Заключение
3. Подробнее об итераторах
3.1.2. Разностный тип
3.1.3. Ссылочный тип и тип указателей
3.1.4. Алгоритмы диспетчеризации и теги итератора
3.1.5. Общее обозрение
3.1.6. Признаки итераторов без iterator_traits
3.2. Определение новых компонент
3.2.2. Советы по написанию итератора
3.2.3. Советы по написанию алгоритма
3.3. Заключение
4. Функциональные объекты
4.2. Концепции функциональных объектов
4.2.2. Предикаты и бинарные объекты
4.2.3. Ассоциированные типы
4.3. Адаптеры функциональных объектов
4.4. Предопределенные функциональные объекты
4.5. Заключение
5. Контейнеры
5.1.2. Как это работает
5.1.3. Последние штрихи
5.2. Концепции контейнеров
5.2.2. Итераторы
5.2.3. Иерархия контейнеров
5.2.4. Тривиальный контейнер
5.3. Концепции контейнера переменного размера
5.3.2. Ассоциативные контейнеры
5.3.3. Аллокаторы
5.4. Заключение
5.4.2. Определение вашего собственного контейнера
Часть 2. Справочное руководство: концепции STL
6.2. Конструируемый по умолчанию
6.3. =Сравнимый
6.4. Упорядочение
6.4.2. Строго слабо сравнимый
7. Итераторы
7.2. Итератор ввода
7.3. Итератор вывода
7.4. Однонаправленный итератор
7.5. Двунаправленный итератор
7.6. Итератор произвольного доступа
8. Функциональные объекты
8.1.2. Унарная функция
8.1.3. Бинарная функция
8.2. Адаптируемые функциональные объекты
8.2.2. Адаптируемая унарная функция
8.2.3. Адаптируемая бинарная функция
8.3. Предикаты
8.3.2. Бинарный предикат
8.3.3. Адаптируемый предикат
8.3.4. Адаптируемый бинарный предикат
8.3.5. Строгое слабое упорядочение
8.4. Специализированные концепции
8.4.2. Функция хеширования
9. Контейнеры
9.1.2. Однонаправленный контейнер
9.1.3. Реверсивный контейнер
9.1.4. Контейнер произвольного доступа
9.2. Последовательности
9.2.2. Последовательность с начальной вставкой
9.2.3. Последовательность с концевой вставкой
9.3. Ассоциативные контейнеры
9.3.2. Уникальный ассоциативный контейнер
9.3.3. Множественный ассоциативный контейнер
9.3.4. Простой ассоциативный контейнер
9.3.5. Парный ассоциативный контейнер
9.3.6. Сортированный ассоциативный контейнер
9.3.7. Хешированный ассоциативный контейнер
9.4. Аллокатор
Часть 3. Справочное руководство: алгоритмы  и классы
10.2. Примитивы итераторов
10.2.2. Классы тегов итераторов
10.2.3. distance
10.2.4. advance
10.2.5. Базовый класс итераторов
10.3. Класс allocator
10.4. Примитивы управления памятью
10.4.2. destroy
10.4.3. uninitialized_copy
10.4.4. uninitialized_fill
10.4.5. uninitialized_fill_n
10.5. Временные буферы
10.5.2. return_temporary_buffer
11. Неизменяющие алгоритмы
11.2. Сравнение подпоследовательностей
11.3. Подсчет элементов
11.5. Сравнение двух диапазонов
11.6. Минимум и максимум
12. Основные изменяющие алгоритмы
12.2. Перестановка элементов
12.4. Замена элементов
12.5. Заполнение диапазонов
12.6. Удаление элементов
12.7. Алгоритмы перестановки
12.8. Разбиения
12.9. Случайные перестановки и выборки
12.10. Обобщенные числовые алгоритмы
13. Сортировка и поиск
13.2. Операции над отсортированными диапазонами
13.2.2. Слияние двух отсортированных диапазонов
13.2.3. Операции над множествами на отсортированных диапазонах
13.3. Операции с кучами
14. Классы итераторов
14.1.2. back_insert_iterator
14.1.3. insert_iterator
14.2. Итераторы потоков
14.2.2. ostream_iterator
14.2.3. istreambuf_iterator
14.2.4. ostreambuf_iterator
14.3. reverse_iterator
14.4. raw_reverse_iterator
15. Классы функциональных объектов
15.1.2. binary_function
15.2. Арифметические операции
15.3. Сравнения
15.4. Логические операции
15.5. Тождественность и проекция
15.5.2. project1st
15.5.3. project2nd
15.5.4. select1st
15.5.5. select2nd
15.6. Специализированные функциональные объекты
15.6.2. subtractive_rng
15.7. Адаптеры функций-членов
15.7.2. mem_fun_ref_t
15.7.3. mem_fun1_t
15.7.4. mem_fun1_ref_t
15.7.5. const_mem_fun_t
15.7.6 const_mem_fun_ref_t
15.7.7 const_mem_fun1_t
15.7.8 const_mem_fun1_ref_t
15.8. Другие адаптеры
15.8.2. binder2st
15.8.3. pointer_to_unary_function
15.8.4. pointer_to_binary_function
15.8.5. unary_negate
15.8.6. binary_negate
15.8.7. unary_compose
15.8.8. binary_compose
16. Классы контейнеров
16.2. Ассоциативные контейнеры
16.3. Адаптеры контейнеров
Приложение А. Переносимость и стандартизация
А.1.2. Параметры шаблона по умолчанию
А.1.3. Шаблоны-члены
А.1.4. Частичная специализация
А.1.5. Новые ключевые слова
А.2. Изменения в библиотеке
А.2.2. Адаптеры контейнера
А.2.3. Второстепенные изменения библиотеки
А.3. Именование и компоновка
Список литературы
Алфавитный указатель
Текст
                    information must be free
-
VBOBUJJEHHOE CT.
ПРОГРАММИРОВАНИЕМ ML
ИСПОЛЬЗОВАНИЕ И НАРАЩИВАНИЕ
СТАНДАРТНОЙ БИБЛИОТЕКИ
ШАБЛОНОВ C++
4
МЭТЬЮ Г. ОСТЕРН
Эта книга не по объектно-ориентированному
программированию Вам это может показаться странным
Вы, вероятно, нашли эту книгу в разделе "C++”___
книжного магазина и, наверное, слыв
“объектно-ориентирванный" и "C+ +
как синонимы. Однако язык C+ + моя
И иначе. C + + поддерживает нссколь
различающихся парадигм, в том числе
новых и наименее знакомых обобщ
программирование (generic program
программирование, как и почти все hi
имеет длительную историю Первые и
статьи по обобщенному программиро
опуб икованы почти 25 л т пазам п
л.1 ip. риментальные обобщены lie биб.
написаны не на C+ + , а на языка* Ad.

Matthew H. Austern Generic Programming and the STL: Using and Extending the C++ Standard Template Library Addison-Wesley An Imprint of Addison Wesley Longman, Inc. Reading, Massachusetts • Harlow, England • Menlo Park, California Berkeley, California - Don Mills, Ontario • Sydney Bonn • Amsteidam • Tokyo • Mexico City
Мэтью Г. Остерн Обобщенное программирование и STL: Использование и наращивание стандартной библиотеки шаблонов C+ + Санкт-Петербург 2004
УДК 681.3.06 ББК 32.973-018 0-76 Перевели с английского С. Анисимов, Ю. Прокофьев, В Калмыцкое Редакторы перевода/!. Махоткин и И. В. Романовский Остерн М. Г. 0-76 Обобщенное программирование и STL: Использование и наращивание стандартной библиотеки шаблонов C++ / Пер. с англ, под ред. А. Махоткина и И. В. Романовского. — СПб.: Невский Диалект, 2004. — 544 с.: ил. В noil кише идея обобщенною upoi раммирования демонстрируемся на вполне реальном и содержа- le.ibiio.M примере* — па iai< называемой Стандартом библио!с*кс* шаблонов (STL — Standard Template Libiaiy), коюрая важной сое 1авной частью вошла в Международный сiандар! языка C++ В шкете нос ледова! гл ьно рассма i риваются и разъясняюк'я понятия и механизмы, необходимые для ре а'ш гании шабтонов библиенеки Значишльную часы» издания занимаю! два справочных раздела pa г- дсл концепций, нсполыуемых при создании библиотеки, и раздел атюршмов и классов Kiии а будет необходима upoi раммнетам. ак! ивно использующим язык C++ и библ испеку STL. а 1ак- же* ie\i. кюхочс! ра-цтаба।ыва।ь собо венные шаблоны Все* права щщищены Никакая чаем» згой книги нс можш быть воспроизведена в любой форме* и любыми средствами, зле ктровными или механическими (включая фоюграфи- рованис. магши HVio завис ь либо иные средства копирования или сохранения информа- ции). бел иис именного разрешения изда!сльства ISBN 5-7940-0119-4 (“Невский Диалект”) ISBN 0-201-30956-4 (англ ) © Addison Wesley Longman, Inc , 1999 © “Невский ДиалекГ, 2003 Научное издание Мэтью Г. ОСТЕРН Обобщенное программирование и STL- Использование и наращивание стандартной библиотеки шаблонов C++ Выпускающий редактор О М Рощиненко Издательство “Невский Диалект” Санкт-Петербург. Политехническая ул , 16 Лицензия на издательскую деятельность ЛР № 065012 от 18 02 1997 г Подписано в печать 17 И 2003 г Формат 70 X 1ОО’Х6 Бумага газетная Печать офсетная Гарнитура PetersburgC Усл печ л 44,2. Тираж 3000 Заказ М» 1258 Отпечатано с ютовых диапозитивов в Академической типографии “Наука” РАН 199034, Санкт-Петербург, 9 линия, 12
Оглавление Предисловие к русскому изданию.......................................... 12 Предисловие............................................................. 13 Часть I. Введение в обобщенное программирование......................... 19 Глава 1. Обзор STL.......................................................20 1.1. Простой пример.................................................. 20 1.2. Заключение...................................................... 24 Глава 2. Алгоритмы и диапазоны.......................................... 25 2 1. Линейный поиск.................................................. 25 2.1.1 Линейный поиск в Си.......................................... 26 2 1.2. Диапазоны .................................................. 28 2.1.3. Линейный поиск в C++........................................ 29 2.2. Концепции и моделирование........................................32 2.3. Итераторы ................................................... ... 35 2.3.1. Итераторы ввода............................................. 36 2.3.2 Итераторы вывода............................................. 38 2.3 3. Однонаправленные итераторы.................................. 41 2.3.4. Двунаправленные ит ераторы.................................. 44 2.3 5. Итераторы произвольного доступа........................... 44 2.4. Развитие концепций.............................................. 45 2.5 Заключение....................................................... 48 Глава 3. Подробнее об итераторах........................................ 50 3.1. Признаки итераторов и ассоциированные типы.................... 50 3.1.1 Типы значений................................................ 50 3.1.2 Разностный тип............................................... 53 3 1.3 Ссылочный тип и тип указателей............................. .. 55 3 1.4 Алгоритмы диспетчеризации и теги итератора................... 55 3 1.5. Общее обозрение............................................. 58 3.1.6. Признаки итераторов без iterator_t raits.................... 60 3.2. Определение новых компонент..................................... 61 3 2.1 Адаптеры итераторов ......................................... 63 3.2.2. Советы по написанию итератора .............................. 64 3 2.3 Советы по написанию алгоритма................................ 64 3 3. Заключение...................................................... 65 Глава 4. Функциональные объекты..........................................66 4 1 Обобщенный линейный поиск........................................ 66 4 2. Концепции функциональных объектов.......................... ... 69 4.2 1 Унарные и бинарные функциональные объекты.................... 69 4.2 2 Предикаты и бинарные предикаты... ...... .... 70 4 2.3 Ассоциированные типы .................................... .. 71 4 3 Адаптеры функциональных объектов .................................73 4 4. Предопределенные функциональные объекты......................... 75 4.5. Заключение.......................................................76
Оглавление Глава 5. Контейнеры ........................................................77 5.1. Простой контейнер................................................... 77 5.1.1 Класс массива....................................................78 5.1.2. Как это работает ...............................................81 5 1.3. Последние штрихи............................................... 82 5.2. Концепции контейнеров.............................................. 85 5.2 1 Содержание элементов............................................ 86 5 2.2 Итераторы........................................................86 5.2 3 Иерархия контейнеров.............................................88 5 2 4. Тривиальный контейнер...........................................89 5.3. Концепции контейнера переменного размера............................90 5.3.1. Последовательности .............................................91 5.3 2 Ассоциативные контейнеры........................................94 5.3.3. Аллокаторы......................................................96 5.4 Заключение...........................................................97 5.4 1. Каким контейнером воспользоваться?.............................98 5 4 2. Определение вашего собственного контейнера.................. 98 Часть II. Справочное руководство: концепции STL.............................99 Глава 6. Базовые концепции................................................. 100 6 1. Присваиваемый ...................................................100 6.2 . Конструируемый по Умолчанию.......................................101 6.3 . =Сравнимый........................................................102 6.4 . Упорядочение.......................................................ЮЗ 6.4.1. <Сравнимый.....................................................103 6.4.2. Строго Слабо Сравнимый.........................................104 Глава?. Итераторы..........................................................107 7 1. Тривиальный Итератор...............................................107 7 2. Итератор Ввода.....................................................109 7 3. Итератор Вывода....................................................112 7.4. Однонаправленный Итератор..........................................115 7.5. Двунаправленный Итератор...........................................117 7 6 Итератор Произвольного Доступа......................................118 Глава 8. Функциональные объекты............................................122 8 1. Основные функциональные объекты....................................123 8.1.1. Генератор.................................................... 123 8.1.2. Унарная Функция............................................... 124 8 13 Бинарная Функция............................................... 125 8.2 Адаптируемые функциональные объекты.................................126 8.2 1. Адаптируемый Генератор.........................................126 8.2 2. Адаптируемая Унарная Функция.................................. 127 8.2.3. Адаптируемая Бинарная Функция..................................128 8 3 Предикаты...........................................................129 8 3 1. Предикат.......................................................129 8 3.2. Бинарный Предикат............................................. 130 8 3.3 Адаптируемый Предикат.......................................... 131 8.3 4. Адаптируемый Бинарный Предикат.................................131 8.3.5. Строгое Слабое Упорядочение....................................132 8 4. Специализированные концепции.......................................134 8 4 1. Генератор Случайных Чисел .....................................134 8 4 2. Функция Хеширования............................................135
Оглавление 7 Глава 9. Контейнеры ...........................................................137 9 1. Общие концепции контейнеров........................................... 137 9 11 Контейнер.......................................................... 137 9 1 2. Однонаправленный Контейнер....................................... 143 9 1 3. Реверсивный Контейнер ........................................... 145 9 14. Контейнер Произвольного Доступа................................... 146 9.2. Последовательности.................................................... 147 9.2 1. Последовательность................................................147 9 2 2. Последовательность с Начальной Вставкой...........................153 9.2.3 Последовательность с Концевой Вставкой............................ 155 9.3. /\ссоциативные контейнеры..............................................156 93 1 Ассоциативный Контейнер............................................ 156 9 3.2 Уникальный Ассоциативный Контейнер.................................161 9 3 3 Множественный Ассоциативный Контейнер..............................163 9.3.4. Простой Ассоциативный Контейнер.................................. 164 9 35 Парный Ассоциативный Контейнер..................................... 166 9.3 6. Сортированный Ассоциативный Контейнер............................ 167 9.3 7 Хешированный Ассоциативный Контейнер...............................171 9.4 Аллокатор... .......................................................... 176 Часть III. Справочное руководство: алгоритмы и классы..........................183 Глава 10. Основные компоненты .................................................184 10 1. Класс pair .......................................................... 184 10.2 Примитивы итераторов................................................ 186 10.2 1 Класс iterator_traits............................................ 186 10.2.2. Классы тегов итераторов......................................... 188 10.2.3. distance........................................................ 190 10 2.4 advance.......................................................... 192 10.2.5. Базовый класс итераторов.........................................193 10.3. Класс allocator.......................................................196 10.4 Примитивы управления памятью.........................................198 10 4 1 construct.........................................................198 10 4.2. destroy......................................................... 199 10 4 3 uninitialized-copy............................................... 200 10.4.4 uninitialized-fill................................................202 10.4.5. uninit ialized_.fi ll_n......................................... 204 10 5. Временные буферы..................................................... 205 10 5 1. get.temporary_buffer....................................... . 206 105.2 return_iemporary_buffer........................................... 207 Глава 11. Неизменяющие алгоритмы...............................................208 11.1 /Iинейный поиск....................................................... 208 1111 find (поиск) ... .............................. ........ ... 208 1112 <ind_'f (поиск по условию)........................... .. 209 11.1 3 adj a cent _ find (поиск соседних)............................... 211 11.1.4 find_f 1 гst_of (поиск первого из)............................... 213 11 2. Сравнение подпоследовательностей..................................... 215 112 1. search (поиск вхождения диапазона) .............................. 215 11.2 2. f ind.end (поиск последнего вхождения диапазона) .................217 11 2.3 search п (поиск п последовательных элементов).................... 219 11 3. Подсчет элементов.................................................... 222 11.31 count (подсчет) ....................................................222 113.2 count it (подсчет но условию) ................................... 224
8 Оглавление 11.4 for .each (выполнение для каждого) .................................. 226 11.5. Сравнение двух диапазонов .......................................... 228 11.5.1. equa 1 (сравнение на равенство)................................. 228 11.5.2. тз smatch (поиск различия) ..................................... 230 11 5 3. lex 1 cograpnical_compare (лексикографическое сравнение) ........ 232 11 6 Минимум и максимум .....................................................234 116 1 min (определение меньшего из двух)................................ 234 11.6 2 max (определение большего из двух)............................... 235 11 6 3. mi n_element (поиск наименьшего в диапазоне).................... 236 11 6.4. max_element (поиск наибольшего в диапазоне).................... 237 Глава 12. Основные изменяющие алгоритмы........................................239 '2 1. Копирование диапазонов........................................... ... .239 12.1.1. сору (копирование) ...............................................239 12 1 2 copy_backward (копирование в обратном порядке).....................241 12.2 Перестановка элементов.................................................243 12.2.1. swap (обмен)......................................................243 12 2.2. iter swap (обмен по итераторам)................................244 12.2.3. swap,ranges (обмен диапазонов) .................................. 245 12.3 . I ransform (преобразование)......................................... 246 12 4. Замена элементов.................................................... 249 12.4.1 । eplace (замена) ................................................ 249 12.4 2. replace_if (замена по условию)....................................250 12.4.3. replace_copy (замена с копированием) .............................251 12.4.4 replace_copy_] f (замена с копированием по условию)............... 253 12.5 Заполнение диапазонов ................................................. 254 12 5 1. fill (заполнение).............................................. 254 12 5.2. fi 11_п (заполнение первых п).....................................255 12.5 3. generate (генерация)............................................. 256 12.5.4. generato_n (генерация первых п).................................. 256 12.6 . Удаление элементов ..................................................257 12.6.1 remove (удаление)................................................. 257 12.6.2 remove_i 1 (удаление по условию)...................................259 12.6.3. remove_copy (удаление с копированием) ........................... 260 12.6.4 remove_copy_if (удаление с копированием по условию)................262 12.6 5 unique (единственные)..............................................263 12.6.6. unique_copy (единственные с копированием).........................266 12.7 Алгоритмы перестановки ................................................268 12 7.1. reverse (переворачивание).........................................268 12.7 2. reverse_copy (переворачивание с копированием).................... 269 12.7.3 rotate (циклический сдвиг)........................ ... . 270 12 7 4 г о tate_copy (циклический сдвиге копированием) .................. 271 12.7.5 nex[ permutation (следующая перестановка)........................ .272 12 7.6 prevpe г mutation (предыдущая перестановка)....................... 274 12 8 Разбиения ............................................................. 276 12.81 par ti I ion (разбиение) .......................................... 276 12.8.2 stable_partition (стабильное разбиение)........................... 277 12 9 Случайные перестановки и выборки ...................................... 278 12 9 1 г andom_shuf Не (случайная перетасовка) .......................... 279 12 9.2 г andom sample (случайная выборка).......................... . 280 12.9.3 г indom_sample_n (случайная выборка из п)....................... 282 12 10 Обобщенные числовые алгоритмы....................................... 284 12 10 1 accumulate (накопление)...........................................284 12.10 2. J nner_product (скалярное произведение)..........................286
Оглавление 9 12.10.3. partial_sum (частичная сумма)................................... 287 12 10 4. adjacent-difference (разность соседних элементов)....... . ... 289 Глава 13. Сортировка и поиск.....................................................292 13.1 Сортировка диапазонов................................................... 292 13.1.1 sort (сортировка) .................................................. 293 13 12. stable_sort (стабильная сортировка)................................. 295 13 13 partial_sort (частичная сортировка) ............................... 298 13 1.4 partial_sort_copy (частичная сортировка с копированием) ............ 300 13 1.5 nth_element (n-й элемент)........................................... 302 13.1.6. is_sorted (является отсортированным) .............................. 304 13.2 Операции над отсортированными диапазонами............................... 305 13.2.1 Двоичный поиск...................................................... 305 13 2 1.1. binary_search (двоичный поиск)............................ 306 13 2.1.2. ]ower_bound (поиск первого возможного вхождения).......... 307 13.2.1 3. upper_bound (поиск последнего нужного элемента)........... 310 13.2.1 4. equal_range (поиск интервала возможного вхождения)........ 312 13.2.2. Слияние двух отсортированных диапазонов...................... .... 315 13.2.2.1. merge (слияние)........................................... 315 13.2.2.2. inplace_merge (слияние “на месте”)........................ 317 13.2.3. Операции над множествами на отсортированных диапазонах .............319 13.2.3 1. includes (проверка включения подмножества)................ 320 13.2.3.2. set.union (объединение множеств).......................... 322 13 2 3 3. set-intersection (пересечение множеств)................... 325 13 2 3.4 set_difference (разность множеств)......................... 328 13 2 3.5 set_symmetric_difference (симметрическая разность множеств) .. 331 13.3. Операции с кучами.......................................................334 13 3.1. make_heap (создание кучи) ......................................... 334 13 3.2. push_heap (добавление элемента в кучу)........................ ... . 335 13.3 3 pop_heap (извлечение из кучи) ...................................... 337 13.3.4 sort_heap (сортировка кучи)......................................... 339 13 3 5. is_heap (является ли кучей)........................................ 340 Глава 14. Классы итераторов......................................................342 14.1 . Итераторы вставки........'............................................ 342 14.1.1. front_insert_iterator.............................................. 342 14.1 2. back_insert_iterater...............................................345 14.1.3. insert-iterator.................................................... 347 14.2 Итераторы потоков....................................................... 351 14.2 1. istream_iterator .................................................. 351 14.2 2.ostream_iterator ................................................... 354 14.2.3 ]streambuf-iterator................................................. 356 14.2.4. ostreambuf-iterator .................................... ... .358 14.3 reverse_iterator........................................................ 360 И 4. raw_storage_iterator........................... . .... ..........365 Глава 15. Классы функциональных объектов.........................................368 15.1 Базовые классы функциональных объектов.................................. 368 15 1 1 unary_funct юп..................... .. ... 368 15 1 2. binary_function.................................................. 369 15 2 Арифметические операции............................................ ... 370 15.2 1. plus (сложение)................................................. . 370 15 2.2. minus (вычитание)... ................................ . . 371 15 2 3. mu] tip] ics (умножение) .......................................... 373
10 ____________________________Оглавление ______ 15 2.4 divides (деление)..................................................... 374 15 2 5. modiil1 is (получение остатка при делении) ...........................375 15.2.6 negate (изменение знака)............................................. 377 15.3. Сравнения............................................................. 378 15 3.1. equal_to (равно).................................................. 378 15.3.2. noi_equal_to (не равно)............................................ 379 15.3.3. less (меньше)...................................................... 380 15.3.4. greater (больше).................................................... 381 15.3.5. less_equal (меньше или равно)..................................... 383 15.3.6. greater_equal (больше или равно)......................................384 15 4. Логические операции.......................................................385 15.4.1. logical_and (логическое И).......................................... 386 15.4.2. log]cal_or (логическое ИЛИ)...........................................387 15.4.3. logical_not (логическое отрицание) ................................. 388 15 5 Тождественность и проекция............................................... 389 15.5 1. identity (тождественность)......................................... 389 15 5 2 projectlst .......................................................... 390 15 5.3 project 2na...........................................................392 15.5.4 . select 1st......................................................... 393 15.5.5 . select2nd.......................................................... 394 15.6. Специализированные функциональные объекты............................... 395 15.6.1. hash..................................................................395 15.6.2. subt ractive_rng......................................................396 15.7. Адаптеры функций-членов...................................................398 15.7.1. mem_fun_t ............................................................398 15.7.2. niem_fun_ref_t ...................................................... 400 15.7.3. mcm_iun1_t........................................................... 402 15.7.4. mem._fun1_ref_t.................................................. •• 404 15 7.5. const _niem_fun_t .................................................406 15.7.6. con3t_mem_fun_ref_t.................................................. 408 15.7.7 const_mem_fun1_t ..................................................... 410 15.7.8. constjriem_fuii1_ref_t............................................... 412 15.8. Другие адаптеры........................................................... 414 15.8 1. binder 1st .......................................................... 414 15 8.2.binder2nd..................................................... . 415 15 8 3. pointer_to_unary_function.............................................417 15.8.4 pointer_to_binary_function........................................... 418 15.8 5. unar y_negate.........................................................420 15.8 6 bi nary_negate ....................................................... 421 15.8.7. unary_compose ....................................................... 423 15 8.8. bi nar y_compose.................................................. .425 Глава 16. Классы контейнеров ......................................................427 16.1. Пос ледова!ельностп...................................................... 427 16.1.1. vector,(вектор)............................................. . 427 16 1.2 list (список)........................................................ .434 16.1 3 si] st (односвязный список)........................................... 442 16 1.4 deque (двусторонняя очередь) ....................................... 450 16 2 /Хссоциатпвные контейнеры............................................. ..456 16.2.1. set (множество)...................................................... 456 16.2 2. таи (отображение, ассоциативная последовательность)...................462 16.2 3. multiset (мультимножество, множество с повторяющимися элементами) . 470 16 2 4. mull пиар (множественное отображение, ассоциативная последовательность с повторяющимися элементами) ......... 176
Оглавление 1 1 16.2.5. nash_set (хешированное множество)....................... .... 482 16.2.6. hash_map (хешированная ассоциативная последовательность)..... 487 16.2.7. hash_multiset (хешированное мультимножество)..................493 16.2.8. hashjnultimap (хешированная ассоциативная последовательность с повторяющимися элементами)..........................................499 16.3. Адаптеры контейнеров.............................................. 505 16.3.1. stack (стек)..................................................505 16.3 2. queue (очередь)..............................................508 16.3.3. priority_queue (приоритетная очередь).........................510 Приложение А. Переносимость и стандартизация...............................515 Л.1. Изменения в языке.................................................. 516 All. Модель компиляции шаблонов.......................................516 А. 1.2. Параметры шаблона по умолчанию................................517 А. 1.3. Шаблоны-члены............................................... .518 А. 1.4. Частичная специализация...................................... 519 А. 1.5. Новые ключевые слова......................................... 522 А.2. Изменения в библиотеке............................................. 525 А.2.1. Аллокаторы.....................................................525 А.2.2. Адаптеры контейнера............................................526 А.2.3. Второстепенные изменения библиотеки............................527 А.З. Именование и компоновка.............................................528 Список литературы..........................................................531 Алфавитный указатель.......................................................533
Предисловие к русскому изданию Как понимать слова обобщенное программирование в названии этой книги? В оригинале на английском языке стоит generic programming, и толковый словарь дает для generic не- сколько объяснений. Из них к данному случаю подходит только такое: применимый к лю- бому индивидуальному из группы или класса, общий, неконкретный. Это толкование правильно и вполне понятно, хотя и представляет трудности для пере- вода. Помните, как у Пушкина: “Она казалась верный снимок Du сотте il faut... (Шиш- ков, прости: Не знаю, как перевести.)”? Нового варианта я предложить не смог, и остается лишь обсуждать установившийся термин. О чем же идет речь? Сейчас мы можем составлять наши программы так. чтобы разрабатываемый алгоритм был в принципе применим к любому типу данных, удовлет- воряющему определенным свойствам. Например, встретившийся тип данных может быть нобым арифметическим, — с данными этого типа нужно производить сложения и вычи- гания. сравнивать друг с другом для упорядочения, копировать их при выполнении опе- рации присваивания. Если требуются только такие действия, для нас становится несуще- ственным, с каким из стандартных типов мы имеем дело — с целыми числами или с пла- вающей точкой, с длинными или с короткими. Однако уже операция сравнения на равенство заставляет нас вводить различия в алгоритмы (как правило, числа с плаваю- щей точкой приходится сравнивать не с нулем, а с маленьким числом, с каким-то, как говорят, эпсилоном). Различия возрастают, когда приходится рассматривать “большие” целые или приближенные числа, записываемые в строках переменной длины. В силу этих особенностей писать обобщенные алгоритмы непросто, но соблазн велик В данной книге идея обобщенного программирования демонстрируется на вполне ре- альном и содержательном примере — на так называемой Стандартной библиотеке шабло- нов (STL — Standard Template Library), которая важной составной частью вошла в Меж- дународный стандарт языка C++. В тексте последовательно рассматриваются и разъясняются понятия и механизмы, не- обходимые для реализации шаблонов библиотеки. Теоретическое введение занимает при- мерно шестую часть книги. Затем идут два справочных раздела: раздел концепций, ис- пользуемых при создании библиотеки, и раздел алгоритмов и классов. Книга будет необходима программистам, активно использующим язык C++ ь библио- теку STL, а также тем, кто хочет разрабатывать собственные шаблоны. И. Романовский, профессор С.-Петербургского государственного университета
Моим родителям, Норману и В ильме Остерн, посвящается Предисловие Эта книга не по объектно-ориентированному программированию. Вам это может показаться странным. Вы, вероятно, нашли эту книгу в разделе “C++” книжного магазина и, наверное, слышали, что слова “объектно-ориентирован- ный” и “C++” употребляются как синонимы. Однако язык C++ может использовать- ся и иначе. C++ поддерживает несколько фундаментально различающихся парадигм, в том числе одну из наиболее новых и наименее знакомых — обобщенное программи- рование (generic programming). Обобщенное программирование, как и почти все новые идеи, уже имеет длитель- ную историю. Первые исследовательские статьи по обобщенному программированию были опубликованы почти 25 лет назад, а первые экспериментальные обобщенные библиотеки были написаны не на C++, а на языках Ada [MS89a, MS89b] и Scheme f KMS88]. Однако обобщенное программирование и сегодня не является широко рас- пространенным — по нему даже не существует учебника. Первым примером обобщенного программирования, использовавшегося не толь- ко в исследовательских группах, стала STL — стандартная библиотека шаблонов C++ (C++ Standard Template Library). Стандартная библиотека шаблонов, созданная ра- ботавшим в то время в Hewlett-Packard Laboratories Александром Степановым (Alexander Stepanov) и Менг Ли (Meng Lee), была принята в 1994 году в качестве части стандартной библиотеки C++. В том же году появилась свободно доступная ре- ализация, известная под названием “реализация HP” [SL95], которая продемонстри- ровала возможности STL. После того как стандартная библиотека шаблонов стала частью стандарта С+ +, сообщество пользователей языка немедленно признало ее как библиотеку высокока- чественных и эффективных контейнерных классов. Известно, что легче всего разо- ораться в том, что знакомо, а любой программист, пишущий на C++, знаком с кон- тейнерными классами. В любой нетривиальной программе нужно использовать на- ооры объектов — и почти каждый программист в C++ написал класс, реализующий строки, векторы или списки. Библиотеки контейнерных классов появились еще на заре истории C++, а когда в язык были введены классы “шаблонов” (параметризованных типов), одним из пер- вых способов их использования стали параметризованные классы контейнеров (что, собственно, и было одной из главных причин появления шаблонов). Многие постав- щики, включая Borland, Microsoft, Rogue Wave n IBM, написали свои собственные библиотеки, в которых имелся Аггау<Т> или его эквивалент.
14 Предисловие В силу широкого знакомства с контейнерными классами, STL показалась лишь еще одной библиотекой таких классов. Это отвлекло внимание от черт, определявших уни- кальность STL. STL — большой, расширяемый набор эффективных, обобщенных, взаимодейст- вующих друг с другом программных компонент. Она содержит множество базовых алгоритмов и структур данных, известных компьютерной науке, и написана таким образом, что алгоритмы и структуры данных отделены друг от друга. Правильней рас- сматривать STL в качестве библиотеки обобщенных алгоритмов, а не классов контей- неров; контейнеры в ней существуют постольку, поскольку алгоритмы должны с чем- го работать. В программах можно применять существующие алгоритмы STL так же, как кон- тейнеры. Например, вы можете использовать обобщенную функцию sort из STLточ- но так же, как использовали бы функцию qsort из стандартной библиотеки Си (хотя sort, несомненно, более проста, гибка, безопасна и эффективна). В нескольких кни- гах, включая Пособие и руководство по STL Дэвида Мюссера (David Musser) и Ату- та Саини (Atul Saini) [MS96] и Введение в стандартную библиотеку шаблонов для программиста на C+ + Марка Нельсона (Mark Nelson) [Nel95], рассказывается о та- ком способе работы с STL. Уже это весьма полезно. Всегда предпочтительнее повторно использовать код, а не писать его заново, и вы можете свободно применять существующие алгоритмы STL в собственных программах. Но, тем не менее, это лишь один из аспектов STL. STL является расширяемой: ее различные компоненты могут взаимодействовать друг с другом, а также с компонентами, которые вы напишете сами. Эффективное исполь- зование STL подразумевает ее развитие. Обобщенное программирование STL — не только набор полезных компонент. Другой ее аспект, менее известный и понятный, заключается в том, что она представляет собой формальную иерархию абстрактных требований, описывающих программные компоненты. Компоненты STL расширяемы, способны взаимодействовать друг с другом. Вы можете добавлять новые алгоритмы и новые контейнеры и при этом быть уверены, что новый и старый код могут использоваться совместно. Причиной такой гибкости является тот факт, что все компоненты STL написаны в соответствии с точно определенными специфи- кациями. Важнейшим достижением в информатике было открытие новых видов абстракций. Одной из ключевых абстракций, поддерживаемых всеми современными языками про- граммирования. является подпрограмма (процедура или функция — в разных язы- ках употребляется разная терминология). Другая абстракция, которую поддержива- ет C++, — абстрактные типы данных. В C++ можно определить новый тип данных, а также набор базовых операций над ним. Сочетание кода и данных образует абстрактный тип данных, операции с которым осуществляются через строго определенный интерфейс. Подпрограммы являются важной абстракцией потому, что при их использовании отсутствует зависимость от конкретной реализации (и даже знание о ней); вы можете использовать абстрактный тип данных — манипулируя значениями и даже создавая их -- и при этом не зависеть от реального представления данных. Важен только интерфейс.
Предисловие 15 C++ поддерживает объектно-ориентированное программирование [Воо94, Меу97], использующее иерархии полиморфных типов данных, связанных отношением насле- дования. По сравнению с моделями абстрактных типов данных, объектно-ориенти- рованное программирование включает в себя дополнительный логический уровень, делая таким образом еще один шаг в абстрагировании. При некоторых обстоятельст- вах можно ссылаться на значение и манипулировать им, не указывая точного типа. Можно написать одну-единственную функцию, которая будет работать с целым на- бором различных типов в рамках иерархии наследования. Обобщенное программирование также определяет новый вид абстракции. Ключе- вая абстракция обобщенного программирования не настолько очевидна, как более ранние абстракции типа подпрограммы, класса или модуля: она представляет собой набор требований к типам данных. Эту абстракцию трудно постичь, поскольку она не привязана к специфическим свойствам языка C++. В C++ (и в любом другом со- временном языке программирования) нет ключевого слова для объявления набора абстрактных требований. Освоение абстракции, которая поначалу может показаться до разочарования не- определенной, достигается нелегко, однако взамен обобщенное программирование предоставляет беспрецедентный уровень гибкости. Крайне важно, что высокий уро- вень абстракции доступен без потери эффективности. Обобщенное программирова- ние, в отличие от объектно-ориентированного, не требует вызовов функций с исполь- зованием дополнительных уровней косвенных вызовов. Вы можете записать в обоб- щенной форме алгоритм, который так же эффективен, как и написанный для конкретного типа данных. Обобщенный алгоритм записывается путем абстрагирования алгоритмов, работа- ющих с конкретными типами и конкретными структурами данных, так что их мож- но применять к аргументам максимально общего типа. Это означает, что обобщен- ный алгоритм состоит из двух частей: конкретных инструкций, описывающих шаги алгоритма, и набора требований, которым типы его аргументов должны точно соответствовать. Главным новшеством STL является тот факт, что эти требования к типам могут 'быть определены и систематизированы. То есть можно определить набор абстракт- ных концепций и сказать, что тип соответствует одной из них, если он удовлетворяет ооозначенным требованиям. Наличие концепций очень важно, потому что большин- ство предположений о типах, используемых в алгоритмах, может быть выражено как в терминах соответствия концепциям, так и в терминах отношений между различны- ми концепциями. Вдобавок эти концепции образуют строгую иерархию — некоторое подобие наследования в традиционном объектно-ориентированном программирова- нии, — но совершенно абстрактную. Иерархия концепций и есть концептуальная структура STL. Это самая важная часть SI L, и именно благодаря ей становится возможным повторное использование алго- ритмов и взаимодействие объектов. Концептуальная структура имела бы самостоя- тельное значение в качестве формальной систематики программных компонент, даже не будучи воплощена в коде. В STL имеются, конечно, конкретные структуры дан- ных, типа pai г и list, но для их эффективного использования необходимо понимать концептуальную структуру, на которой они базируются. Определение абстрактных концепций и написание алгоритмов и структур данных в терминах абстрактных концепций — сущность обобщенного программирования.
16 Предисловие Как читать эту книгу Эта книга описывает стандартную библиотеку шаблонов как библиотеку абстракт- ных концепций. В ней определяются фундаментальные концепции и абстракции STL, а также показывается, что именно подразумевается под формулировками “тип моделирует ту или иную концепцию” или “алгоритм записывается в терминах ип- юрфейса концепции”. Обсуждаются классы и алгоритмы, входящие в базовую часть STL, и объясняется, как написать свои собственные, совместимые с STL, классы и алгоритмы и в каком случае это может понадобиться. И наконец, в книгу включено полное справочное руководство по концепциям, классам и алгоритмам STL. Всем следует прочитать первую часть, которая знакомит с основными идеями STL и обобщенного программирования. В ней рассказывается, как использовать и писать обобщенные алгоритмы, а также объясняется смысл понятия обобщенного алгорит- ма. Под обобщенностью подразумевается не просто возможность работать с множе- ством типов данных. Исследование идеи обобщенного алгоритма естественным образом приводит к та- ким ключевым понятиям, как концепция, моделирование и развитие, — понятиям, на- столько же существенным для обобщенного программирования, как полиморфизм и наследование для объектно-ориентированного. Параллельно, обобщенные алгорит- мы работы с одномерными диапазонами приводят к фундаментальным концепциям STL: итераторам, контейнерам и функциональным объектам. В первой части вы знакомитесь с системой обозначений и типографскими согла- шениями, принятыми в книге: терминологией моделирования и развития, асиммет- ричной нотацией диапазонов и специальными шрифтовыми выделениями для назва- ний концепций. В STL определено много концепций, причем некоторые из них отличаются друг от друга лишь незначительными техническими деталями. Первая часть является обзор- ной, и в ней обсуждаются концепции STL в общем виде. Вторая часть — детальное справочное руководство, в котором точно определена каждая концепция STL. Воз- можно, вам не захочется читать вторую часть от начала до конца. Полезнее было бы возвращаться к определению той или иной концепции по мере необходимости (в част- ности, это может понадобиться всякий раз, когда вы пишете новый тип, соответству- ющий той или иной концепции STL). Третья часть также является справочным руководством. В ней описаны все пре- допределенные алгоритмы и классы STL. Она в значительной степени опирается на определения концепций из второй части. Все алгоритмы STL и почти все конкрет- ные типы являются шаблонами, и каждый параметр шаблона может быть охаракте- ризован как модель той или иной концепции. Определения третьей части ссылаются на соответствующие разделы второй. В идеале книга должна бы па бы закончиться третьей частью. К сожалению, реаль- ная действительность требует еще одного раздела — приложения, в котором обсуж- даются вопросы переносимости. В первой версии STL переносимость не представля- ла проблемы, потому что существовала только одна реализация. Сейчас существует множество реализаций и языка и библиотеки, и каждый, кого волнует проблема пе- реносимости, должен знать различия между ними. Устаревшая реализация HP все еще доступна по анонимному ftp по адресу ftp //butler hpl hp com, но уже не поддерживается. Более новая бесплатная реали- зация, созданная в Silicon Graphics Computer Systems (SGI), доступна по адресу
Предисловие 17 http //www sgi com/Technology/STL, а адаптации SGI STL для различных компьюте- ров, поддерживаемые Борисом Фомичевым (Boris Fomitchev), находятся по адресу http //www metabyte com/-f bp/stl/. Наконец, есть несколько различных коммерческих реализаций STL Если вы пишете реальные программы, недостаточно иметь представление о теоре- тической структуре библиотеки — необходимо понимать, чем различаются разные реализации STL и разные компиляторы C+ + . Эти скучные, но необходимые дечали и составляют содержание приложения А. Кому стоит читать эту книгу Несмотря на то что данная книга в значительной степени посвящена алгоритмам, на- писанным на C++, она, тем не менее, не является ни введением в алгоритмы, ни учеб- ником по C++. Однако в ней объясняются некоторые малоизвестные аспекты и того и другого. В частности, поскольку использование шаблонов в STL отличается от дру- гих программ на C++, здесь рассматриваются некоторые более изощренные приемы программирования с применением шаблонов. Эта книга не должна быть вашей пер- вой книгой по C++ и первым знакомством с анализом алгоритмов. Вы уже должны знать, как пишутся типовые программы на C++ и что означает нотация O(N). В качестве стандартных руководств по алгоритмам и структурам данных можно привести книгу Искусство программирования для ЭВМ Дональда Кнута (Donald Knuth) [Knu97, Knu98a, Knu98b] и Введение в алгоритмы Кормена (Сопнеп), Лсй- зерсона (Leiscrson) и Ривеста (Rivest) [CLR90]. Два лучших руководства по C++ — это Язык программирования C+ + Бьёрна Страуструпа (Bjarne Stroustrup) [Str97] и Язык программирования C++: Вводный курс Стенли Липпмана (Stanley Lippman) и Жози Лажойе (Josee Lajoie) [LL98]. Как эта книга появилась на свет Я начал работать в компьютерной группе фирмы Silicon Graphics Computer Systems (SGI) в 1996 году. Алекс Степанов (Alex Stepanov) пришел в SGI из HP нескольки- ми месяцами раньше. В то время компилятор C++ фирмы SGI не содержал реализа- ции стандартной библиотеки шаблонов. Используя в качестве базы исходную реали- зацию HP, мы с Алексом и Гансом Боэмом (Hans Boehm) написали реализацию STL, которая поставлялась с версией 7.1 (и последующими релизами) компилятора MIPSpro фирмы SGI. Стандартная библиотека шаблонов SGI [Aus97] содержала много новых и рас- ширенных возможностей, таких как эффективное и безопасное, с точки зрения мультпнитевости, управление памятью, хеш-таблицы и алгоритмические улучше- ния. Если бы улучшения оставались собственностью фирмы, от них пользователям SGI не было бы никакого проку, поэтому SGI STL была сделана бесплатной. Она вместе с документацией распространяется через Интернет и доступна по адресу Ь'Чр //www sgi com/Tecnnology/STL. В документации, представленной на веб-страницах, концептуальная структура STL занимает основное место В Heii описываются абстрактные концепции, составляющие ее концептуальную структуру, а алгоритмы и структуры данных STL излагаются в терминах абстрактных концепций. Мы получали множество просьб о создании более
18 Предисловие обширной документации, и предлагаемая книга — ответ на эти просьбы. Справочные разделы книги, части II и III, выросли из веб-страниц, посвященных SGI STL. Веб-страницы написаны для фирмы SGI, ей же принадлежат права на них. Я ис- пользую их с любезного разрешения моего руководства. Благодарности Первая и наиважнейшая: эта книга, вероятно, никогда бы не появилась на свет без работ Алекса Степанова (Alex Stepanov). Алекс принимал участие в написании кни- ги на всех этапах: он привел меня в SGI, он научил меня почти всему, что я знаю в обобщенном программировании, он принимал участие в разработке SGI STL и веб- страниц, посвященных ей, и посоветовал мне написать книгу на основе этих матери- алов. Я благодарен Алексу за содействие и поддержку. Я также хочу поблагодарить Бьёрна Страуструпа (Bjarne Stroustrup) и Энди Кё- нига (Andy Koenig) за помощь в постижении C++, а также Дэйва Мюссера (Dave Musser) за его многочисленные работы (некоторые из них включены в библиографию) по обобщенному программированию, STL и вклад в эту книгу. Дэйв использовал ран- нюю версию веб-страниц, посвященных SGI STL, на своих курсах, и в результате веб- страницы были значительно усовершенствованы благодаря комментариям, получен- ным от него самого и его студентов. Книга была существенно улучшена вследствие замечаний рецензентов, включая Тома Беккера (Tom Becker), Стива Клэмеджа (Steve Clamage), Джея Гишера (Jay Gis- cher), Брайана Кернигана (Brian Kernighan), Джека Кирмана (Jak Kirman), Энди Кёнига (Andy Koenig), Анжелики Лангер (Angelika Langer), Дэйва Мюссера (Dave Musser), Сибиллы Шуп (Sibylle Schupp) и Алекса Степанова (Alex Stepanov), кото- рые читали ранние версии. С их помощью книга стала не столь расплывчата и содер- жит гораздо меньше ошибок. Все оставшиеся погрешности лежат только на моей со- вести. Несколько ошибок, обнаруженных в первом издании, были исправлены, и я хочу поблагодарить Сэма Бредшера (Sam Bradsher), Брюса Экеля (Bruce Eckel), Ги Гасу- апа (Guy Gascoigne), Джона Джеггера (Jon Jagger), Нейта Льюиса (Nate Lewis), Шо- на Д. Пауца (Shawn D. Pautz) и Джорджа Рилли (George Reilly), которые указали на них. Я также весьма обязан сотрудникам Addison-Wesley: Джону Фулеру (John Fuller), Майку Хендриксону (Mike Hendrickson), Марине Ланг (Marina Lang) и Женевьеве Раевски (Genevieve Rajewski), которые были моими проводниками в процессе напи- сания, и Карене Тонгиш (Karen Tongish) — за ее тщательное редактирование. Наконец, я благодарен моей невесте Жанет Лафлер (Janet Lafler) за любовь, под- держку и терпение в течение многих вечеров и выходных дней, проведенных мной за письменным столом. Наши ко гы, Рэнди (Randy) и Оливер (Oliver), пытались помочь, прогуливаясь по клавиатх’ре, но в окончательной версии пришлось убрать большую часть их вклада.
Часть I Введение в обобщенное программирование
1 Обзор STL Программирование в значительной степени связано с алгоритмами и структурами данных. Неважно, какую программу вы пишете, одна из ее обязательных задач — организация и манипулирование данными. Чтобы нс писать один и тот же алгоритм каждый раз, когда он вам требуется в слег- ка различающихся контекстах, стандартная библиотека шаблонов (Standard Template Library — STL) C++ позволяет повторно использовать существующие алгоритмы. Алгоритмы STL обобщены — они не привязаны к какой-либо конкретной структуре данных или к типу объекта, — но при этом столь же эффективны, как если бы вы на- писали их специально для вашей задачи. Кроме того, STL расширяема. Компоненты STL взаимодействуют с компонентами, которые вы пишете, точно так же, как послед- ние взаимодействуют друг с другом. 1.1. Простой пример Чтобы понять, как работает такая система, проанализируем простую программу, ко- торая использует несколько элементов STL. В качестве примера рассмотрим упро- щенную версию утилиты sort операционной системы UNIX: данная программа чита- ет строки текста из стандартного потока ввода и записывает их в отсортированном виде в стандартный ноток вывода. Задача состоит из трех частей: чтение текста из стандартного ввода и разбиение его на отдельные строчки, сортировка строчек и запись в стандартный вывод Ч Каждая часть является стандартным алгоритмом, и, используя STL, объединить их чрезвы- чайно просто: in г main() ve;tor<string> V, st ring rmp '1см i iijii с 1|>ока (string) разбиваемся на строчки (lines) — При меч ped
1 1. Простой пример 21 while (get 1ine(cin tmo)) V push_back(nnp). sort(V begin(), V end()), copy(V begin(), V end(), ostream_iterator<string>(cout \n')), Мы создаем пустой vector (простейший и наиболее полезный контейнерный класс STL), построчно добавляем в него текст, затем сортируем текст и копируем его со- держимое на стандартный вывод. По умолчанию sort располагает свои аргументы в порядке возрастания, но достаточно написать: surt(V begin(). V end(), greatersstnng>()). и строки будут отсортированы по убыванию. Последняя строка программы довольно интересна. Использованным в ней алго- ритмом STL — cqpy — вы бы воспользовались для копирования элементов из одного массива в другой. Его аргументы — как, собственно, аргументы всех алгоритмов STL — итераторы, а итератор (iterator) может быть чем угодно, начиная с банального ука- зателя Си и заканчивая, как в нашем случае, оберткой (wrapper) потока вывода. Мож- но пользоваться сору для копирования элементов из всего, что похоже на источник, во что угодно, напоминающее приемник (во второй главе мы определим это точнее), именно поэтому им можно воспользоваться и для вывода. Вы можете использовать сору и для ввода, если у вас есть подходящий для этого итератор Именно, вам нужен итератор, возвращающий по одной строчке из пото- ка ввода при каждом обращении. В STL пет такого итератора, но его легко написать и затем использовать не только с сору, но и со многими другими алгоритмами STL. class line_i1eralor { ist г earn* in siring line bool is_valid. void read() { if (* In) gelline(*in, line), is_valid = (*in) 9 true false, public typedef input_^tera1or_tag iteral or_category, "ypedef string value_type, t>neoef ptrdiff_t difference_type typedef const string* pointer, typedef const string& reference line_itorator() in(&cin) is_valid(fa]se) {} 1ine_iierator(istream& s) in(&s) { read(), } reference ooerator*() const { return line, } pointer operator->() const { return &line }
22 Глава 1. Обзор STL • ine_i1 enter operator+4 () { read() f eturn *tnis iine_iterator operater++(int) { I’j'it-iterator tmp = *this, rcad() return tmp bool operatoi—(const ]ine_iterator& i) const { return (tn -= i in && is_valid == i is_valid) || (is.valid == false && i is.valid =- false). boo: operator'-(const line_iterator& i) const { return ’(*this -- i) He огорчайтесь, если вы не поняли, почему все это включено в класс line.iterator; некоторые фрагменты необходимы исключительно по техническим причинам (пол- ный перечень требований излагается в разделе 7.2). Теперь, когда у нас есть этот класс, наша программа сортировки становится даже проще. Мы можем обойтись без цикла ввода и создать vector прямо из диапазона итераторов: КЧ ПкИГ.О { L1nc_;1erator iler(cin) li ie_jterator end.of.lile, vector<stnng> V(iter, end.of.file). sort(V begin() V end()). copy(V Degtn() V end(), ostream_iterator<string>(cout ’\n”)) Альтернативный метод Продемонстрированный способ чтения и сортировки строчек привлекает своей про- сютой, но является ли он лучшим? Возможно, нет. Проблема в том, что сортировка диапазона обьектов подразумевает выполнение очень большого числа присваива- ний. Так как string — большая и сложная структура данных, операция, когда одной строке присваивается другая, вероятно, окажется довольно медленной. В этом слу- чае следует сортировать не сами строчки, а указатели на них. (Реализация, которой вы располагаете, может использовать специальные трюки для ускорения присваива- ния строк, а может нс использовать. В любом случае не следует слишком полагаться на специфические детали.) Если в вашей программе используется большое количество строк, лучше совсем не применять класс string. Зачастую оказывается более эффективным собрать все строки в одну “таблицу строк” и, когда вам нужно обратиться к конкретной строке, указывать на строки таблицы.
1.1. Простой пример 23 Подобная техника немного сложнее, чем просто использование vector<stnng>, но STL прекрасно с этим справляется. Вместо одного вектора для хранения строк нам понадобятся два: один — для таблицы строк и другой — для указателей на строки в таб- лице. Сначала построим таблицу строк, а затем vector строчек. Принимая во внима- ние способы работы большинства алгоритмов STL, удобней представить каждую строчку в виде пары итераторов в строковой таблице: один — указывающий на пер- вый символ строчки и другой — указывающий за ее конец (за пос 1едний символ) Так как строчка представляется в виде пары итераторов, мы должны указать алго- ритму sort, как сравнивать два таких элемента, поскольку для сравнения теперь не чьзя использовать оператор <. Здесь нет ничего сложного. Мы уже знаем о существова- нии версии sort, у которой есть дополнительный аргумент, как раз именно потому, что часто требуется сравнивать элементы особым образом. Нужно только написать (рункционалъный объект (function object) сравнения, который будет передан sort в ка- честве аргумента. Кроме того, мы можем написать еще один функциональный объект, который! поможет нам с выводом: struck strtab_cmp t'/pedef vector<char> iterator strtab_iterator bool operator()(const pair<strtab_iterator, strtab_iterator>& x const pair<strtab_iterator, strtab_iterator>& y) const { return lexicographical_compare(x first, x second, у first, у second) struct strtab_print { ostream& out, srrrao_print(ostream& os) out(os) {} typedef vector<char> iterator strtab_iterator void operator()(const pair<strtab_iterator, strtab_iterator>& s) const { copy(s first, s second, ostream_iterator<char>(out)), Hit rnain() f i ventor<char> strtab // создание строковой таблицы fnar с, while (cin get(c)) { strtab push_back(c), // разбиение строковой таблицы настрочки (lines) typedef vector<char> iterator strtab_iterator, v«~^ior<pair<strtab_iterator, strtab_iterator> > lines. strtab_iitrator start = strtab begin()
24 Глава 1. Обзор STL wnit<- (start ' = strtab end()) { strtab_i1 orator next = find(start strtab end(). \n ), if (next '= strtab end()) ++next, lines pLish_back(make_pair(start next)), start = next, } // сортировка вектора строчек sort(lines begin() lines end(), strtab_cmp()), // запись строчек в стандартный вывод for_each(]ines begin() lines end() strtab_print(cout)) 1.2. Заключение I la этих двух простых примерах мы рассмотрели основы STL. Опа содержит базовые алгоритмы, например sort, find и lexicographical compare; итераторы, например istream_iterator и ostream_iterator; контейнеры, например vector, и функциональ- ные объекты, например less и greater. Кроме того, эти примеры иллюстрируют некоторые важнейшие аспекты исполь- зования STL. • Чтобы использовать STL, нужно расширять ее. В обоих наших примерах мы в конце концов написали наши собственные компоненты STL: новый итератор в первом примере и два новых функциональных объекта — во втором. (Можно было даже пойти дальше: вместо того чтобы писать цикл разбора таблицы на от- дельные строчки, написать для этих целей обобщенный итератор, занимающий- ся синтаксическим разбором.) • Алгоритмы STL отделены от контейнеров. Добавление алгоритма не требует пе- реписывания контейнера, и вы можете совмещать контейнеры и алгоритмы как угодно. При наличии N различных алгоритмов и М различных классов контей- неров вам не придется поддерживать 7V* Л/отдельных реализаций. • STL расширяема и настраиваема без использования наследования. Новые итера- торы, например line_iterator, и новые функциональные объекты типа strtab.cmp не обязаны являться наследниками некоторого специального базового класса. Они должны лишь удовлетворять набору абстрактных требований. При расши- рении STL вы не перегружены множеством нюансов реализации наследования и вам не приходится бороться с “хрупким базовым классом”. • Абстракция не означает потери эффективности. Обобщенные алгоритмы типа sort и сору и обобщенные контейнеры типа vector столь же эффективны, как если бы они были написаны специально для какого-то конкретного типа дан- ных. STL обеспечивает новый уровень осмысления программирования, главные инстру- менты которого — алгоритмы и абстрактные наборы требований. Эти абстрактные |ребования являются основой STL, и именно им посвящена книга.
2 Алгоритмы и диапазоны В первой главе мне пришлось слегка слукавить. Я показал, насколько просто расши- рить STL, написав код, который взаимодействует с существующими компонентами STL. Это действительно несложно, но только если вам известны требования, кото- рым нужно удовлетворить. Знание того факта, что STL и алгоритмы определены в тер- минах итераторов, бесполезно, если вы не знаете, что такое итератор. В этой главе вводится важная идея алгоритма, работающего с линейным диапазо- ном (linear range) элементов — одномерной коллекцией, в которой есть первый эле- мент и последний, причем у каждого элемента (за исключением последнего) имеется следующий за ним, и можно пошагово дойти от первого до последнего элемента. Та- кие алгоритмы составляют ядро STL. Семейство концепций итераторов, которые также представлены в этой главе, тес- но связано с алгоритмами на диапазонах. Итераторы являются наиболее важным нов- шеством в STL, именно они позволяют отделить алгоритмы от структур данных, с ко- торыми эти алгоритмы работают. Другие части STL — функциональные объекты (гла- ва4) и контейнеры (глава 5) — проще всего понять, используя алгоритмы и итераторы Мы будем обсуждать идеи, стоящие за алгоритмами и итераторами, и эти идеи ис- пользуются для иллюстрации концепций (concepts), моделирования (modeling) и раз- вития концепций (refinement). Даже если вы уже знакомы с итераторами и обобщен- ными алгоритмами, вам стоит прочесть эту главу, поскольку в ней вводится система обозначений, используемая в оставшейся части книги. Эта глава является лишь введением в алгоритмы и итераторы: подробная инфор- мация вынесена в справочный раздел книги. Концепции итераторов документирова- ны в главе 7, а алгоритмы STL — начиная с главы 11 по главу 13 включительно. 2.1. Линейный поиск Линейный поиск (linear search), или последовательный поиск, если пользоваться тер- кинологией Кнута [Knu98bj, — один из базовых алгоритмов. Он заключается в на- хождении заданного значения в линейном наборе элементов. Если элементы отсор- тированы или организованы каким-либо другим специальным образом, мы можем
26 Глава 2. Алгоритмы и диапазоны воспользоваться более изощренным или специализированным алгоритмом Линей- ный поиск - самый общий алгоритм поиска, и в нем делается меньше всего предпо- ложений о структуре данных, в которой идет поиск. 2.1.1. Линейный поиск в Си Прежде чем мы увидим, как написать линейный поиск в виде обобщенного алгорит- ма (generic algorithm), взглянем на конкретный знакомый пример: функцию strenr из стандартной библиотеки Си. Ее определение выглядит так: соаг * strehr(char* s, int c) Она ищет символ с в строке s. Вот пример возможной реализации: cJ'ar* st г ch г (char* s, int с) t grille >s ’= \0 && *s ' = c) r+s ft'iirn *s == c 9 s (char*) 0, Идея линейного поиска проста: требуемое значение ищется в линейной последова- тельности элементов пошаговым проходом всей последовательности от начала до кон- ца с проверкой каждого элемента. Если текущий элемент равен искомому значению, алгоритм возвращает позицию текущего элемента в последовательности. В против- ном случае — переходит к следующему элементу. В случае отсутствия следующего элемента (то есть при достижении конца последовательности) функция как-то дает знать, что поиск завершился неудачно. Любая реализация линейного поиска должна ответить на следующие вопросы. • Каким образом мы задаем последовательность, в которой идет поиск? • Каким образом мы задаем позицию в последовательности? (Это нужно для того, чтобы как-то задать “текущий элемент” и возвратить найденную позицию, если поиск увенчался успехом.) • Каким образом мы переходим к следующему элементу? • Как мы узнаем, что достигли конца последовательности? • Какое значение мы возвращаем в случае неудачи поиска? Ввиду того что strchr написана на Си, последовательность, где осуществляется по- иск, является строкой — массивом символов, который заканчивается нулем. Мы за- лаем строку, передавая функции st г ch г указатель на ее первый символ; функция st г ch» знает, когда она достигла конца строки, потому что последним стоит нулевой символ. Естественно, позиция внутри строки представлена указателем; мы возвращаем ука- затель на первый элемент, который равен с, или нулевой указатель, если такой эле- мент отсутствует. Эта функция, конечно, полезна, но она не столь универсальна, как хотелось бы. Отчасти проблема заключается в том, что strehr может быть использована только для поиска char в массиве char (но не для того, чтобы искать unsigned char или wchar t) Другая проблема, не связанная с именем типа, состоит в том. что интерфейс ограни- чивает st rch г поиском только по одной конкретной структуре данных.
2.1 Линейный поиск 27 Дело в том, что st rch г требует, чтобы ее аргумент был указателем на массив симво- лов, заканчивающихся нулем, а это требование не настолько общо, как может пока- заться. Действительно, в Си часто используется представление строк в'виде масси- вов символов, заканчивающихся нулем, но даже в Си не менее важны и другие пред- ставления. Например, как может быть представлена подстрока? Сама по себе подстрока массива, заканчивающегося нулем, не является массивом, заканчивающим- ся нулем, и поэтому должна быть представлена иначе. Рассмотрим два очевидных способа модификации интерфейса s t rch г. Один состо- ит в том, чтобы предоставить целочисленный счетчик: искать в первых п символах начиная с s. Другой способ — предоставление указателя на конец (pointer to the end) массива, в котором производится поиск, — более удобный. Именно эту форму мы и ис- пользуем для f ind1 — обобщения st rch г. Имеется несколько соображений, почему указатель на конец последовательности удобнее целочисленного счетчика. Во-первых, мы, скорее всего, организуем поиск именно так; инкрементировать указатель проще, чем делать отдельный счетчик Во-вторых, такой подход более последователен. Начало и конец диапазона задаются одинаковым образом; то же самое можно сказать и о возвращаемом значении. Это упрощает использование результата одной операции поиска в качестве аргумента другой. Третье, и наиболее важное, соображение понадобится нам чуть позже, когда мы еще более обобщим алгоритм find 1. char* find1(char* first char* last, int c) { while (firsl ’= last && *first ’= c) ++first, return first, } В этой реализации f ind1 использует обычный для Си цикл по массиву, применяя ука- затель, выходящий за конец (pointer beyond the end) массива в условии завершения. Цикл в f indi прекращается, когда f i rst становится равен last, при этом сам last ни- когда не разыменовывается. Таким образом, f ind1 ищет среди элементов, на которые указывают все указатели от f i rst до last, но не включая последний. Например, для поиска по всему массиву символов можно написать. char A[N] char* result - find1(A, A + N, c), if (result == A + N) { // неудачный поиск t else { // поиск завершился успешно } Указатель А + N используется для указания конца массива, все элементы которого на- ходятся в диапазоне от А[ 0] до A[N - 1 ]. К моменту достижения А + N мы уже прошли по всем элементам А и можем прекратить цикл. Это поднимает важный технический вопрос. Как правило, Си очень требовате- лен к операциям с указателями. Так, невозможно выполнить сравнение р < q, если р
28 Глава 2 Алгоритмы и диапазоны и q не указывают на элементы одного и того же массива. Нельзя также выполнять арифметические операции с недопустимыми (invalid) указателями, например пу- левыми. Почему же в нашем примере мы используем, казалось бы, недопустимый указатель А + N? В Си существует специальное правило (есть оно и в C++), введенное именно для того, чтобы работал такой код, как f ind 1. Обычно параметр цикла пробегает значения от 0 до N - 1, и поэтому указатели “за конец” вроде А + N являются допустимыми (valid) в Си, но только для некоторых специальных целей. Каждый массив в Си имеет один указатель, указывающий за его конец. Его нельзя разыменовать, поскольку в действи- тельности он ни на что не указывает, и к нему не применяется операция инкремента, потому что каждый массив имеет только один указатель за конец, по его можно ис- пользовать для сравнения и в арифметических операциях над указателями. (В проти- воположность этому массивы не имеют указателя “перед началом”; выражение А - 1 недопустимо. Иногда это работает, но может привести и к необратимым последствиям.) Поэтому в Си есть три вида указателей: обычные допустимые указатели, типа &А[ 0 ], которые можно разыменовывать, недопустимые указатели, типа NULL {сингулярные указатели) и указатели на элемент, следующий за последним (указатели за конец), которые нельзя разыменовывать, но можно использовать в арифметике указателей. 2.1.2. Диапазоны В алгоритме find1 мы обращаемся при поиске ко всем указателям от first до last, за вычетом последнего. Подобный интерфейс встречается в ЗТЬтак часто, что для него имеется специальное обозначение — диапазон (range); диапазон [f i rst. last) состоит из всех указателей от first до last, не включая last. Это обозначение пришло из ма- тематики. Асимметричная форма записи используется для акцентирования того фак- та, что [first, last) является полуоткрытым интервалом, который включает в себя Г} rst, но не last. Иногда [fi rst, last) означает указатели f i rst,..., last-1, а иногда — элементы *f i rst, .., ^(last-1). Как правило, из контекста попятно, что имеется в виду, а в случае необ- ходимости их различают с помощью терминов диапазон указателей +) и диапазон эле- ментов. Диапазон [f i rst, last) является допустимым, если все указатели [ f i rst, last) мож- но разыменовать и если last достижим из first, то есть если инкрементируя first конечное число раз, мы попадем в last. Таким образом, например, диапазон [A, A+N) является допустимым диапазоном. Допустим также пустой диапазон [А, А). А вот ди- апазон [A+N, А) недопустим: A+N появляется позднее А. а нс раньше, поэтому не имеет смысла говорить об элементах от A+N до А. Вообще, диапазоны удовлетворяют следующим свойствам: 1. Для любого указателя р пустой диапазон [р, р) является допустимым диапа- зоном. 2. Если [ fi rst, last) — допустимый непустой диапазон, то [fi rst+1, last) — также допустимый диапазон. * В дальнейшем мы обобщим -но поняше до диапазона итераторов Сама идея при эюм ociaeica прежней
2.1. Линейный поиск 29 3. Если [ first, last) — допустимый диапазон и mid — любой указатель, причем mid достижим из first, a last достижим из mid, то [first, mid) и [mid, last) являются допустимыми диапазонами. 4. Верно и обратное: если оба диапазона [first, mid) и [mid, last) являются допус- тимыми, то и [first, last) — допустимый диапазон. Почему используется такое странное асимметричное определение диапазонов, в ко- тором включается один конец диапазона и исключается другой? Это сделано для удоб- ства. Обозначение полуоткрытого интервала может сначала казаться непривычным, но оно проще в употреблении, чем другие, как при формулировании определений, так и на практике — при написании и использовании алгоритмов. Отчасти мы уже наблюдали это в findl. Цикл while в findl столь прост именно потому, что last не входит в [first, last). Более того, как указывает Эндрю Кёнт [Кос89|, асимметричные диапазоны помогают избежать ошибок “потери единицы’ количество элементов в [first, last) равно last - first, то есть столько, сколько вы и ожидаете. Это особенно полезно в Си и C++, где массивы начинаются с нулевого индекса. Массив из N элементов описывается диапазоном [A, A+N), несмотря на то что последний элемент этого массива А[ N-1 ]. Наконец, определение диапазона в виде по- луоткрытого интервала позволяет нам представлять пустые диапазоны так же про- сто, как и непустые’ [А, А) является обычным диапазоном (нс содержащим элемен тов), поэтому f 1 nd 1 (А, А, с) — корректный вызов, как и должно быть. С фундаментальной точки зрения диапазоны определены таким образом потому, что если диапазон содержит п элементов, то нам требуется ссылаться на п + 1 различ- ную позицию, а не только на п. В findl, например, нам нужно было какое-нибудь зна- чение, возвращаемое в случае неудачного поиска. Аналогично, часто очень важно ссы- латься не только на существующие элементы диапазона, но и на позицию, куда мож- но вставить новый элемент. В диапазоне из п элементов таких позиций п + 1: начало, конец и п - 1 промежутков между существующими элементами диапазона. 2.1.3. Линейный поиск в С + + В соответствии с определением функцию findl можно применять только для поиска в массиве элементов типа cha г. Но в C++, где есть возможность пользоваться шабло- нами для параметризации типов аргументов функции, имеется очевидное и прямое обобщение. Мы определим функцию, являющуюся копией findl, которая вместо ука- зателей на char принимает указатели на любой тип Т. Гипотетическая функция fina2 может быть объявлена следующим образом: tempi ate <class Т> fird2(T* first. Т* last Т value) В SI L используется несколько менее очевидное обобщение. Функция линейного по- иска в STL называется find. template sclass Iterator class T> iterator find(Iterator first. Iterator last, const T& value) { while (first '= last && *first '= value) ++f1rst, rel ut ii f 11 st }
30 Глава 2. Алгоритмы и диапазоны Почему именно find, а не более очевидная find2? Что мы выиграли? Мы выиграли функцию гораздо более общего вида, чем f ind2. В то время как find? требует, чтобы ее аргументы first и last являлись указателями, find не накладывает таких ограничений. Так, в ней используется синтаксис операций с указателями (operator* для получения значения, на которое указывает first, operator++для ин- кремента first, operator== для проверки равенства first и last), но это не означает, что Iterator должен быть указателем, — он лишь должен поддерживать аналогичный интерфейс. Так же как и findl, find ищет в диапазоне [first, last), полагая, что диа- пазон удовлетворяет всем свойствам, описанным в разделе 2.1.2, но find уже не тре- бует, чтобы элементы в [f i rst, last) были элементами массива. 11а примере find получен алгоритм, который был обещан в начале этой главы: обоб- щенный алгоритм поиска в любых структурах данных, поддерживающих понятие одномерной последовательности элементов. Нам требуется лишь возможность срав- нения элементов, перехода к следующему элементу и проверки того, что все элемен- । ы обработаны, то есть базовые операции, без которых вообще вряд ли имеет смысл говорить о [ fi rst, last) как о линейном диапазоне. Поиск в связном списке В качестве примера общности find воспользуемся ею для поиска значения в одно- связном списке (singly linked list). Значения в массивах и связных списках организо- ваны в последовательность разными способами, но мы можем воспользоваться fine в обоих случаях. Почти в каждой нетривиальной Си-программе есть список узлов, связанных друг с другом, и в каждом узле имеются данные и указатель на следующий узел. Например: struct int_node { int val. »n1_node* next, > Код прохода по всем элементам выглядит примерно так: int_node* р. for (р - list_head, р ’= NULL, р = p->next) // некоторое действие (Это, конечно, упрощенный вариант. В реальной программе узел был бы в виде st ruct с несколькими полями; одно целое значение не стоит таких хлопот.) Распространенной операцией над подобными связными списками является поиск указанного значения, то есть выполнение линейного поиска. Так как список, состоя- щий из элементов набора int_node, представляет собой линейную последовательность, нам не придется писать линейный поиск заново; должна быть возможность повторно использовать find Как это сделать? Мы можем воспользоваться find для поиска в диапазоне [first, last). Во всех на- ших предыдущих примерах f i rst и last были указателями, но в данном случае это не । ак. Если бы fi г st был указателем па int_node, то функция find попыталась бы полу- чип ь следующий элемент путем выполнения над указателем операции ++£i rst. Одна- ко это не приведет к успеху, потому что мы работаем со связным списком, а не с мас- сивом. Если р является указателем на узел списка, то указателем на следующий у.зе i будс! о-х>пс> I. а не р+1
2.1. Линейный поиск 31 Решение подсказывается самой задачей: C++ допускает перегрузку операций (operator overloading). Если ope rateг++ не обеспечивает ожидаемого поведения, то нужно определить его таким образом, чтобы продвижение find по последовательно- сти было допустимо. Нельзя переопределить operateг++ для аргументов типа int_node*. C++ допускает определение значения выражений с оператором, но не изменение существующего (Представьте, что могло бы случиться, если бы можно было изменить смысл ope rate г+ так, что “сложение” на самом деле означало бы вычитание!) Мы можем лишь напи- сать простой класс-обертку (wrapper class), который выглядит как int_node*, но при этом определяет operator++ более подходящим образом. Можно сделать еще один шаг на пути обобщения, имея в виду, что int_node вряд ли является единственным видом узла, имеющего указатель next на следующий узет. Определение класса-обертки, работающего только с int_node, не намного сложнее определения шаблона класса-обертки, который работает с любым типом узла, име- ющего указатель next на следующий узел: template <class Node> struct node.wrap { Node* ptr, node_wrap(Node* p = 0) ptr(p) {} Node& operator*() const { return *ptr, } Node* operator->() const { return ptr, } node_wrap& operator++() { ptr = ptr->next, return *this, } nooe_wrap operator++(int) { node_wrap tmp - *this, ++*this, return tmp, } bool operator~(const node_wrap& i) const { return ptr == i ptr, } bool operator'=(const node_wrap& i) const { return ptr ' = i.ptr, } }; Наконец, исключительно из соображений удобства мы можем определить operate г который позволит нам сравнивать int_node и int: bool operator“(const int_node& node, int n) { return node val == n, } Для поиска int_node с требуемым значением нужно писать цикл. Мы сможем вос- пользоваться find, и поиск будет произведен при помощи одного вызова функции: find(node_wrao<int_node>(list_head), node_wrap<int_node>(). val) Второй аргумент, node_wrap<int_node>(), использует конструктор по умоччанию (default constructor) класса node_^rap. Он создает node_wrap, который содержит нуле- вой указатель. Так как последний узел списка имеет нулевой указатель next, мы ищем от list_head до конца списка. Обертывающий класс node_wrap выглядит тривиальным и таковым действительно является, но он позволит нам сделать нечто довольно важное. Структура int. посе не связана напрямую с обобщенными алгоритмами; она могла быть написана много лет назад, до появления на свет обобщенных алгоритмов (или даже самого языка C++). Воспользовавшись крошечным классом-оберткой, мы достигли взаимодействия
32 Глава 2 Алгоритмы и диапазоны между сирой структурой данных, списком узлов типа int node и новым оооощен пым алгоритмом find. Класс node_wrap служит посредником между ними, и при этом эффективность не теряется. Все функции-члены node_wrap определены как inline, поэтому запись w++ для узла node_wrap<int_node>, скорее всего, будет иметь абсолют- но тот же эффект, что и запись р = p->next для int_node*. Обратите внимание, что поведение node_wrap похоже на поведение обычных указа- телей Си. Он обеспечивает базовые операции над указателем: разыменование (operator*), доступ к члену класса (operators) и оба вида инкремента Версия cperator++ без аргументов осуществляет преинкремент, а версия с одним целочис- ленным аргументом — постинкремент. 2.2. Концепции и моделирование Является ли алгоритм find обобщенным? Или, другими словами, какие требования па налагает на свои аргументы? Мы можем создать список предположений, которые find делает об аргументе Iterator: можно использовать оператор разыменования operator*, operator** для пе- рехода к следующему Iterator и т. д. Будем иметь в виду, что алгоритм find намеренно разрабатывался в максимально общем виде. Предположения, которые он делает о своих аргументах, применимы для любого диапазона. Этот набор требований, таким образом, применим далеко за пре- делами find, то есть к любому алгоритму, работающему с диапазонами. Обсуждаемый набор требований важен настолько, что у него есть свое собствен- ное название: параметр шаблона find называется итератором. Итераторы являются фундаментальной частью STL. Требования и концепции Утверждают, что проблемы в информатике зачастую можно решить, добавив уровень косвенной адресации. В нашем случае добавим уровень ссылки к требованиям на find Самос важное требование состоит в том, что Iterator должен быть Итератором Ввода (Input Iterator). Сделанное шрифтовое выделение означает, что Итератор Ввода является концеп- цией (concept). В книге таким образом будут выделены названия всех концепций. Концепция описывает набор требований к типу, и при полном соответствии конкрет- ного типа этим требованиям будем называть его моделью (model) данной концепции. I laiipiiMep, char* и node_wrap являются моделями Итератора Ввода. Мы пользуемся специальным шрифтом, чтобы не забыть, что концепция нс явля- ется именем класса, переменной или параметром шаблона, то есть опа не может по- явиться непосредственно в программе на C++. Однако концепции исключительно важны в любой программе на C++, использующей методологию обобщенного про- граммирования. В качестве примера рассмотрим разницу между параметром шаблона Iterator и кон- цепцией Итератор Ввода. Параметр Iterator является формальным параметром шаб- лона. Синтаксически, в языке C++, он рассматривается как тип, и в любой специали- зации шаблона функции find параметр шаблона Iterator соответствует какому-либо конкретному типу. Концепция Итератор Ввода не соответствует какому-то одному
2.2 Концепции и моделирование 33 типу. Наоборот, она представляет собой перечень требований (list of requirements), которым тип должен удовлетворять. Представим себе язык, в котором можно явно определять концепции. Вы можете объявить список требований, дать ему имя и затем, как и в случае объявления пере- менных конкретного типа, объявить, что тип или формальный параметр шаблона яв- ляется моделью некоей конкретной концепции. Язык C++ не является таким гипоте- тическим языком. В C++ нет явного способа объявить, что параметр шаблона — мо- дель какой-либо концепции. Мы дали параметру шаблона find имя Iterator, но компилятор C++ не имеет возможности связать это имя с концепцией Итератора Вво- да, потому что концепции не являются элементами языка C++. Что такое концепция? Что же такое концепция, если это не класс, не функция и не шаблон? Есть три спосо- ба понять, что есть концепция. На первый взгляд эти способы кажутся разными, но на самом деле они эквивалентны. Все три подхода полезны для понимания различ- ных аспектов обобщенного программирования. Во-первых, концепцию можно рассматривать как список требований к типу (type). Если тип Т является моделью концепции К, то Т удовлетворяет всем требованиям К. Описание свойств, которыми должен обладать тип, почти всегда является простей- шим способом определения концепции. Во-вторых, концепцию можно рассматривать как набор типов. Например, концеп- ция Итератор Ввода состоит из типов char*, int*, node_wrap и т. п. Если тип Т является моделью концепции К, то это определение говорит, что Т принадлежит множеству типов, которые представляет К. Легко заметить, что это определение эквивалентно предыдущему, если рассмотреть множество всех типов, удовлетворяющих списку требований. Данное определение использует такое множество, а предыдущее исполь- зует требования, которые задают множество, но это лишь две разные точки зрения на одно и то же явление. В-третьих, концепцию можно рассматривать в виде списка допустимых программ. В соответствии с этим определением, например, значение концепции Итератора Вво- да заключается в ее использовании в find и многих других алгоритмах. Сама концеп- ция состоит из свойств типа Iterator, используемых во всех алгоритмах. Это опреде- ление может показаться гораздо более абстрактным, чем предыдущие, но оно важно, потому что в некотором смысле является самым практичным из трех. Новые концеп- ции открываются и описываются не путем записи списка требований ex mhilo, а пу- тем определения алгоритмов и изучения того, каким образом формальные парамет- ры шаблонов используются в них. Мы открыли Итератор Ввода при изучении find. Причиной появления всех остальных концепций в этой книге также явились алго- ритмы. Системы формальных спецификаций, такие как Larch [GHG93J и Tecton [КМ92, KMS82], определяют концепции в терминах математической логики. Например, в Tecton концепция определяется как “набор множественно-сортируемых алгебр” В этой книге формальная логика не используется. Требования концепций Как выглядит набор требований концепции? Нельзя просто сказать, что требования являются списком функций-членов Так, мы знаем, что спа г* — модель Итератора
34 Глава 2. Алгоритмы и диапазоны Ввода, но у спа г* нет функций-членов. Очевидно, шаблоны функций работают иначе. Вы можете вызвать find с любым типом аргумента Iterator, если после подстановки его вместо Iterator результирующий код функции find имеет смысл. Таким образом, требования являются набором допустимых выражений. Например, мы требуем, что если Iterator является моделью Итератора Ввода, а 1 представляет собой объект типа Iterator, то *1 является допустимым выражением. Даже такое простое требование включает в себя два различных типа: сам итератор (модель Итератора Ввода) и тип, который получается, когда вы пишете *1. Это очень распространенная ситуация. В более общем случае концепция К содержит список допустимых выражений, включающих в себя тип Т, который моделирует К, а также другие типы, ассоциированные с Т. Детали этой связи зачастую являются важнейши- ми требованиями концепции. Базовые концепции Некоторые операции до такой степени фундаментальны, что обеспечиваются почти каждым разумным типом. В обобщенном программировании эти операции соответ- ствуют весьма ограниченному числу настолько общих и фундаментальных концеп- ций, что большинство алгоритмов STL предполагают их наличие. На самом деле об- ласть их применения гораздо шире STL; эти концепции являются основополагающи- ми для любой обобщенной библиотеки. Одной из базовых является концепция Присваиваемого ( Assingnable) Ч Тип X есть модель Присваиваемого, если допускается копирование значений типа X и присваи- вание новых значений объектам типа X. Это не так тривиально, как кажется, потому что все различные формы присваиваний и копирования должны быть согласованы друг с другом. Например, если мы рассмотрим Присваиваемый тип, можно ожидать, что х будет иметь одинаковое значение во всех нижеприведенных случаях: X х(у) ИЛИ X = у или Пир = у х = trnp Можно также рассчитывать, что присваивание нового значения х не будет иметь по- бочных эффектов в виде изменения значения другой переменной у. Недостаточно просто иметь конструктор копирования и оператор присваивания. Они еще должны быть согласованы друге другом и с фундаментальными свойствами объектной моде- ли Си. Другими базовыми концепциями являются Конструируемый по Умолчанию (Default Constructiblc), =Сравнимый (Equality Comparable) и <Сравнимый (LessThan Com- parable). ) При переводе в выбранных терминах произошла некоюрая потеря модальное!и например, “As- signable” значит “Допускающий присваивание”, а нс “Присваиваемый" Однако полное сохранение зна- чения привело бы к ( ’пинком длинным юрмипам -- Прнмеч ред
2 3. Итераторы 35 Тип. являющийся моделью Присваиваемого, имеет конструктор копирования, а тип, являющийся моделью Конструируемого по Умолчанию, имеет конструктор по умол- чанию — конструктор без аргументов . Таким образом, тип Т является Конструируе- мым по Умолчанию, если можно создать объект типа Т с помощью Т() и объявить переменную типа Т т t Все встроенные типы C++, такие как int и void*, являются Конструируемыми по Умол- чанию. Концепции ^Сравнимого и <Сравнимого относятся к сравнению двух объектов. А именно, тип Т является моделью ^Сравнимого, если можно сравнивать два объекта типа Т на равенство с помощью выражения х -- у или X у Аналогично, тип Т является моделью <Сравнимого, если можно проверить, что один объект типа Т меньше или больше, чем другой, с помощью выражения X < V или X > у Назовем регулярным (regular) тип, который является моделью Присваиваемого, Конструируемого по Умолчанию, =Сравнимого, а также тип, операции с которым взаи- модействуют друг с другом ожидаемым образом. В случае регулярного типа можно полагать, например, что если вы присвоили переменной у значение переменной х, то выражение х =- у истинно (имеет значение тгие). Большинство базовых типов C++ являются регулярными (int является моделью всех концепций этого раздела), такими же оказываются почти все типы, определен- ные в STL. 2.3. Итераторы Итераторы являются обобщением указателей; это объекты, которые указывают на Другие обьекты. Они имеют большое значение для таких обобщенных алгоритмов, как find, поскольку могут использоваться для прохода по диапазону объектов Если итератор указывает на какой-либо объект диапазона, то после инкремента он будет указывать на следующий объект этого диапазона. ’В C++ если вы явно необьявили пи одною KOHcipyiaopa, toaior класс имеет неявный консфуктор 1,0 умолчанию
36 Глава 2 Алгоритмы и диапазоны Итераторы важны для обобщенного программирования потому, что они являются интерфейсом между алгоритмами и структурами данных. Если алгоритм, например find, принимает в качестве аргументов итераторы, то он может работать с множеством структур данных, даже с такими разными, как связные списки и массивы Си. Все, что нам требуется, — это возможность доступа ко всем элементам структуры путем ли- нейного прохода. Исходя из рассуждений раздела 2.2, можно предположить, что итераторы должны быть определены в качестве концепции и что указатели являются моделью этой кон- цепции. Однако это не совсем так. Указатели в C++ имеют множество различных свойств. Обобщенный алгоритм find использует лишь малое их подмножество. Дру- гие обобщенные алгоритмы используют иные подмножества. Таким образом, суще- ствует несколько разных способов обобщения указателей, и каждый способ является отдельной концепцией. Итераторы — не единственная концепция, а семейство из пяти концепций: Итератор Ввода (Input Iterator), Итератор Вывода (Output Iterator), Од- нонаправленный Итератор (Forward Iterator), Двунаправленный Итератор (Bidirectional Iterator) и Итератор Произвольного Доступа (Random Iterator). (Шестая концепция, Тривиальный Итератор (Trivial Iterator), существует лишь для прояснения определе- ний других концепций итераторов.) 2.3.1. Итераторы ввода Мы можем осуществлять поиск по связному списку с помощью find потому, что find использует аргумент шаблона Iterator в стилизованном виде. Тип аргумента Iterator должен быть похож на указатель, но не обязательно поддерживать все операции, ко- торые определены для указателей. Iterator должен быть моделью Итератора Ввода (Input Iterator), причем последний является типом, удовлетворяющим требованиям find. Это нс циклическое определение — вовсе нет! — и мы сейчас перечислим требо- вания. Это совсем не бесполезно. Итератор Ввода является важной концепцией, пото- му что многие алгоритмы наряду с find имеют аналогичные требования. Существенно, что Итератор Ввода настолько похож на указатель, что имеет смысл говорить о диапазоне итераторов [first, last). • Как и в случае обычных указателей Си, имеется три варианта: итератор ввода может быть разыменуемым (dereferenceable), указывать на элемент, следующий за последним (past the end), и сингулярным (singular). И, как и в случае с указате- лями, [first, last) имеет смысл, только если и first и last несингулярны. • Можно сравнить два объекта типа Iterator на равенство. В find мы проверяем два итератора на равенство, чтобы уточнить, достигнут конец диапазона или нет • Итераторы ввода можно копировать и присваивать. В find, например, first и last передаются по значению, что требует вызова конструктора копирования гипа Iterator. • Мы можем разыменовать объект типа Ite га to г. Это означает, что выражение -р определено. Каждый Итератор Ввода имеет ассоциированный (associated) с ним тип значения, представляющий собой тип объекта, на который! ссылается итера- тор. Например, в случае node_wrap<int_node> типом значения является int_node (Ниже мы увидим, что Итератор Ввода имеет и другие ассоциированные типы.) • Мы можем применять операцию инкремента (increment) к объектам типа Iter at о г. Таким образом, определены выражения ++р и р++. Как и в случае обык-
2.3. Итераторы 37 новепных указателей Си, ++р означает “увеличить р и вернуть новое значение’’, а р++ означает “увеличить р и вернуть прежнее значение”. Есть еще несколько моментов, которые столь же важны, что и приведенные выше. • В find мы рассматриваем значения из диапазона [first, last), не модифицируя их. Итератор Ввода указывает на некоторый объект, но не обязательно предос- тавляет способ модификации этого объекта. Вы можете разыменовать Итератор Ввода, но не можете присвоить новое значение, пользуясь результатом опера- ции разыменования: выражение *р = х не обязательно является допустимым. (Например, вы не можете изменить значение, на которое указывает const int* ) • Итератор Ввода должен поддерживать небольшое подмножество операций, к ко- торым мы привыкли при использовании арифметики указателей. К Итератору Ввода можно применить инкремент, но не обязательно — декремент (decrement). (Например, у node_wrap нет функции-члена operator--.) Аналогично, выраже- ния типа р + 5 и р1 - р2 не обязательно допустимы. Единственная форма ариф- метики указателей, которую должен поддерживать Итератор Ввода, — это operate г++. • Итераторы Ввода поддерживают лишь некоторые формы арифметики и некото- рые формы сравнений. Можно сравнить два Итератора Ввода на равенство, но нельзя узнать, находится ли один из них “раньше” другого. Например, р 1 < р2 — допустимое булево выражение, если pl и р2 — указатели, но не обобщенные Ите- раторы Ввода. • Линейный поиск является “однопроходным” (“single pass”) алгоритмом. Он про- сматривает диапазон [first, last) один раз и ни одно значение не читает более одного раза. Это — единственно правильный способ использования Итератора Ввода. По диапазону Итераторов Ввода нельзя пройти более одного раза, как нельзя использовать два итератора, указывающие на различные элементы диа- пазона Итераторов Ввода. Каждый диапазон может поддерживать только один активный итератор единовременно. Например, нельзя написать р1 = р2++, а за- тем продолжать использование и р1, и р2. Аналогично, если р == q, вы не можете полагать, что ++р == ++q. Важно помнить, что это ограничения на алгоритмы, использующие Итераторы Ввода, а не на типы, которые моделируют концепцию Итератора Ввода. Например, тип double* является моделью Итератора Ввода, однако поддерживает и “многопроходные” ( multipass”) алгоритмы. Требования к Итератору Ввода обеспечивают минимальный набор функциональности, но модели Итератора Ввода могут (обычно так оно и есть) выходить далеко за пределы минимального набора. Однако алгоритм, работающий с Итераторами Ввода, не должен предполагать ничего, выходящего за пределы мини- мальной функциональности, гарантируемой требованиями к Итератору Ввода. ^ти ограничения очень строгие, особенно то из них, которое требует, чтобы алго- ритм, использующий Итераторы Ввода, был однопроходным, и возникает вопрос* как Реализовать алгоритмы в соответствии с этими требованиями? Ответ состоит в том, Чт° такой необходимости нет! Итераторы Ввода представляют собой одну из двух самых слабых концепции пте- торов, и для реализации многих алгоритмов требуются другие виды итераторов. Удивительно, что большинство алгоритмов не могут быть написаны в терминах
38 Главе! 2. Алгоритмы и диапазоны Итераторов Ввода, и трудно удержаться от восхищения, когда это оказывается воз- можным. Кроме find (с. 208) и аналогичных алгоритмов, таких как find_if (с. 209), Итераторы Ввода используются в equal (с. 228), partial_sum (с. 287) и даже в таких сложных алгоритмах, как random_sample (с. 280) и set_intersection (с. 325). Само название Итератор Ввода раскрывает причину такого странного, на первый взгляд, набора требований. Использование Итератора Ввода похоже на чтение вход- ных значений с терминала или чтение данных из сетевого соединения. Вы можете проверить текущее значение и запросить очередное, но, как только затребовано сле- дующее значение, предыдущее исчезает. Если необходим доступ к предыдущему зна- чению, то его следует сохранить “вручную”. Другими словами, алгоритмы, которые можно написать с помощью Итератора Ввода, скорее всего, работают непосредствен- но со значениями, считанными из потока ввода. Действительно, если вы пользуетесь предопределенным в STL Итератором Ввода class istream_iterator (с. 351), то вы просто читаете значения из входного потока. 2.3.2. Итераторы вывода Поскольку работа Итераторов Ввода подобна чтению из потока ввода, то можно пред- положить, что есть другие итераторы, аналогичные записи в поток вывода. Они на- зываются Итераторами Вывода (Output Iterator). Чтобы уяснить концепцию, необходимо разобраться в алгоритмах, которые ее ис- пользуют. Примером алгоритма, требующего вывода значений, является простая опе- рация копирования значений из одного диапазона в другой. Входной диапазон может состоять из Итераторов Ввода, а выходной диапазон — нет. Итераторы Ввода не пре- доставляют метода для изменения значения, на которое указывает итератор. Л вот с использованием Итераторов Вывода легко реализовать алгоритм, который последо- вательно копирует диапазон: Template ^class Inputiterator, class Outputlterator> Outputiterator copy(lnputlterator first, Inputiterator last, Outputiterator result) / for( first ’ = last, ++result, ++first) «resuli - *first, return result, i Это алгоритм copy из STL (c. 239), он использует концепцию Итератора Ввода то1 > гак же, как и find: это однопроходный алгоритм, который читает каждый Итератор Ввода только один раз перед переходом к следующему. Итератор Вывода в сору применяется схожим образом, учитывая, естественно, раз- ницу между вводом и выводом. С помощью Итератора Вывода можно сделать запись последовательного набора значений. Мы записываем значение, инкрементируем Ите- ратор Вывода и записываем следующее значение. Один и тот же итератор никогда не пишется дважды, мы никогда не возвращаемся к предыдущему итератору вывода и ни- когда не пропускаем итератор. Диапазон вывода предназначен только для записи, как диапазон ввода предназначен только для чтения. Копирование — это однопроходный алгоритм по отношению и к выводу, и к вводу. Требования к Итераторам Вывода похожи на требования к Итераторам Ввода: • Итераторы Вывода можно копировать и присваивать, как и Итераторы Ввода.
2.3. Итераторы 39 • Можно воспользоваться Итератором Вывода для записи значения. Таким обра- зом, выражение *р = х всегда определено. • Итератор Вывода может быть инкрементирован. Следовательно, выражения ++р и р++ определены. Ограничения Итераторов Вывода во многом похожи на ограничения Итераторов Ввода. Итератор Вывода должен поддерживать operator**, но может не поддержи- вать другие операции арифметики указателей. Аналогично, все алгоритмы, исполь- зующие Итераторы Вывода, должны быть однопроходными вроде сору; вдобавок, как и в случае с Итераторами Ввода, не может существовать двух различных Итераторов Вывода, указывающих на разные места в диапазоне одновременно. “Рука поэта пи- шет и, написав, движенье продолжает”. Кроме того, концепция Итератора Вывода имеет еще два важных ограничения. Одно из них очевидно: как Итераторы Ввода предназначены только для чтения, так и Ите- раторы Вывода предназначены только для записи. Таким образом, можно написать, например, *р = х, но совсем не обязательно можно также написать х = *р. Другое огра- ничение менее очевидно, но хорошо иллюстрируется алгоритмом сору. В сору есть входной и выходной диапазоны. Каждый из этих диапазонов имеет и на- чало, и конец, что в сумме составляет четыре позиции. Однако в списке аргументов сору только три итератора: диапазон ввода[fi rst, last) и начало диапазона вывода — result. Этого вполне достаточно, потому что сору записывает точно такое же количе- ство элементов, которое считывает; конец диапазона вывода задан неявно. Таким образом, в сору выполняется проверка first 1 = last, но итератор result по- добной проверки не проходит. Так как в сору и других сходных алгоритмах никогда не возникает потребности в сравнении одного Итератора Вывода с другим, концеп- ция Итератора Вывода и не предоставляет средств для этого. “Диапазон” Итераторов Вывода всегда задается единственным итератором и счетчиком. Например, в сооу выходной диапазон определен в виде диапазона, который начинается в result и име- ет то же количество элементов, что и [first, last). Можно написать алгоритм, удовлетворяющий всем ограничениям концепции Ите- ратора Вывода: сору — один из таких алгоритмов; есть еще transform (с. 246), merge (с. 315) и многие другие. Однако имеется ли какая-нибудь причина, чтобы ограни- читься итератором для записи, поддерживающим лишь однопроходные алгоритмы? Ответ на этот вопрос положителен. Действительно, Итераторы Вывода довольно часто встречаются и очень полезны. Одним из наиболее важных таких итераторов является insert iterator (с. 347), который вместе со своими родственниками +‘ronT_insert_iterator и back_insert_iterator вставляет значения в контейнер. Про- стейшим примером Итератора Вывода, иллюстрирующего многие характеристики концепции, является ost ream iterator (с. 354). Класс ost-eam_iterator — зеркальное отражение isiream_iterator. Это Итератор Вывода в прямом смысле слова. Если р — это ost ream_iterator, то *р = х выполняет форматированный вывод х в ost ream. Определение ost ream_iterator простое, но по- учительное: template <class Т> class ostream_iterator { P r jvat e ostream* os cues I ciia''’ siring
40 Глава 2 Алгоритмы и диапазоны pliij 1 i с Obi reain_iterator(ostream& s, const char* с = 0) os(&s), string(c) {} ostream_iterator(const ostream_iterator& i) os(i os), string(i string) {} osi ream_iterator& operator=(const ostream_iterator& i) { os = i os string = i string, return *this, ost•eam_iterator<T>& operator=(const T& value) { *os << value if(slring) *os << string, return *this, f ostream_iterator<T>& operator*() { return *this, } ostream.1terator<T>& operator++() { return *this, } ostream_iterator<T>& operator++(int) { return *this, } r Такое определение класса иллюстрирует две особенности Итераторов Вывода. Здесь показано, как написать итератор только для записи, и почему алгоритм, использу- ющий Итераторы Вывода, должен быть простым однопроходным алгоритмом типа сору. Если вспомнить определение ost ream_iterator, ограничения Итератора Вывода более не кажутся такими произвольными. Итератор ost ream_iterator можно использовать только в однопроходном алгорит- ме. поскольку класс ost ream_iterator не запоминает конкретную позицию в ost ream, он осуществляет вывод и ничего более. Невозможно сохранить предыдущую пози- цию. копируя значения ostream_iterator, потому что ostream_iterator, пишущий в тот же ost ream, идентичен первому. Алгоритмы типа сору действуют, как и ожидается- сору(А. А + N ostream_iterator<int>(cout. " ”)) - копирует последовательность целых чисел в стандартный вывод, однако использо- вать ost ream i terator могут алгоритмы только такого типа. Мы видим, как и почему ost ream_iterator обеспечивает доступ только для записи. Такой доступ необходим ввиду семантики самого класса ost ream, а простейшим спо- собом обеспечить доступ только для записи является трюк C++ с определением заме- щающего класса (или объекта-посредника). Мы хотим, чтобы выражение *р = х вы- полняло какое-то действие, и для этого определяем *р так, чтобы объект-посредник. У которого определен соответствующий operator^, выполняющий это действие, воз- вращался. Единственный немного необычный нюанс ostream_iterator в нашем слу- чае — это то, что замещающим классом является сам ost ream_iterator. Можно было бы определить другой класс, но в этом нет необходимости. Вероятно, мы проделали слишком много работы только ради того, чтобы иметь возможность писать *р = х вместо os << х, но на самом деле мы получили нечто очень важное. Обобщенное программирование опирается на синтаксические ограничения. Однообразный синтаксис делает возможным использование алгоритма сору для каж-
2.3. Итераторы 41 дрй операции, включающей в себя запись значений в диапазон вывода любого вида Алгоритм сору может перемещать элементы из одного массива в другой, вставлять элементы в контейнер или записывать элементы в стандартный вывод. 2.3.3. Однонаправленные итераторы Часть алгоритмов может быть написана исключительно в терминах Итераторов Вво- да и Итераторов Вывода, однако не удивительно, что другая часть не принадлежит к этой категории: • Итераторы Ввода предназначены только для чтения, Итераторы Вывода — толь- ко для записи. Эти концепции нельзя использовать в алгоритмах, которые и чи- тают, и изменяют элементы диапазона. Можно написать некоторые алгоритмы поиска в терминах Итераторов Ввода, но нельзя написать в тех же терминах ал- горитм, делающий поиск и замену элементов. • Так как алгоритмы с Итераторами Ввода и Итераторами Вывода могут быть только однопроходными, это автоматически ограничивает рассмотрение алго- ритмами, линейными по сложности. Однако мы прекрасно знаем, что не все ал- горитмы линейны. Например, наиболее полезные алгоритмы сортировки имеют сложность O(Nlog N) или О(М). • В любой момент времени в диапазоне возможен только один активный Итера- тор Ввода или Итератор Вывода. Это ограничивает нас алгоритмами, работаю- щими единовременно только с одним элементом. Мы не можем писать алгоритмы, использующие отношения между двумя или более элементами. Алгоритмы, применяющие Однонаправленные Итераторы (Forward Iterator), не име- ют таких ограничений. Однонаправленный Итератор поддерживает то же подмноже- ство операций арифметики указателей, что и Итератор Ввода и Итератор Вывода (вы можете написать ++р, но не --р или р += 5), но в остальном он гораздо менее ограничен. Тип, являющийся моделью Однонаправленного Итератора, представляет собой так- же модель и Итератора Ввода, и Итератора Вывода, что позволяет писать алгоритмы, использующие один и тот же диапазон как для чтения, так и для модификации. При- мером служит алгоритм replace, который ищет все вхождения old_value и меняет их на new_value: template <class Forwarditerator, class T> void replace(Forward!terator first, Forwarditerator last. const T& old_value, const T& new_value) { for ( first ' = last. ++first) it (*first == old_value) *first = new_value, } Как и алгоритмы, использующие Итераторы Ввода и Итераторы Вывода, replace — однопроходный алгоритм, работающий единовременно только с одним элементом Нам требуется более сильная концепция Однонаправленного Итератора, поскольку один и тот же итератор применяется как для чтения, так и для записи. Однонаправ- ленные Итераторы могут встречаться и в алгоритмах с менее специфическим спосо- бом доступа к элементам.
42 Глава 2. Алгоритмы и диапазоны Рассмотрим, например, вариант линейного поиска. Вместо того чтобы искать эле- мент, имеющий требуемое значение, мы будем искать два последовательных элемен- та с одинаковым значением: template <class ForwardlteratoO c^rwardlterator ad]acent_find(ForwardIterator first. Forwarditerator last) ]f (first -= last) return last, Forwarditerator next = first, whilc(++next last) { if (*first -- *next) return first. f]rst = next, return last. } Запись этого алгоритма включает в себя Однонаправленные Итераторы: даром что adjacent_find является алгоритмом только для чтения, его нельзя написать в терми- нах Итераторов Ввода, потому что каждая итерация цикла сравнивает два различных значения * f i rst и *next. Алгоритм, использующий Итераторы Ввода, не может обра- батывать более одного итератора в одном диапазоне. Важно понять, что Итераторы Ввода и Итераторы Вывода имеют довольно специ- фические свойства, и алгоритмы, опирающиеся на эти концепции, ограничены одной стилизованной формой — одной простой вариацией цикла. Алгоритмы с Однона- правленными Итераторами нс имеют таких ограничений. Фундаментальное отличие состоит в том, что Итераторы Ввода и Итераторы Вы- вода, в отличие от Однонаправленных Итераторов, не полностью следуют обычной модели памяти в Си. Например, адаптер nooe_wrap связного списка из раздела 2.1.3 является Однонаправленным Итератором, Объект типа node_wrao указывает на один конкретный адрес так же, как и обыкновенные указатели. Если р и q — обыкновенные указатели Си, можно записать набор операций Р = q - о - х. и ожидать, что значение, на которое указывает р, изменилось, а значение, на которое указывает q, — нет. Это фундаментальное свойство модели памяти в Си: различные указатели указывают на различные области памяти. Если р и q можно разымено- вать, то р == q тогда и только тогда, когда *р и *q — один и тот же объект. Это свойство можно использовать в качестве определения равенства указателей. Однонаправленные Итераторы подчиняются тому же правилу идентичности (identity). Принцип идентичности является основополагающим и имеет большое зна- чение. Только благодаря ему sort сортирует, a reverse меняет порядок на противопо- ложный. С другой стороны, это сильное условие. Начнем с того, что если два итсрато-
2.3. Итераторы 43 ра р и q равны, то *р == *q. В действительности это условие еще сильнее: р и q указыва- ют не просто на одинаковое значение, но на один и тот же объект. Если вы модифици- руете *р, то вы модифицируете также и *q. Это можно записать в виде &*р == &*q, пото- му что объекты *р и *q являются на самом деле одним и тем же объектом, если имеют одинаковые адреса Идентичность итераторов проиллюстрирована па рис. 2.1. &-q Рис. 2.1. Идентичность одинаковых итераюров Как и в случае обыкновенных указателей, данное описание можно использовать для определения равенства двух итераторов. (Это одна из причин, почему Итераторы Вывода нельзя сравнивать на равенство. Если невозможно проверить значение, на которое указывает итератор, то это определение не имеет смысла.) Константные и изменяемые итераторы Оба алгоритма, replace и adjacent_find, используют Однонаправленные Итераторы но есть существенная разница в способе их использования: replace модифицирует значения из диапазона [f i rst, last), a adj acent_f ind — нет. He все алгоритмы, приме- няющие Однонаправленные Итераторы, нуждаются в модификации значений, на ко- торые указывают итераторы. Однонаправленный Итератор может быть либо константным (constant), и в этом случае можно обращаться к объекту, на который он указывает, но не присваивать че- рез него новое значение, либо изменяемым (mutable), и тогда можно выполнять обе операции. Таким образом, int* — переменный Однонаправленный Итератор, как и node_wrap<int_node>. (Он также является Итератором Ввода и Итератором Вывода.) A const int* - константный Однонаправленный Итератор. Термин константный итератор часто употребляется в этой книге, однако он несет в себе некоторую двусмысленность: константный итератор сам по себе не обязатель- но является константным объектом (более того, обычно он таковым не является). Константным итератором нельзя воспользоваться для изменения значения, на которое он указывает, но сам итератор как объект может быть константным (но может и нс быть). Поэтому, например, const int* — константный итератор, потому что им нельзя воспользоваться для изменения значения, на которое он указывает, a int* const — не константный итератор. Сам объект объявлен как const, но им можно воспользо- ваться для изменения значения, на которое он указывает. Мы могли бы выразить различия между константным и изменяемым итераторами, разбив Однонаправленный Итератор на две разные концепции, но это привело бы к не- оправданному усложнению. )Эю пс всегда iак Обычно *р — объем. па коюрый указываем' итератор р, полому &*р — адрес лою °бьекта Вспомним хотя бы, что в C++ допускас!ся перегрузка оператора & Если & iiepei рулен, ю &»р Может означать ч ю-нибудь другое В большинстве случаев мы можем проигнорирова! в ло сообщен не
44 Глава 2. Алгоритмы и диапазоны 2.3.4. Двунаправленные итераторы Двунаправленные Итераторы (Bidirectional Iterator) допускают написание многопро- ходных алгоритмов, как и Однонаправленные Итераторы. Аналогично Однонаправ- ленным, Двунаправленные Итераторы могут быть как константными, так и перемен- ными. Из названия ясно, что Двунаправленные Итераторы поддерживают движение в обо- их направлениях. При инкременте итератор переходит к следующему элементу, а при декременте — к предыдущему, в то время как Однонаправленный Итератор должен поддерживать перемещение только вперед. Например, итератор типа node_wrap, ко- торый перемещается по односвязному списку, является Однонаправленным Итерато- ром, а итератор, который перемещается по двусвязному списку, должен быть Двунап- равленным Итератором. Алгоритмы используют Двунаправленные Итераторы, если им требуется проход по диапазону в обратном порядке или если необходимо для некоторого элемента найти предыдущий. Например, мы уже видели алгоритм сору, который копирует элементы из одного диапазона в другой. Однако чтобы скопировать элементы в противополож- ном порядке, требуется проход в обратном направлении по одному из диапазонов ввода или вывода: Template <class Bidirectionallterator, class OutputlteratoO Output Iterator reverse_copy(BidirectionalIterator first, Bidirectionallterator last, Outputiterator result) { while (first '= last) { --last, * result = *last, ++result } return result, } Необходимость прохода в обратном направлении одновременно и в диапазоне ввода, и в диапазоне вывода отсутствует — достаточно только в одном из них. Мы выбрали диапазон ввода, чтобы интерфейс reverse_copy был похож на интерфейс сору. Диапазоны ввода и вывода задаются тремя итераторами, а не четырьмя; выходной диапазон состоит из Итераторов Вывода. В reverse_copy мы декрементируем last, а не инкрементируем first. Двунаправ- ленный Итератор поддерживает все операции Однонаправленного Итератора, но кро- ме operator++ он предоставляет еще и operator--. Обратите внимание на различие между reverse_copy и сору: в reverse_copy мы декрементируем last перед разымено- ванием, что может показаться немного странным, но это совершенно естественно. Такая необходимость возникает потому, что диапазон [first, last) асимметричен. 1 о есть last не принадлежит этому диапазону, а указывает за его конец. Разыменова- ние такого итератора недопустимо, а вот декремент вполне определен. 2.3.5. Итераторы произвольного доступа Каждая из четырех указанных концепций итераторов обеспечивала лишь небольшое подмножество операций арифметики указателей: Итератор Ввода, Итератор Вывода
2.4. Развитие концепций 45 и Однонаправленный Итератор определяют только ope rat о г++, а Двунаправленный Ите- ратор добавляет только operator--. Последняя концепция итераторов STL, Итератор Произвольного Доступа (Random Access Iterator), использует все оставшиеся опера- ции арифметики указателей: сложение и вычитание (р + лир-л), индексирование (о[л]), вычитание одного итератора из другого (р1 - р2) и упорядочение (р1 < р2). Итераторы Произвольного Доступа важны для алгоритмов типа sort (с. 293), по- скольку при сортировке требуются операции сравнения и перестановки значений элементов, находящихся на расстоянии друг от друга, а не только соседних. На первый взгляд кажется, что Итераторы Произвольного Доступа необходимы. Вы можете написать р + л, если р — указатель, поэтому, очевидно, должна существовать концепция итератора, выполняющего такую операцию. Однако если задуматься над этим вопросом более основательно, то можно усомниться, нужно ли вообще опреде- лять Итератор Произвольного Доступа. Большинство операций Итератора Произволь- ного Доступа может быть определено в терминах operateг++ и operator--. Например, мы можем (STL так и делает) определить функцию перехода на другой элемент та- ким образом, что advance(р, л) осуществляет столько операций инкремента Однонап- равленного Итератора, сколько необходимо. Вопрос в том, получаем ли мы нечто прин- ципиально новое от введения этих операций или же они просто “синтаксический са- хар”? И если они — всего лишь “сахар”, зачем реализовывать их для одних итераторов и не реализовывать для других? Ответ на этот вопрос зависит от характеристики, которую мы еще не рассмотрели: сложность (complexity) алгоритма. Если р — Однонаправленный Итератор, то advance(p, л) в цикле выполняет operator++. Разумно предположить, что ad vance(p, 500) будет выполняться примерно в пять раз медленнее, чем advance(р, 100). Другими сло- вами, сложность advance — O(N). Нормальные указатели работают не так. Если р — указатель, то р + 1000000 вы- числяется не медленнее, чем р +1. Другими словами, сложность арифметики указате- лей — 0(1). Доступ к одному элементу массива занимает ровно столько же времени, сколько к любому другому, и алгоритмы, работающие с массивами, основываются на этом факте. Например, quicksort — это быстрый алгоритм сортировки массивов имен- но потому, что есть возможность доступа к произвольному элементу массива за кон- стантное время. Нет смысла использовать quickso rt со структурами данных типа связ- ных списков, где единственный способ получения произвольного элемента — поэле- ментный проход по списку. Отличительной характеристикой Итератора Произвольного Доступа является воз- можность прямого доступа к произвольным элементам за константное время. Разни- ца в определениях Итератора Произвольного Доступа и Двунаправленного Итератора отражает этот факт. 2.4. Развитие концепций Двунаправленный Итератор удовлетворяет всем требованиям Однонаправленного Ите- ратора и некоторым дополнительным; с другой стороны, любой тип, являющийся моделью Двунаправленного Итератора, также является моделью Однонаправленного Итератора. Эта взаимосвязь между Двунаправленным Итератором и Однонаправленным Итера- тором довольно часто встречается и достаточно важна — определим ее как развитие
46 Глава 2. Алгоритмы и диапазоны концепции Refinement). Концепция К2 является развитием концепции К1, если К2 пре- доставляет всю функциональность К1 и, возможно, некоторую дополнительную. Моделирование и развитие имеют три жизненно важных свойства, которые легко проверить, рассматривая концепции в качестве набора типов: 1. Рефлексивность (reflexivity). Любая концепция К является развитием самой себя 2. Включение (containment). Если тип X является моделью концепции К2, а К2 яв- ляется развитием концепции К1, то X также является моделью К1. 3. Транзитивность (transitivity). Если КЗ является развитием К2, а К2 является раз- витием К1, то КЗ является развитием К1. Практический вывод состоит в том, что если алгоритм требует, чтобы его парамет- ры составляли модель некоторой концепции К1, то всегда можно использовать тип, являющийся моделью концепции К2, при условии, что К2 является развитием К1, — ото, в общем и целом, просто замысловатый способ сказать то, что и так понятно... Например, алгоритм find требует, чтобы параметр шаблона был Итератором Ввода. Но вы уже знаете, что можете передать ему для поиска указатели, а тип указателя является моделью Итератора Произвольного Доступа. Тип может быть моделью более чем одной концепции, а концепция, в свою оче- редь, может быть развитием более чем одной концепции. Однонаправленный Итера- тор является развитием как Итератора Ввода, так и Итератора Вывода. В общем слу- чае набор концепций формирует сложную иерархию (hierarchy)**. Концепции и наследование И моделирование, и развитие — суть отношения между двумя разными явлениями. Моделирование — это отношение между типом и концепцией (тип Т является моде- лью концепции К), а развитие — это отношение между двумя концепциями (концеп- ция К2 является развитием концепции К1). Оба отношения называются “является чем-то”, но в любом введении в C++ опре- деляется другое отношение такого типа — наследование. Часто говорят, что класс D наследует от класса В, если D “является” В. Означает ли это, что моделирование и раз- витие — не что иное, как наследование, что шаблоны не нужны и что мы можем эму- . шровать обобщенное программирование средствами наследования и полиморфизма? Ответ отрицательный, а мораль рассуждения заключается в том, что следует осто- рожно относиться к фразам вроде “является чем-то”. Моделирование, развитие и на- следование (inheritance) — три разных вида отношений, и невозможно эмулировать одно из них в терминах двух других. Формальную разницу можно понять, рассмотрев определения. Каждое из отноше- ний работает на своем уровне. Наследование — это отношение между типами, моде- лирование — отношение между типом и набором типов, а развитие — отношение между двумя наборами типов. Если класс D наследует от класса В, то каждый объект типа D также является объектом типа В. Развитие же задается на уровне типов, а нс на уров- не объектов. Если концепция К2 представляет собой развитие концепции К1, го каж- дый тип, являющийся моделью К2, оказывается также моделью К1. ’Cipolo говоря се пало называть нс иерархией, а направленным ацикчиче' кчм ipa<|»o\i Пен») ii.bjb.i- iню слова иерархия бо.ice i раднционно хо!я кхничсски нскоррск!но
2.4. Развитие концепций 47 Это факт отражен на рис. 2.2. Несмотря на то что К2 — это развитие К1, а ТЗ и Т4 — мОдели К2, типы ТЗ и Т4 не обязательно связаны с Т1 и Т2, являющимися моделями К1. Например, между Т4 и Т1 отсутствуют отношения наследования. Единственное, что связывает эти четыре типа, — интерфейс; они не ограничены деталями реализации, не имеющими отношения к делу. Рис. 2.2. Иллюстрация развития и моделирования Например, Итератор Произвольного Доступа — это развитие Итератора Ввода, a char* — модель Итератора Произвольного Доступа. Однако это вряд ли означает, что char* наследует от каждого типа, являющегося Итератором Ввода! Иначе говоря, простейший способ увидеть, что наследование и моделирование не связаны друг с другом, — попытаться реализовать моделирование в терминах насле- дования. Это просто не сработает. Можно представить концепцию в виде абстракт- ного базового класса, но при этом не удастся выразить необходимые отношения. Рас- смотрим вариант реализации Присваиваемого в качестве абстрактного базового класса. Начнем, например, так: class assignable_base { public virtual -assignable_base() {} virtual assignable_base& operaior=(const assignable_base&) = 0. }. У нас уже появились проблемы. Класс, наследующий от assignable_base, не получает автоматически операции типа, который является моделью Присваиваемого. Полимор- физм тоже не помогает: сам по себе тип assignable_base не предоставляет этих опера- ций. Если обобщенная функция имеет аргументы типа, являющегося моделью При- сваиваемого, то она может присваивать значениям данного типа, создавать новые переменные этого типа и т. д. Однако полиморфная функция с аргументами типа assignable_base* или assignable_base& ничего подобного не может. Даже если у поли- морфной функции есть два аргумента типа assignable_base*, то она не вправе пола- гать, что оба они одного типа; а если все же это так, то нет никаких причин считать, Что один из аргументов можно присвоить другому. Понятие Присваиваемого полезно в качестве концепции, но бесполезно в качестве абстрактного базового класса. Концепции вроде Итератора Ввода, включающие в себя более чем один тип, со- здают более сложные проблемы. Каким образом понятие типа значения итератора
48 Глава 2. Алгоритмы и диапазоны может выражаться в терминах наследования и полиморфизма? Типы double* и node_wrap<int_node> являются моделями Итератора Ввода, но тип значения одного из них — double, а другого — int_node. Вряд ли можно рассматривать их в качестве наследников одного и того же базового класса. Ничто из вышеперечисленного не отрицает важности или полезности наследования и полиморфизма. Просто необходимо понимать, что наследование и моделирование полезны в разных случаях. Наследование, моделирование и развитие представляют собой совершенно разные отношения, и любая методология, пытающаяся опираться только на какое-либо одно из них, неполна. 2.5, Заключение Раздел 2.3 начался с определения итераторов в качестве обобщения указателей, и мы наконец увидели, что это означает. Указатели можно обобщить разными способами, в зависимости от того, какие свойства мы абстрагируем. Соответственно, итераторы являются не единственной концепцией, а семейством концепций, связанных разви- тием. Семейство проиллюстрировано на рис. 2.3. Итератор Ввода Итератор Вывода Однонаправленный Итератор Двунаправленный Итератор Итератор Произвольного Доступа Рис. 2.3. Иерархия концепций итераторов Итератор Ввода и Итератор Вывода — наиболее ограниченные по своим возможно- стям концепции. Один предназначен только для чтения, а другой — только для запи- си; оба поддерживают прямой проход по диапазону и позволяют реализовать про- стые однопроходные алгоритмы. Однонаправленные Итераторы тоже поддерживают прямые проходы по диапазону, но помимо этого они поддерживают и более общие виды алгоритмов. Однонаправленный Итератор “указывает” на некий объект, в том же смысле, что и обыкновенный указатель Си. Формально это означает, что в резуль- тате разыменования Однонаправленного Итератора получаем значение, которому можно присвоить lvalue. Гак как Двунаправленный Итератор является развитием Однонаправленного Ите- ратора, а Итератор Произвольного Доступа является развитием Двунаправленного Ите- ратора, то и Двунаправленный Итератор, и Итератор Произвольного Доступа поддер- живают проходы в прямом направлении, а также обычную модель памяти Си. Прав- да. они обеспечивают дополнительные формы прохода. Двунаправленный Итератор допускает проходы в обратном направлении, а Итератор Произвольного Доступа, как и следует из его названия, позволяет скачки произвольных размеров.
2.5. Заключение 49 различные концепции итераторов обеспечивают естественный способ классифи- кации обобщенных алгоритмов на диапазонах. Можно разбить алгоритмы по катего- риям в зависимости от того, какие итераторы они используют. Еще одно немаловаж- ное различие существует между немодифицирующими (nonmodifying) алгоритмами, которые пользуются константными итераторами, и изменяющими (mutating), кото- рые пользуются изменяемыми итераторами. Все концепции итераторов полностью документированы в седьмой главе. Мы на- меренно не привели пока список требований к каждой из пяти концепций итерато- ров. Хотя мы и обсуждали основные свойства итераторов, есть еще одно требование, о котором до сих пор не было речи.
3 Подробнее об итераторах В главе 2 были представлены обобщенные алгоритмы для диапазонов, итераторы и пять концепций итераторов STL: Итератор Ввода, Итератор Вывода, Однона- правленный Итератор, Двунаправленный Итератор и Итератор Произвольного Досту- па. Однако это не полное введение в предмет, поскольку не были рассмотрены неко- торые вопросы, важные для написания классов итераторов или обобщенных алго- ритмов. Они и составляют предмет обсуждения третьей главы. 3.1. Признаки итераторов и ассоциированные типы В разделе 2.2 мы видели, что определение концепции может включать в себя ассоци- ированные типы (associated types). Требования к типу Т — это список выражений, содержащих Т, а эти выражения могут использовать или возвращать еще и другие типы. Упомянутые пять концепций итераторов STL включают в себя ассоциирован- ные типы, и эти типы составляют важную часть требований к итераторам. 3.1.1. Типы значений Мы уже видели, что каждый итератор (за исключением Итератора Вывода) имеет по крайней мере один ассоциированный тип. Итератор указывает на некий объект, 1 ип которого и есть тип значения (value type) итератора. Проблема в том, что мы пока не знаем, как обратиться к типу значения. Предполо- жим, что у нас есть тип итератора I и нам нужно объявить переменную, типом которой является “тип значения I”. Это самый обычный сценарий. Очевидно, многим алгорит- мам нужны врёменные переменные. (Представьте себе, например, обобщенный алго- ритм, который вычисляет среднее и стандартное отклонение набора чисел.) Однако фраза “тип значения I” не говорит нам, как именно выразить этот тип в языке С+х. Иногда мы можем использовать прием с применением стандартного для языка C++ механизма логического выведения типа (type inference mechanism). Допустим, что есть обобщенная функция f (), которая получает параметр типа I, и нужно объявить вре- менную переменную, тип которой является типом значения I. Язык С+ ь не позволя-
3 1. Признаки итераторов и ассоциированные типы 51 “ gT нам записать непосредственно typeof (*1), но есть способ обойти это ограничение, доожно превратить f () в простую функцию-посредник, уступая всю реальную рабо- ту внутренней функции ): template <class I, class T> void f_impl(I iter. T t) { T tmp /7 1 является типом значения I } template <class I> inline void f(I iter) { f_impl(iier, *iter), В пределах внутренней функции f _impl мы можем объявить переменную того же типа, что и тип значения I. Вывод типа параметров шаблона срабатывает автоматически, и таким образом, внутри f_impl параметр шаблона Т есть синоним для типа, возвра- щаемого *iter. Это ловкий и полезный прием. В одном из мест STL используется его вариант К сожалению, этого не всегда достаточно. Прием адекватен (пусть и немного неуклюж) в случае, когда нам нужно только объявить временные переменные, но с его помо- щью нельзя объявить тип значения, возвращаемого функцией. Проблема, собствен- но, состоит в том, что тип, возвращаемый функцией f (), должен быть объявлен не- посредственно в f (), а вывод типа не позволяет нам распознать его, пока мы не до- брались до f_impl(). Второй метод заключается в объявлении типа значения внутри класса итератора. Язык C++ разрешает объявления вложенного типа (nested type); так, например, мы могли бы пополнить класс итератора node_wrap (раздел 2.1.3) оператором typedef. template <class Node> struct node_wrap { typedef Node value_type, Node* ptr }. Если тип I — это node__wrap, то такое вложенное объявление типа позволяет нам записать тип значения I в виде typename I • • value_type . Кажется, это то, что надо. Однако мы не можем требовать, чтобы все итераторы объявляли вложенные типы Это оыло бы естественно для классов итераторов типа node_wrap. но что делать с ите- раторами, которые не являются классами? Указатели — это итераторы, по они не являются классами. Если I — это, скажем, int*, то невозможно сделать так, чтобы ;value_type стал типом int. Положившись на вложенные типы, мы нанесли бы доданы использовать немкою странное ключевое с лоно typename, но i ому чю, когда I — на- рамстр шаблона комничятор нс распошаег I до тех пор, пока шаблон не ппстапцирован В частное in. к°М||иляюр не pacnoMiaci. является ли I value_type именем пша. меюда и ш ноля См paue i А 15 Раздел С 13 5 в книге Я ня к программирования C+ + |Stг971. 1де описаны дссали
52 Глава 3. Подробнее об итераторах ___________ поражение самой идее использования итераторов. Указатели являются моделями Итератора Произвольного Доступа, и мы преднамеренно использовали указатели в ка- честве образца для других итераторов. Любые требования, которые мы накладываем на итераторы, должны быть выполнимы для указателей. Мы можем решить проблему, добавив еще один уровень косвенности. Определим вспомогательный класс не rator_t raits: template <class Iterator> struct iterator_traits { typedef typename Iterator value_type value_type, } Затем мы можем обратиться к типу значения итератора I, написав следующее: lypename iterator_traits<I> value_type Это не похоже на улучшение, так как iterater_t raits все же предполагает, что пара- метр шаблона Iterator имеет вложенный тип. Тем не менее это действительно усо- вершенствование, потому что мы можем специализировать (to specialize) класс terator_traits, обеспечивая заменяющее определение для некоторых конкретных параметров шаблона. Язык C++ разрешает как полную специализацию (обеспечивая заменяющее опре- деление для некоторых особых типов, таких как int*), так и частичную специализа- цию (обеспечивая заменяющее определение, которое само является шаблоном). В этом случае мы должны использовать частичную специализацию, так как нам нужно заме- няющее определение для каждого типа указателя: template <class Т> struct iterator_traits<T*> { typedef T value_type, }. Вот почти и все: теперь мы можем единообразно ссылаться на типы значений всех итераторов, включая указатели, но необходимо прояснить еще одну маленькую де- таль: каков тип значения у константного итератора? Рассмотрим пример: iterator,,!ra]ts<const int*> value_type Но нашему определению класса iterator_traits, искомый тип — const int, а не int. Дело в том, что мы определили iterator_t raits для параметров типа Т*, a const int* соответствует Т* только тогда, когда Т — это const int. К сожалению, это не то, что нам нужно. Наша цель состоит в том. чтобы объявить временные переменные, тип кото- рых совпадает с типом значений итератора, а от временной переменной мало пользы, если ей нельзя присвоить значение. Тип значения итератора, даже если это констант- ный итератор, не должен быть квалифицирован как const. Тип значения итератора вроде const int* — это int, а не const int. Добавка const — просто ограничение на операции, которые можно выполнять с помощью самого итератора. 1 Trails — черты, признаки. По поводу происхождения этого термина см станио Nathan С Mvris http //www cantrip org/traits html — Примеч ped
3.1. Признаки итераторов и ассоциированные типы 53 Эту проблему можно легко решить с помощью другой частичной специализации: template <class Т> struct iterator_traits<const Т*> { typedef Т value_type, Теперь у нас есть три различные версии iterator_t raits: одна для параметров типа Т, другая Для параметров типа Т* и третья для параметров типа const Т*. Когда вы пишете iterator_traits<const int*>, то в принципе этот тип мог бы соответствовать любой из трех версий, но никакой неоднозначности не возникнет, потому что компи- лятор языка О+ всегда выбирает самое специфическое совпадение, какое только воз- можно. Итак, у нас появился механизм, который позволяет записывать алгоритмы, использующие тип значения итератора. Например, следующая обобщенная функция вычисляет сумму чисел в непустом диапазоне: template <class Inputlterator> typename itera1or_traits<lnputlterator> value_type sum_nonempty(lnputlterator first. Inputiterator last) { typename iterator_traits<lnputlterator> value_type result = *first++; for ( , first ,= last, ++first) result += * first. return result, } Функция действует с помощью построенного нами механизма iterator_traits, ко- торый работает для указателей, потому что есть специализированные версии *iterator_traits<const Т*> и iterator_traits<T*> для итераторов, определяющих value_type как тип, вложенный в класс итератора. Если I — это Итератор Ввода (включая концепции, являющиеся развитием Ите- ратора Ввода), то требуется, чтобы iterater_traits<I>: : value.type был типом значе- ния I. Значит, если вы определяете новый класс итератора, то должны быть уверены, что ваш класс поддерживает iterator_traits. Самый простой способ сделать это — определить value_type как вложенный тип вашего класса. При этом не придется явно упоминать iterator_t raits, а класс iterator_t raits, определенный в библиотеке, бу- дет работать правильно без дополнительных усилий с вашей стороны. Если по ка- ким-то причинам это невозможно или неудобно, то вы можете специализировать iterator ! raits для вашего класса так же, как мы это сделали для указателей. Очевидно, механизм iterator_t raits является вполне общим. Мы видели, как он работает для типов значений итераторов, но в более общем случае он может быть ис- пользован для любого отображения типа итератора на тип, который с ним ассоцииро- ван. Концепции итераторов из раздела 2.3 неявно имеют несколько ассоциирован- ных типов, и STL использует iterater_traits для всех. 3.1.2. Разностный тип Если I является моделью Итератора Произвольного Доступа, а р1 и р2 — значения типа I» то р2-р 1 — это расстояние (distance) между pl и р2. Чтобы использовать расстояние в программе, нужно знать его тип. Какой тип имеет выражение р2-р1?
54 Глава 3. Подробнее об итераторах Для указателей ответ прост. Когда вы вычитаете один указатель из другого, исполь- зуется тип ptrdiff.t Одним из возможных вариантов могло бы быть требование, чтобы не только для указателей, но и для всех Итераторов Произвольного Доступа выражение р2-р1 имело бы тип ptrdiff_t. Ответ прост, но он накладывает слишком много ограничений. Что если у вас диа- пазон итераторов настолько велик, что число содержащихся в нем элементов не по- мещается в pt rd 1 f f_t? В некоторых ситуациях, например в случае с файлами, это вы- зывает серьезное беспокойство. Указатели — значения длиной в 32 бита на большин- стве современных процессоров, поэтому pt rdif f_t обычно представляет числа до 231. Хотя 2 п (более двух миллиардов) кажется немалым числом, в действительности это не так. Большинство операционных систем позволяют создавать файлы, размер ко- торых намного превышает названную величину, и терабайтовые базы данных сегод- ня уже широко распространены. Если вы определяете итератор, который проходит по очень большому файлу, то pt rdiff_t будет недостаточно. Итак, для общих Итераторов Произвольного Доступа р2-р1 не обязательно являет- ся значением типа pt rdif f_t. Это выражение имеет некий целочисленный тип со зна- ком: разностный тип (difference type) итератора. Все Итераторы Ввода имеют разностный тип. Когда мы пишем р2-р1, то в результа- те просто вычисляем количество элементов в диапазоне [р1, р2), что можно обобщить на Итераторы Ввода. (На Итераторы Вывода это не распространяется, поэтому Итера- тор Вывода не обязательно имеет разностный тип.) Некоторые алгоритмы, функционирующие на диапазонах итераторов, должны об- ращаться не только к типу значений, но и к разностному типу итераторов. Один из самых простых подобных алгоритмов — это count (с. 222), который подсчитывает число появлений значения х в диапазоне [f i rst, last). Ясно, что возвращаемый тип должен быть разностным типом итераторов, так как значение, возвращаемое count, может рав- няться расстоянию от первого до последнего значения. Очевидное решение состоит в том, чтобы использовать один и тот же механизм как для dif ference_type, так и для value_type. Мы определяем dif ference_type как вложенный тип в классах итераторов и включаем difference_type в iterator_traits, чтобы можно было ссылаться на раз- ностный тип каждого итератора одним и тем же способом: template <ciass Inputiterator, class T> lypename iterater_traits<lnputlterator> difference_type count(lnoutlterator first, Inputiterator last, const T& x) i typename iterator_traits<lnputlterator> difference_type n = 0. for ( . fust ! = last, ++fust) if (*first == x) +_un return n. Исторически одной из основных причин введения iterater_t raits стала необхо- димость обеспечить механизм для объявления типа, возвращаемого функцией count. )Тип ртгdif f t определен в стандартах языков Си и C++ Это не отдельный самоеюятельпый гин. а скорее typedef для некоторого другого знакового целочисленного тина, обычно irt или long.
3.1. Признаки итераторов и ассоциированные типы 55 3 1.3. Ссылочный тип и тип указателей Однонаправленный Итератор указывает на некоторое место в памяти. Если р — это Однонаправленный Итератор, который указывает на объекты типа Т, то выражение * р обычно не может вернуть значение типа Т. Вместо этого *р должно вернуть lvalue, то есть “нечто, ссылающееся на объект”, если использовать определение Страуструпа [Str97]. В языке C++ функция может вернуть lvalue, возвращая ссылку. Возврат по ссыл- ке и является причиной того, что Однонаправленный Итератор может обеспечивать как чтение, так и запись объекта, на который он указывает. Если р - это изменяемый итератор, тип значения которого — Т, то *р обычно будет иметь тип Т&. Аналогично, если р является константным итератором, то *р обычно будет иметь тип const Т&. Во- обще говоря, тип выражения *р не всегда совпадает с типом значения р. На самом деле *р возвращает тип ссылки (reference type) на р. Указатели и ссылки в языке C++ тесно связаны. Если можно возвратить lvalue, ссылающееся на объект, на который указывает р, то, очевидно, можно ссылаться па адрес этого объекта, то есть мы должны уметь возвратить указатель на этот объект. Эти типы уже появились в простом классе node_>rap (раздел 2.1.3). Метод operator* возвращает Node&, а метод operators , который обеспечивает доступ к полям Nooe, возвращает Node*. Ссылочный тип в этом случае - Node&, а тип указателя (pointer type) — Node*. Эти ассоциированные типы также определены в iterator t raits. Два вложенных типа: reference (ссылка) и pointer (указатель) определены так же, как value Дуре и difference_type. 3.1.4. Алгоритмы диспетчеризации и теги итератора Последний из типов, определенных в iterator_traits, является более абстрактным. Чтобы понять это, мы должны рассмотреть не итераторы сами по себе, а алгоритмы, которые, казалось бы, никак не связаны с ассоциированными типами итераторов. Зачастую алгоритм имеет разумное определение для одной концепции итератора, но существует и другой способ, учитывающий развитие этой концепции. Например, предположим, что мы указали алгоритм для Однонаправленных Итераторов. Он .мо- жет быть использован для Итераторов Произвольного Доступа, так как каждый тип, который является моделью Итератора Произвольного Доступа, есть также модель Од- нонаправленного Итератора. Однако то, что он пригоден для использования, не озна- чает его оптимальности. Иногда мы можем написать более удачную версию специ- ально для Итераторов Произвольного Доступа, которая использует, например, такие °гооенности, как operator< и operator+= с линейной сложностью вычисления. Один из простейших примеров — это функция advance, упомянутая в разделе 2 3 5 ама по себе она не очень интересна, но другие алгоритмы STL используют ее в каче- стве примитива. Функция advance получает два аргумента: итератор р и число п, и п раз инкрементирует р. Фактически есть три различных определения advance: для Итерато- ров Ввода, для Двунаправленных Итераторов и для Итераторов Произвольного Доступа Оператор -> нс обеспечивает какой-либо дополни гелык)й функциональности сверх юго, чю об<\ >чИвае| .prrator* Выражение p->val - это только сокращенная щнись для (*р) val Oi Итераторов °Да |рсб\'ется, чюбы они обеспечивал и и operator-> и operator*
56 Глава 3. Подробнее об итераторах template <class Inputiterator, class Distance> void advance_II(InputIterator& i. Distance n) { for ( , n > 0, --n. ++i) {} } template <class Bidirectionallterator, class Distance> void advance_BI(BidirectionalIterator& i, Distance n) if (n >- 0) for ( , n > 0, --n, ++i) {} else for ( , n < 0, ++n, --1) {} } template <class RandomAccessIterator, class Distance> void advance_RAI(RandomAccessIterator& i, Distance n) { i += n, } Реализация для Итераторов Ввода декрементирует п до тех пор, пока мы не дойдем до нуля. Реализация для Двунаправленных Итераторов по существу та же самая, однако она позволяет нам “продвигаться вперед” на отрицательные расстояния. Наконец, ре- ализация для Итераторов Произвольного Доступа использует operator+=. Мы не обес- печили отдельное определение для Однонаправленных Итераторов, потому что нет причин использовать для них какую-то другую реализацию, отличающуюся от реа- лизации для Итераторов Ввода. Которую из трех реализаций следует использовать для примитива advance? К со- жалению, выбор сделать невозможно. Выбор advance_II был бы чрезвычайно неэф- фективен для Итераторов Произвольного Доступа: операция, которая должна выпол- няться за время 0(1), выполнялась бы за время O(N). С другой стороны, выбор advance_RAI вообще делал бы невозможным использование advance с Итератором Ввода Нам нужен какой-то способ объединить эти три реализации в одну. Попробуем сделать так: template <class Inputiterator, class Distance> void advance(lnputlterator& i, Distance n) if (is_random_access_iterator(i)) advance_RAI(i, n), else if (is_bidirectional_iterator(i)) advance_BI(i. n) else advance_II(i n). } Конечно, мы не можем реализовать advance таким образом. Выбирать между тремя реализациями во время выполнения — слишком поздно. Мы должны выбрать пра-
3 1. Признаки итераторов и ассоциированные типы 57 ильную реализацию во время компиляции. Фактически мы пытаемся здесь обобщить нечто, хорошо знакомое каждому программисту на C++, — перегрузку функций. В языке C++ можно определить несколько различных функций, имеющих оди- наковое имя, но различные типы аргументов. Когда вы вызываете перегруженную функцию, компилятор выбирает версию, соответствующую типам, с которыми она вызывается. Мы пытаемся сделать нечто подобное и здесь, но перегружаем скорее по концепциям, а не по типам. Язык C++ непосредственно не поддерживает перегрузку по концепциям, потому что они не являются частью языка, но мы можем эмулировать это в терминах обыч- ной перегрузки, если найдем способ представить концепции в рамках системы типов языка C++. Сначала задается набор “типов-заглушек” (placeholder types), по одному для каж- дой концепции итераторов. Неважно, какие это типы, — главное, чтобы они были уни- кальны. Второй шаг определяет три реализации функции advance с одним именем, перегру- женной по этим типам тегов. Перегруженная функция будет выбирать нужную из трех функций advance_II, advance_BI и advance_RAI: template <class Inputiterator, class Distance> void advance(lnputlterator& i, Distance n, input_iterator_tag) { // Та же реализация, что и в advance_II } template <class Forwarditerator, class Distance> inline void advance(ForwardIterator& i, Distance n, forward_iterator_tag) { advance(i n, input_iterator_tag()), } * template <class Bidirectionallterator, class Distance> void advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) { // Та же реализация, что и в advance_BI } template <c]ass RandomAccessIterator, class Distance> void advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) { // Та же реализация, что и в advance RAI } Мы ничего не делаем с аргументами “tag”. Это просто заглушки, которые нужны лищь для различения версий advance. Обратите внимание на то, что теперь у нас есть Реализация для Однонаправленных Итераторов. Ясно, что Однонаправленные Итера- т°Ры используют ту же реализацию, что и другие Итераторы Ввода. Наконец, мы должны написать функцию верхнего уровня, которая вызывает пере- груженную функцию advance. Она получает два аргумента: итератор i и расстояние п,
58 Глава 3. Подробнее об итераторах и передает их функции advance вместе с третьим аргументом: одним из пяти типов тегов. Функция верхнего уровня должна уметь вывести тип тега для данного типа итератора. Это та же самая проблема, которую мы рассматривали в данном разделе, и она имеет то же самое решение. У каждого итератора кроме типа значения есть ассоциирован- ный тип категории итератора. Это тип тега, который соответствует наиболее специ- фичной концепции (most specific), смоделированной итератором. Например, int* -- это модель Итератора Произвольного Доступа, Двунаправленного Итератора, Однонап- равленного Итератора, Итератора Ввода и Итератора Вывода, но его категория — это andom_access_iterator_tag. Подобно другим ассоциированным типам, iterator_category определен как тип, вложенный внутрь iterator traits. Это позволяет нам записывать advance и другие алыртмы, которые должны различать категории итераторов: template <class Inputlter class Distance> joii-'e void advance(lnputlter& i, Distance n) i advance(i n typename iterator_traits<lnputlter> iterator_category()), STL определяет пять типов тегов в виде пустых классов: struct input„ilerator_tag {}, struct output_iterator_tag {}, -t-ruci forward_irerator_tag public input_iterator_tag {}, su'ijct bidi reel ional_iterator_tag public forward_iterator_tag {}, struct random_access_iterator_tag public bidirectional_iterator_tag {}, Тот факт, что данные типы тегов являются классами, а классы используют наследо- вание, не имеет большого значения. В типах тегов важна их уникальность, поэтому они могут использоваться для перегрузки функций. Использование наследования удобно. Например, мы написали четыре версии advance, но только три из них разли- чаются. Версия для Однонаправленных Итераторов просто вызывает версию д тя Ите- раторов Ввода. Так как forward_iterator_tag наследует от input_iterator_tag, то мы можем опускать такие тривиальные функции пересылки. 3.1.5. Общее обозрение 11екоторые следствия введения механизма ite rater_t raits трудноуловимы, но его ре- ализация совершенно проста: template <class Iterator> siiurt iterdror_traits { typedc* typename Iterator typedef lypename Iterator typedef lypename Iterator typedef typename Iterator typeoef typename Iterator iterator_category iterator_category, value_type value_type, difference_type difference_type, pointer pointer, reference reference, template <class T> sHiic! iteraior_i гаиs<T”> {
3.1 Признаки итераторов и ассоциированные типы typedef random_access_iterator_tag iterator_category, typedef T value_type, typedef ptrdiff_t difference_type, typedef T* pointer, typedef T& reference, template <class T> struct iterator_traits<const T*> { typedef random_access_iterator_tag iterator_category, typedef T value_type; typedef prrdiff_t difference.type, typedef const T* pointer, typedef const T& reference; Вам придется позаботиться об этом механизме лишь единожды, когда вы определя- ете свои собственные итераторы или алгоритмы. Можно сформулировать две причи- ны обращения кiterator_traits. • Необходимо вернуть значение или объявить временную переменную, тип кото- рой совпадает с типом значения, разностным типом, ссылочным типом или ти- пом указателя итератора. • Ваш алгоритм похож на advance тем, что должен использовать различные реали- зации в зависимости от категории, к которой принадлежит итератор. Без этого механизма пришлось бы решать трудную задачу — выбрать общую, но неэффек- тивную или эффективную, но чрезмерно ограниченную реализацию. Некоторые алгоритмы STL используют такой механизм, поэтому всякий раз, когда вы определяете новый итератор, убедитесь в том, что он правильно работает с iterater_traits. К счастью, это нетрудно. Когда вы определяете новый класс итератора I, следует либо определить внутри этого класса пять вложенных типов: iterator_category, value._type, difference_type, pointer и reference, либо явно специализировать iterator_traits для вашего класса I так же, как iterator_traits был специализирован для указателей. Первый вариант почти всегда проще, и в STL есть вспомогательный базовый класс iterator, макси- мально упрощающий задачу: template <class Category, class Value, class Distance = ptrdiff_t, class Pointer = Value*, class Reference = Value&> struct iterator { typedef Category iterator_c typeaef Value value_type typedef Distance difference typedef Pointer pointer, typedef Reference reference, iterator_category, value_type, difference_type pointer,
60 Глава 3. Подробнее об итераторах Гарантией правильного определения nerator irails для нового класса итератора I является наследование I от iterator. Базовый класс iterator не содержит ни мето- дов, ни полей, так что наследование от него не трудоемко 3.1.6. Признаки итераторов без iterator_traits Класс iterator t raits — относительно недавнее изобретение и потому не вошел в пер- воначальную версию HP STL [SL95]. В HP STL была система обозначений для типов значений, разностных типов и категорий итераторов с другим (более неуклюжим) механизмом доступа к ним. Этот механизм больше не входит в стандарт языка C++, но многие реализации STL все еще поддерживают его для совместимости с предыду- щими версиями. Старый механизм представлял собой набор функций, запрашивающих итератор: distance_type, value_type и iterator_category. Каждый из этих запросов имел один аргумент — сам итератор, а информация о типе, ассоциированном с итератором, была закодирована в типе значения, возвращаемого функцией. Функция iterator_category получает единственный аргумент — итератор — и воз- вращает метку, соответствующую категории этого итератора. Таким образом, она воз- вращает значение типа random_access_iterator_tag, если ее аргумент — модель Ите- ратора Произвольного Доступа, или значение типа bidirectional_iterator_tag, если ее аргумент — модель Двунаправленного Итератора, и т. д. Аналогично, если 1 — ите- ратор, у которого тип значения V и разностный тип D, то value_type(i) возвращает некоторое значение типа V*, a dif f е rence_type( i) возвращает некоторое значение типа D*. Во всех трех случаях фактическое возвращаемое значение неважно. Главное — тип значения. Эти функции менее удобны, чем iterator_t raits, потому что не обес- печивают типы непосредственно. При использовании iterator^ raits, например, мож- но легко написать функцию, которая меняет местами значения двух итераторов: template <class Forwardlteratorl, class Forwardlterator2> void iter_swap(Forwardlteratorl p, Forwardlterator2 q) { typename iterator_traits<Forward!terator1> value_type tmp = *p, * P = *q, * q = tmp, } В старом механизме нет способа объявить tmp напрямую. Можно только разбить код на две отдельные функции: template <class Forwardlteratorl, class Forwardlterator2, class T> void iter_swap_impl(Forwardlteratorl p, Forwardlterator2 q, T*) { T tmp = *p, * P = *q. * q = tmp, } template <class Forwardlteratorl, class Forwardlterator2> void iter_swap(Forwardlteratorl p, Forwardlterator2 q)
3.2. Определение новых компонент 61 1 iter_swap_irnpl(p, q, value_type(p)), } g оригинальной версии HP STL эти три функции предусматривались для каждо- го типа итератора. При определении нового итератора простейший способ удосто- вериться в том, что три функции были определены, состоял в наследовании класса итератора от одного из пяти базовых классов: input_iterator, output-iterator, forward-iterator, bidirectional_iterator или random_access_iterator. Три функции запроса итератора и пять базовых классов итераторов более не явля- ются частью стандарта языка C++. Функции запроса итератора были заменены клас- сом iterator_t raits, а базовые классы итераторов — одним базовым классом iterator. Многие реализации STL продолжают обеспечивать старый механизм для совмести- мости с ранними версиями, что можно увидеть в старом коде. 3.2. Определение новых компонент STL сконструирована так, чтобы быть расширяемой. Вы можете использовать суще- ствующие алгоритмы STL с новыми итераторами, которые напишете сами, а суще- ствующие итераторы — с новыми алгоритмами. Необходимость возможности расширения (extensibility) уже очевидна. Как итера- торы, так и алгоритмы написаны в терминах требований к итераторам. Алгоритм STL, который работает с диапазоном Однонаправленных Итераторов, использует свойства только в определении концепции Однонаправленного Итератора. В свою очередь, ите- ратор, который моделирует Однонаправленные Итераторы, должен обеспечить все функциональные возможности, требуемые этой концепцией. Поскольку концепция итератора используется в качестве интерфейса как алгорит- мами, так и итераторами, вам следует использовать ее для проверки всякий раз, когда вы пишете новый алгоритм или итератор. Концепция задает контрольный список Функциональных возможностей, которые вы должны либо обеспечить в итераторе, либо использовать в алгоритме. В главе 7 детально описаны концепции итераторов. На базовом уровне итератор должен делать две вещи: указывать на объект и переме- щаться по диапазону. Обычно как только вы определили operator* и operator++, ос- тальные операции итератора записываются очень легко. Лишь в одном месте нужно проявить некоторую осторожность. Необходимо удо- стовериться, что итераторы правильно определены как константные или изменяемые Именно здесь часто кроется причина ошибок. Для Итераторов Ввода, предназначен- ных только для чтения, или для Итераторов Вывода, которые предназначены только Для записи, это обычно не представляет проблемы, но для других видов итераторов Необходима осторожность. Например, класс node_wrap (раздел 2.1.3) является изменяемым итератором. Он по- ляет изменять значение, на которое указывает. “Корректность константности” краине важна в языке C++. Const-указатели нужны именно для того, чтобы невоз- л ж Н° бЬ1Л0 случайно изменить структуру данных, которая должна оставаться немо- ^Фицированной. Когда вы определяете итератор, проходящий по структуре данных, ° °бычно задаете не единственный итератор, а пару итераторов: константный и из- Чяемый, с одинаковым типом значения и с возможностью преобразования из изме- еМого в константный (но не наоборот).
62__________________________Глава 3. Подробнее об итераторах________________________ Можно предположить, что такие действия не нужны, поскольку итератор типа г cde_v/r ар в состоянии выполнять обе работы одновременно — и как константный, и как изменяемый, но это не так. Есть два места, где можно было бы написать const, и оба раза ошибочно. Итератор const node_wrap<T> не константный. Отличие состоит в возможности использовать его для изменения значения, на которое он указывает, а так как operator* возвращает Т&, а не const Т&, то ответ положительный. Больше под- ходит noae_wrap<const Т> (по крайней мере, operator* возвращает const Т&), но и это неправильно. Тип значения node_wrap<const Т> был бы const Т, но, как мы видели в раз- деле 3.1.1, тин значения даже константного итератора не должен быть указан как const. Тин значения, тип ссылки и тип указателя изменяемого итератора node_wrap<T> - ого, соответственно, Т, Т& и Т*. Для комплементарного константного итератора необ- ходимы типы т, const Т& и const Т*. Таким образом, нужны два отдельных класса для константной и изменяемой версий, хотя мы можем использовать шаблоны или на- следование, чтобы выделить идентичные части двух классов. Типы указателя и ссылки представляют собой самое важное различие между кон- стантной и изменяемой версиями. Можно предположить, что мы обошлись бы одним классом итератора, который получает эти типы в виде параметров шаблона. Другое различие между версиями заключается в том, что можно создать константный ите- ратор из изменяемого, но не наоборот. Есть очень простой (однако филигранный) способ решения проблемы: определить конструктор, параметром которого является изменяемый итератор. Для некоторых значений параметров шаблона это конструк- тор копирования, а для других — конструктор преобразования, который нам и ну- жен. Ниже предлагается полное определение классов итераторов node_wrao и const.node_wrap Оба класса соответствуют всем требованиям к Однонаправленно- му Итератору. Я полностью проверяю это соответствие в главе 7. Вам следует делать го же самое, когда вы определяете новый класс итератора. template <clas$ Node, class Reference, class Pointer> struct node.urap_base public iterator<forward_iterator_tag, Node, ptrdiff.t, Pointer. Reference> i typedef node_wrap_base<Node. Node&, Node*> iterator, typedef node_wrap_base<Node, const Node&. const Node*> const-iterator. Pointer ptr. ncde_’//r ap_base(Pointer p = 0) ptr(p) {} node_wrap_base(const iterator& x) ptr(x ptr) {} P'-ferenct operator*() const { return *ptr, } Pointer cne'ator->() const { return ptr, } void inc4) { ptr = ptr->next, } bool operatoi—(const node_wrap_base& x) const { return ptr == x ptr. } boot operate-1-(const node.wrap_base& x) const { return pt r 1= x ptr } template <class Node4 sir act nooe_wrap public nocie_-*rap_base<Node, Node& Node*>
3.2. Определение новых компонент 63 typedef node_wrap_base<Node, Node&, Node*> Base, node_wrap(Node* p = 0) Base(p) {} node_wrap(const node_wrap<Node>& x) • Base(x) {} node_wrap& operator++() { incr(), return *this } node_wrap operator++(int) { node_wrap tmp = *this, incr(), return tmp, } }‘ template <class Node> struct const_node_wrap : public node_wrap_base<Node, const Node&, const Node*> { typedef node_wrap_base<Node, const Node&, const Node*> Base, const_node_wrap(const Node* p = 0) Base(p) {} const_node_wrap(const node_wrap<Node>& x) Base(x) {} const_node_wrap& operator++() { incr() return *this. } const_node_wrap operator++(int) { const_node_wrap tmp = *this, incr(), return tmp, } Базового класса node wrap base почти достаточно. Необходимость наследования node wrap и const node wrap от node wrap base вместо использования базового класса объясняется тем, что запись node_wrap<T> гораздо более удобна, чем node_wrap_base< Т, Т&, Т*>. Язык C++ не имеет шаблонных операторов typedef, поэтому приходится использовать на- следование или вложенные классы. Если вас не смущает неудобное имя или вы определите другое имя этого типа, то о классах-наследниках не стоит беспокоиться. 3.2.1. Адаптеры итераторов Один из полезных типов итераторов — это адаптер (adaptor). Вообще говоря, адап- тер преобразует один интерфейс в другой. Класс node_wrap — в определенном смысле адаптер, обеспечивающий интерфейс STL к связному списку, который, возможно, был написан, когда еще не были известны итераторы или STL. Следующий пример — это адаптер, который обеспечивает другой интерфейс для сУЩествующего компонента STL. STL определяет несколько таких адаптеров, напри- МеР f ront_insert_iterator, back_insert_iterator и reverse_iterator. Самый интересный из них — reverse_iterator (с. 360). Это именно адаптер. У него есть единственный параметр шаблона: Двунаправленный Итератор Iter, а reverse_iterator<lter> совпадаете Iter, но перемещается он не вперед, а назад. Тог- ^a°Perator++для rever se_iterator<Iter> эквивалентен operator-- для Iter. Если вы пишете адаптер вроде reverse_iterator, то не обязательно решать, будет 011 к°нстантным или изменяемым итератором. Например, reverse_iterator<Iter>
64 Глава 3. Подробнее об итераторах по своей природе имеет тот же самый ссылочный тип и 1ии указателя, что и Iter, поэтому он является изменяемым тогда и только тогда, когда изменяемым является Iter. 3.2.2. Советы по написанию итератора • Используйте требования к итератору из главы 7 в качестве контрольного списка. • Внимательно отнеситесь к тому, будет ли итератор константным или изменяе- мым. Главный критерий — ссылочный тип и тип указателя: Т& и Т* или же const Т& и const Т* соответственно. Если потребуется пара итераторов: константный и изменяемый, определите преобразование из изменяемого итератора в констан- тный, но не наоборот. • Удостоверьтесь, что iterater_t raits должным образом определен для вашего ите- ратора, определив вложенные типы. Простейший способ сделать это — исполь- зовать класс iterator в качестве базового. • Нужно определять как можно больше операций итератора без потери эф- фективности. Это нужно, чтобы ваш итератор можно было использовать с наи- большим количеством алгоритмов. Лучше определить его как модель Итератора Произвольного Доступа (если это целесообразно), чем как модель Двунаправлен- ного Итератора. Если вы пишете итератор, который проходит по структуре дан- ных (в отличие от итератора, который предоставляет интерфейс к некоторому оператору ввода/вывода), то он почти всегда должен быть по меньшей мере Од- нонаправленным Итератором. • Для Однонаправленных Итераторов, в частности, вы должны удостовериться в со- ответствии фундаментальной аксиоме равенства (equality) итераторов: два ите- ратора равны тогда и только тогда, когда они указывают на один и тот же объект. 3.2.3. Советы по написанию алгоритма • Делайте как можно меньше предположений. Если можно записать ваш алгоритм так, чтобы он без потери эффективности работал с Итератором Ввода или Итера- тором Вывода, то так и нужно сделать. Это означает также, что алгоритм можно использовать с самыми разными видами итераторов. • Если вы можете записать свой алгоритм так, чтобы он работал, например, с Ите- раторами Ввода, но есть возможность записать его более эффективно с помощью Однонаправленных Итераторов, то не нужно выбирать между эффективностью и общностью. Обе цели слишком важны, чтобы отказываться от них. Примени- те технику диспетчеризации, описанную в разделе 3.1.4. • Согласуйте свою разработку с требованиями к итератору, описанными в гла- ве 7, для того чтобы случайно не использовать больше возможностей предпо- лагаемого параметра шаблона, чем определено в соответствующей концепции итератора. Когда вы проверяете алгоритм, используйте конкретный тип, кото- рый максимально близко придерживается этой концепции. Если вы написали алгоритм, который работает, например, с Итератором Ввода, следует проверить его с ist ream_iterator, а нес int*, однако если он работает с Итератором Вывода, то — с ost ream_iterator. Если же ваш алгоритм работает с Однонаправленным
3 3. Заключение 65 Итератором, то лучшим выбором для проверки будет итератор, определенный в контейнерном классе si 1st • Обратите внимание на пустые диапазоны. Большинство алгоритмов могут ра- ботать на пустых диапазонах, и проверка на пустоту диапазона очень важна. 3.3. Заключение Основная черта STL — это обобщенные алгоритмы для диапазонов. STL содержит множество предопределенных алгоритмов, которые описаны в главах 11-13. Эти ал- горитмы включают в себя такие разнообразные операции, как поиск подстроки пли случайные перестановки. Все алгоритмы функционируют на диапазонах итераторов, представляющих со- бой семейство пяти концепций, похожих на указатели. Не все алгоритмы можно ис- пользовать вместе с любой структурой данных. Например, безнадежно ожидать, что- бы алгоритм quicksort (быстрой сортировки) работал с односвязным списком. Ите- раторы, однако, предоставляют способ классифицировать, какие алгоритмы можно использовать с теми или иными структурами данных. В этой главе и главе 2 мы встре- тили алгоритмы, работающие с различными концепциями итераторов, и даже алго- ритмы, которые выбирают между различными реализациями в зависимости от пере- данного им вида итератора. Концепции пяти итераторов STL связывают алгоритм со структурой данных, с ко- торой он работает. Эти концепции обеспечивают возможность многократного исполь- зования существующих алгоритмов с новыми структурами данных и существующих структур данных с новыми алгоритмами. Итераторы наряду с функциональными объектами (гема главы 4) позволяют даже самые простые алгоритмы применять по- вторно в самых разнообразных контекстах.
4 Функциональные объекты В главе 2 мы видели, что многие алгоритмы для диапазонов могут быть написаны исключительно в терминах итераторов. Однако такой узкий подход иной раз слиш- ком ограничивает. Один из таких случаев — линейный поиск, с описания которого мы начали главу 2 В разделе 2.1.3 мы узнали, как написать обобщенный алгоритм find, который ищет в диапазоне [first, last) первый элемент, равный параметру value. Этот алгоритм работает корректно, но иногда его возможностей недостаточно. Вот как определена общая задача поиска в главе 6 книги Кнута Искусство программирования [Knu98b]. Допустим, имеется набор из Nзаписей и задача состоит в том, чтобы найти нужную. Как и в случае сортировки, мы предполагаем, что каж- дая запись содержит специальное поле, которое называется ключом... Алгоритму поиска в качестве так называемого аргумента дается /С, и необходимо узнать, у какой записи К является ключом. Алгоритм find не является достаточно общим, потому что он не полностью соот- ветствует определению. Используем терминологию Кнута. Функция find принимает один аргумент. Однако вместо того чтобы найти запись, ключ которой равен аргу- менту, она ищет запись, полностью идентичную аргументу. Таким образом, find со- ответствует определению только в том случае, когда ключ записи совпадает со всей записью. Предположим, мы осуществляем поиск в коллекции записей, каждая из которых содержит информацию о человеке. Можно искать по фамилии или по номеру карточ- ки социального обеспечения. Мы не сумеем воспользоваться find, потому что find проверяет на равенство. С помощью find можно найти запись, идентичную конкрет- ному значению, но в данном случае нужно отыскать ту, которая отвечает более слабо- му критерию. Таким образом, для поиска по фамилии применить find невозможно 4.1. Обобщенный линейный поиск В find мы иараметризировали две различные вещи: тип искомого объекта и способ организации объектов в структуру данных. Для полной общности мы должны иметь
4.1. Обобщенный линейный поиск 67 возможность задать еще одну вещь: как определять, нашли мы подходящий элемент или нет. Для этого требуется новый алгоритм, find.if: tinplate <class InputIterator class Predicate> Inoi-tlterator find_if(Inputiterator first, Inputiterator last, Predicate pred) { while (first '= last && ’pred(*first)) ++first return firs! } Алгоритм find.if очень похож на find. Вместо элемента, равного аргументу Т, он ищет элемент, удовлетворяющий условию pred. Очевидно, это более общий алгоритм, чем find и даже чем определение Кнута. Последнее говорит о поиске записи, имеющей конкретный ключ, a find_if применяется для поиска записи, соответствующей усло- вию, которое не использует никаких ключей. (Конечно, f ind_if можно применить для поиска, определенного Кнутом.) В find_if условие pred описано не в виде указателя на функцию, как это делается в языке Си, а в виде параметра шаблона Predicate. Нечто подобное нам встретилось в главе 1. Это функциональный объект (function object) или функтор (functor) — эти термины синонимичны. То есть pred есть нечто, что может быть вызвано как функция. Функциональные объекты полезны в различных контекстах. В STL они использу- ются прежде всего как “параметры шаблона для указания стратегии” (Страуструп [Str97]). 11апример, параметры шаблона, передаваемые в f ind_if, определяют вид по- иска, который будет выполняться. Самый простой функциональный объект — это обычный указатель на функцию. Предположим, вы хотите найти первое четное число в массиве целых чисел. Вы мог- ли бы определить небольшую функцию, которая проверяет, четно int или нечетно: bool 1s_5*ven(int х) { return (х & 1) == 0, } Указатель на is_even можно передать в качестве аргумента функции f ind_if. Напишем: find_Lf(fl ], ]s_even) В результате будет возвращен итератор, указывающий на первое четное число в диа- пазоне [f, 1). Если в диапазоне нет четных чисел, будет возвращено значение 1. В этом случае указатель на is_even имеет тип bool (*)(int). Язык C++ выполняет обычный процесс выведения типа, подставляя bool (*)(int) вместо формального параметр^! Шаблона Predicate. Использовать указатель на функцию не обязательно. Оператор вызова функции, operator (), может быть перегружен, так что вы можете передать любой тин в качестве аргумента find_if, если он должным образом определяет operator(). Вы могли бы, например, задать проверку в виде класса, а не функции: femplate <ctass Number> struct even { bjul operalor()(Number x) const { return (x & 1) == 0, } }
68 Глава 4. Функциональные объекты _____________ В .ном объявлении const означает, что operator^) являеюя константным методом. Он не изменяет состояние объекта even. В данном случае это не нужно, так как у even не г нолей, которые могли бы быть изменены, но и вообще метод operator() лучше, когда это возможно, делать константным. При определении тривиального класса, на- пример вроде even, легко забыть такое описание, но его нужно делать Если вы пере- дадите функциональный объект как const-ссылку, а не по значению, то operator^) можно использовать, только если это const-метод. Мы можем использовать класс even так же, как функцию is_even. Чтобы найти пер- вый элемент в диапазоне, напишем: find_if(f 1 even<int>()) Здесь стоит немного странное выражение even<int>() по очень простой причине: even<tnr> — это класс, а нам нужно передать объект этого класса в find_if, поэтому мы вызываем конструктор. Класс even -- не очень интересный пример функционального объекта. Он более гибок, чем функция is_even (его можно использовать с любым составным типом, а не только с int), и, скорее всего, он эффективнее (при использовании even не будут вы- зываться функции, так как operator() описан как inline-метод), но в остальном он практически не отличается от функции. Это просто функция, облаченная в форму класса. Функциональный объект не ограничен этой простой формой. Он может быть пол- ностью общим классом, у него могут быть методы и поля как у любого другого класса. Значит, вы можете применять функциональные объекты, чтобы реализовать функ- цию с локальным состоянием. Локальное состояние можно использовать для задачи поиска, описанной Кнутом. Например, чтобы создать функциональный объект, который позволяет легко найти человека в списке по фамилии. Мы используем f ind.if для поиска элемента, отвеча- ющего некоторому условию. В данном случае условие состоит ^том, что поле last_name элемента равняется конкретному значению: struct last_name_is { stiing value 1ast_name_is(const string& val) value(val) {} tao] operator;)(const Person& x) const { return x last_name ~ value. Воспользуемся этим функциональным объектом, чтобы написать, например, следу- ющее. '>d_if(f 1 rst last, last_name_is(’Smith’)) К iacc вроде last_name_is — это пример функционального объекта в его полной общ- ности У него есть локальное состояние и конструктор, но из-за метода ooerator() объект типа last_name_is выглядит как функция, которая получает единственный аргумент типа Person и возвращает bool.
4 2. Концепции функциональных объектов 69 На языке Си трудно написать что-либо подобное last_name_ Вероятно, самое бтиз- кое — это обычная функция с глобальной переменной Но это все же не совсем то, поскольку при таком решении нельзя сделать несколько экземпляров вызова, осущест- вляющих поиск по различным фамилиям одновременно. (В некоторых других язы- ках это сделать даже проще. Например, функции с локальным состоянием являются базовыми в Scheme и ML.) 4.2. Концепции функциональных объектов в разделе 4.1 мы рассмотрели три функциональных объекта: is_even, even и last_name_is. Кроме того, что каждый вызывается как функция и у каждого есть орегатог(), общего у них очень мало. Очевидно, основное требование к любой концепции функциональ- ного объекта состоит в наличии возможности применить к нему ope rate г (). 4.2.1. Унарные и бинарные функциональные объекты Все три функциональных объекта из раздела 4.1 имели следующее свойство: если f — один из таких объектов, то для некоторого х можно написать f (х). Таким образом, f выглядит как функция, получающая лишь один аргумент. Существуют и другие методы использования функционального объекта. В функ- циях, которые получают всего один аргумент, нет ничего специфического. Мы обоб- щили fi^d, введя функциональный объект с одним аргументом, так же мы можем обобщить adj acent_f ind (раздел 2.3.3), передавая ему еще один аргумент — функцио- нальный объект с двумя аргументами. template <class Forwarditerator, class BinaryPredicate> Forwardltcrator adjacent_find(Forward!terator first. Forwarditerator last. BinaryPredicate pred) { if (first -= last) return last. poiwarditerator next = first, whi le(+-rnext 1 = last) { if (pred(* first, *next)) return first first = next } r0tuin last, $ перегружает функцию adjacent_f ind. Есть две версии этой функции, хотя, стро- г° говоря, только та, что испочьзует функциональный объект, действительно необ- ходима. Такое встречается часто. Алгоритм получает функцию в качестве аргумен- та, который параметризирует некоторое поведение, в данной ситуации — проверку соседних элементов. Обычно существует поведение по умолчанию, используемое
70 Глава 4. Функциональные объекты в большинстве случаев. При этом STL содержит версию алгоритма, обеспечивающую поведение по умолчанию. Мы рассмотрели алгоритмы, которые используют функциональные объекты, по- лучающие один и два аргумента. Некоторые другие алгоритмы, вроде generate (с. 256), используют функциональные объекты, которые вообще не имеют аргументов. Таким образом, основные концепции функциональных объектов — Генератор (Generator), Унарная Функция (Unary Function) и Бинарная Функция (Binary Function). Эти кон- цепции описывают объекты, которые могут быть вызваны как f(),f(x)nf(x,y) соот- ветственно. (Очевидно, список можно продолжить тернарной функцией и далее, но практически алгоритмы STL не требуют функциональных объектов с более чем дву- мя аргументами.) Все другие концепции функциональных объектов, определенные в STL, являются развитием этих трех. Три названные концепции тесно связаны (все три описывают функциональные объекты), но тем не менее не являются развитиями некоторой базовой концепции функционального объекта. Требования к концепциям включают в себя специфиче- ские выражения, и три выражения: f (), f (х) и f (х, у) — совершенно различны в этом смысле. 4.2.2. Предикаты и бинарные предикаты Оба примера, уже рассмотренные в этой главе, используют функциональные объек- ты, которые проверяют некоторое условие и возвращают true или false. Возникает вопрос: все ли функциональные объекты имеют такой вид? Нет, не все. Функциональные объекты — очень общее понятие, и они могут пара- метризировать любой вид операции. Почти любой алгоритм может быть обобщен аб- страгированием части его поведения в функциональный объект. Это характерно и для банального алгоритма сору (с. 239), который копирует значения из одного диапазона в другой. Очевидное обобщение — не просто копировать значения из входного диапа- зона, а предварительно преобразовывать их тем или иным способом. Значит, вместо операции у,<—х, (4.1) нужно выполнять операцию (4.2) (Равенство (4.1) — специальный случай равенства (4.2), где f— есть тождественная функция, id.) Алгоритм STL для этого обобщенного сору называется transform (с. 246). Если бы оп не использовал функциональный объект, то его реализация была бы совсем как реализация сору. template <class Inputiterator. class Outputiterator, class UnaryFunction> Ourputlterator transform(lnputlterator first, Inputlterator last, Outputiterator result, UnaryFunction f) { while (first ’= last) *resu]t++ - f(*fjrst++), return result.
4 2. Концепции функциональных объектов 71 Очевидно, Унарная Функция не обязана возвращать лишь значения true или false. Она может возвращать любое значение, если возвращаемый ею тип можно присвоить итератору Outputiterator. Например, можно использовать transform, чтобы изменить знак диапазона чисел. функциональные обьекты, которые возвращают bool, — это специфический слу- чай; они используются чаще, чем полностью обобщенные функциональные объекты. Этот вид объекта удобен, когда алгоритм вроде f ino_if делает какие-либо проверки функциональный объект, получающий единственный аргумент и возвращающий в качестве результата значение true или false, называется Предикат (Predicate). Эта концепция представляет собой развитие концепции Унарной Функции. Аналогично, Бинарный Предикат (Binary Predicate) является развитием Бинарной Функции. Бинар- ный Предикат — это функциональный объект, который получает два аргумента и воз- вращает значение true или false. 4.2.3. Ассоциированные типы Ассоциированные типы (associated types) функционального объекта — это типы его аргументов и возвращаемого значения. Генератор имеет один ассоциированный тип (тип возвращаемого значения), Унарная Функция — два ассоциированных типа (тип ее аргумента и тип возвращаемого значения), а у Бинарной Функции три ассоцииро- ванных типа (типы ее первого и второго аргументов и тип возвращаемого значения). Класс even<T>, например, имеет тип аргумента Т и тип возвращаемого значения bool. Как и в случае с итераторами, иногда полезно иметь возможность обращаться к этим ассоциированным типам. Мы решили эту задачу для итераторов, введя iterator _t raits (раздел 3.1), аналогичным будет решение и для функциональных объектов. Функци- ональные объекты, объявленные в виде классов, как и объявленные в виде классов итераторы, могут иметь вложенные типы. Определим even<T>: - argument-type имею- щим значение Т, a even<T> • result-type — имеющим значение bool. Снова, как и с ите- раторами, введем пустые базовые классы unary_function (с. 368) и binary_function (с. 369), которые не содержат ничего, кроме определений typedef: template <class Arg, class Result> struct uriary_function { typedef Arg argument-type. typedef Result result_type, J . template <dass Arg1, class Arg2. class Result> struct oinary_function { typedef Argl first_argument_type. r>P?def Arg2 secund_argument_type, fypedef Result result_type } Можно было бы модифицировать even<T> явным объявлением этих двух типов или, что более удобно, их наследованием от unary f unction: template <class Number> si "ict even public unary_function<Number, bool>
72 Глава 4. Функциональные объекты bool operaior()(Number x) const { return (x & 1) == 0, } i Эго неполное решение, поскольку его нельзя использовать для такого важного вида функционального объекта, как указатели на функции вроде bool (* )(int). Указатели на функции представляют собой встроенные типы, а не классы, поэтому определить внутри них вложенные типы невозможно. Мы могли бы решить эту проблему так же, как уже было сделано для итерато- ров. Можно определить класс признаков: unary_function_t raits, а затем — его час- тичную специализацию для указателей на функции. Однако в STL нет класса jf'.ar y_f unction_t raits. Его отсутствие не объясняется техническими причинами, но несмотря па то, что функциональные объекты важны, они не столь вездесущи, как итераторы. Многие алгоритмы, в которых применяются функциональные объекты, не используют явно тип аргумента или тип возвращаемого значения, поэтому нет необходимости обеспечивать средства доступа к этим типам каждому функциональ- ному объекту. В результате STL различает функциональные объекты и адаптируемые функци- ональные объекты (adaptable function objects). Вообще, у каждого функционально- го объекта есть тип аргумента (или типы аргументов в случае Бинарной Функции) и тип возвращаемого значения, но узнать имена этих типов в программе не всегда возможно. Адаптируемый функциональный объект задает тип аргумента и тип возвращаемого значения, он также содержит вложенные объявления typedef, по- этому такие типы можно именовать и использовать в программах. Если тип F1 — .по модель Адаптируемой Унарной Функции (Adaptable Unary Function), то он дол- жен определять F1 argument_type и F1 result_type, а если тип F2 — это модель Адаптируемой Бинарной Функции (Adaptable Binary Function), он должен опреде- лять F2 first_argument_type, F2 •second_argument_type и F1.•result_type. Базовые классы unary_f unction и binary_function упрощают определение Адаптируемых Унар- ных Функций и Адаптируемых Бинарных Функций. Как Адаптируемая Унарная Функция является развитием Унарной Функции, так и Адаптируемый Предикат (Adaptable Predicate) — это развитие Предиката. Рис. 4.1 демонстрирует различные концепции Унарной Функции. Сходную диаграмму можно нарисовать и для концепций Бинарной Функции. Унарная Функция Адаптируемая Унарная Функция Предикат Адаптируемый Предикат Рис. 4.1. Концепции функциональных объекюв с одним парамещом
4.3. Адаптеры функциональных объектов 73 Ни один алгоритм в главах 11-13 не требует адаптируемых функциональных объек- тов. Например, find.if использует Предикат, a adjacent_find использует Бинарный Предикат. Зачем вообще заботиться о введении концепций Адаптируемой Унарной функции и Адаптируемой Бинарной Функции, если они никогда не используются? На самом деле они нужны, но только не в стандартных алгоритмах STL. Они опре- делены (и называются “адаптируемыми”), потому что используются адаптерами функциональных объектов. 4.3. Адаптеры функциональных объектов Адаптер (adaptor) — это компонент, который преобразует один интерфейс в другой Например, reverse_iterator из раздела 3.2 — это адаптер-итератор. Возможности адап- теров функциональных объектов особенно замечательны. • Композиция функций является базовой операцией математики и вычислитель- ной техники. • Определение множества специальных классов функциональных объектов неудоб- но. Относительно малое число хорошо подобранных адаптеров функциональ- ных объектов позволяют формировать сложные операции из более простых • Многие полезные адаптеры функциональных объектов чрезвычайно легко пи- шутся. (Если ваш образ мыслей похож на мой, то вы увидите, что самая трудная операция при написании нового адаптера функционального объекта — приду- мать ему хорошее имя!) Например, можно найти первое четное число в диапазоне целых чисел, написав: find_if(f, 1 even<int>()) А что делать, чтобы найти первое нечетное число в диапазоне? Можно написать дру- гой функциональный объект odd<T>, но это нерационально. Две такие проверки вза- имно дополняют друг друга, а раз вы определили одну, то вам не нужно определять другую. Код следует использовать повторно, а не писать заново. Решение — адаптер функционального объекта, который является частью STL: template <class AdaptablePredicate> class unary_negate { private AdaptablePredicate pred, Public typedef typename AdaptablePredicate argument-type argument-type. typedef typenamc AdaptablePredicate resiilt_type result_type. unary_negate(const AdaptablePredicate& x) pred(x) {} bool operator()(const argument_type& x) const { return ’pred(x) У
74 Глава 4. Функциональные объекты Если г — это предикат типа I-, то unary_negate<F>(f) — это предикат, который являет- ся отрицанием f, что означает следующее: предикат f_neg обладает тем свойством, что f_neg(x) имеет значение true тогда и только тогда, когда f (х) — false. Параметр шаблона AdaptablePredicate должен, как подсказывает имя, быть моде- лью Адаптируемого Предиката. Поскольку тип аргумента una ryjiegate описан так, что- бы быть одного типа с аргументом Адаптируемого Предиката, мы должны уметь обра- титься к этому типу. В свою очередь, unary_negate -- это тоже модель Адаптируемого Предиката, потому что содержит вложенное определение typedef. Это типично для адаптера функционального объекта, и именно по этой причине у подобных функцио- нальных объектов на первом месте в названии стоит “адаптируемые”. Единственная проблема для предложенного адаптера unary_negate заключается в том, что выражение unary_negate<F>(f) слишком громоздко. Мы должны обеспе- чить как функциональный объект f, так и его тип F. Для удобства определим крошеч- ную вспомогательную функцию, которая облегчит создание объектов: unary_negate. template <class AdaptablePredicate> inline unary_negate<AdaptablePredicate> nol1(const AdaptablePredicare& pred) return unary_negate <AdaptablePredicate>(pred), } Можно считать not1 функцией высшего порядка, или функцией, которая работает с функциями. Ее аргумент — функциональный объект, а ее возвращаемое значение — отрицание этого функционального объекта. Таким образом, мы можем найти первый нечетный элемент диапазона, написав: find_if(f]rst, last, not1(even<int-())) Соединяя вместе несколько разных адаптеров функциональных объектов, можно кон- струировать сложные функциональные объекты из простых компонентов без напи- сания новых функций или классов. Если вам нужно передать обычный указатель на функцию адаптеру функциональ- ного объекта, то можно использовать pointer_to_unary_function (с. 417) для транс- формации указателя на функцию в Адаптируемую Унарную Функцию. (Заметим, что адаптер pointer_to_unary_f unction необходим только в случае, если вам надо тракто- вать указатель на функцию как Адаптируемую Унарную Функцию. Как мы видели на примере передачи указателя на функцию в f ind_if, указатель на функцию представ- ляет собой Унарную Функцию.) template <class Arg, class Result> class pointer_to_unary_function public unary_function<Aro, Result> { pr r/ate Result (*ptr)(Arg), public puinter_to„unafy_function() {} pelntur_to_unary_function(Result (*x)(Arg)) ptr(x) {}
4 4. Предопределенные функциональные объекты 75 Result operator()(Arg х) const { return ptr(x), } }: template <class Arg, class Result> inline pointer_to_unary_function<Arg, Result> ptr_fun(Result (*x)(Arg)) < return pointer_to_unary_function<Arg, Result>(x), } Есть похожий адаптер для бинарных функций — pointer_to_binary_f unction. STL определяет несколько других адаптеров функциональных объектов. Самые важные из них — это bindedst и binder2nd, преобразующие Адаптируемую Бинар- ную Функцию в Унарную Функцию; адаптер mem_fun_t и его варианты, похожие на pointer_to_unary_f unction, но оперирующие с методами, а не с глобальными функци- ями; и unary compose, преобразующий два функциональных объекта f и g в один функ- циональный объект h, такой что h (х) есть то же самое, что f (g(х)). 4.4. Предопределенные функциональные объекты В дополнение к адаптерам STL включает в себя множество простейших функцио- нальных объектов, которые можно использовать в качестве строительных блоков В частности, STL содержит основные арифметические операции и операции срав- нения. Основные арифметические операции, определенные в STL, — это plus (сложение), minus (минус), multiplies (умножение)**, divides (деление), modulus (модуль) и negate (отрицание, изменение знака), при этом negate является моделью Адаптируемой Унар- ной Функции, а остальные — моделями Адаптируемой Бинарной Функции. Основные операции сравнения — это equal_to (равно), not_equal_to (не равно), greater (больше), less (меньше), g reate r_equal (больше или равно) и less_equal (мень- ше или равно). Это модели Адаптируемой Бинарной Функции. Причем equal_to и less - самые важные из функциональных объектов сравнения, поскольку востребованы многими алгоритмами. Всякий раз, когда эти операции сравнения параметрпзируют- ся как функциональные объекты, equal_to и less используются по умолчанию. Сравнение двух значений — настолько общая и важна операция, что для нее есть специальная концепция. Концепция Строгого Слабого Упорядочения (Strict Weak Ordering) — это развитие концепции Бинарного Предиката. Строгое Слабое Упорядо- чение есть бинарный предикат, представляющий “корректное” отношение упорядо- чения. Формальные правила, которым подчиняется корректное отношение упоря- дочения, довольно необычны (вы можете найти их на с. 132), но выполнять их не- сложно. Они лишь формализуют то, что мы интуитивно ожидаем от отношения Упорядочения. ^opin iina.'ibiioii версии HP STL .но называлось times - “умножение” Имя было изменено дчя vci- Ранецця конф |ик)а с одной и < функции в библио 1скс UNIX
76 Глава 4. Функциональные объекты 4.5. Заключение функциональные объекты как таковые не очень полезны. Функциональный объект это обычно очень маленький класс, выполняющий одну-единственную специфическую операцию. Он зачастую имеет только один метод operate г () и не содержит полей. Польза от функциональных объектов заключается в том, что они позволяют обоб- щенным алгоритмам быть еще более общими. Они способствуют параметризации стратегии поведения. Функция f ind_if, например, может искать любые объекты в лю- бой! линейной последовательности, применяя любой критерий поиска. Аналогично, алгоритм сортировки sort не должен учитывать все мыслимые способы, с помощью которых вам вздумается сортировать диапазон. Вместо этого он использует функци- ональный объект (который должен быть моделью Строгого Слабого Упорядочения), определяющий, как будет сортироваться диапазон. Функциональные объекты, как и большая часть STL, полезны в качестве дополне- ния к алгоритмам для линейных диапазонов.
5 Контейнеры В главе 2 мы познакомились со способами записи обобщенных алгоритмов в терми- нах итераторов. Абстрактное представление итераторов позволяет отделить алго- ритмы как таковые от элементов, которыми они оперируют, и писать алгоритмы, работающие с любыми линейными последовательностями. Во второй и двух последующих главах был проигнорирован один существенный вопрос: откуда берутся эти элементы. В большинстве примеров этих глав использо- вались обычные массивы языка Си. Однако если мы собираемся применять эти алго- ритмы только к массивам, то польза от этой обобщенности невелика. Алгоритм, работающий с некоторым диапазоном элементов, оперирует членами какой-то структуры данных. Такие алгоритмы наиболее интересны, поскольку во мно- гих случаях структуру данных, полностью или частично, можно рассматривать как линейный диапазон элементов. Это означает следующее: чтобы понять обобщенные алгоритмы над диапазонами, нам потребуется рассмотреть еще один их аспект: струк- туры данных, содержащие эти диапазоны. 5.1. Простой контейнер Простейшей структурой данных, способной включать в себя диапазон, и в то же вре- Мя единственной, поддерживаемой непосредственно самим языком, является массив (array). Массив содержит набор элементов, расположенных последовательно, что и позволяет осуществлять итерацию по элементам. Итераторы, связанные с массива- ми, — это указатели. Массивы в Си и C++ имеют несколько очевидных положительных черт, которых вполне достаточно для решения многих задач. • Массив естественным образом использует идею диапазона (раздел 2.1.2). Если А — массив из N элементов, то А + N — указатель на элемент, следующий за по- следним, а все элементы А содержатся в диапазоне [А, А + N). • Память под массивы может быть выделена на стеке. Большинство структур дан- ных вынуждены использовать динамическое выделение памяти, то есть чтобы
78 Глава 5. Контейнеры получить память, им приходится обращаться к mall ос или new, а массивам это не нужно. Объявление типа int А[10], не подразумевает динамического выделения памяти. Это двойное преимущество, потому что действие выполняется быстрее и отсутствует необходимость про- верки, успешно ли была выделена память. • Массивы эффективны, поскольку доступ к элементам массива не требует при- менения нескольких уровней косвенной адресации. Для обращения к элементу массива требуется всего несколько машинных команд. • Массивы имеют фиксированный размер, известный на этапе компиляции. Опе- рации над массивами не подвержены дополнительным накладным расходам, связанным с изменением их размера, и программы, которые используют масси- вы, не заботятся о возможном изменении размера. • Существует удобная форма записи для инициализации массивов. Массив мож- но инициализировать следующим образом: mt А[61 - {1. 4, 2, 8, 5. 7}, Воспользоваться этим синтаксисом в случае любых более сложных контейнер- ных классов нельзя. Недостатки массивов в Си и C++ столь же очевидны: • Несмотря на то что массивы имеют фиксированный размер, программы должны явно отслеживать его, так как у массивов нет функции-члена (member function) size(). • У каждого массива есть итератор, который указывает на первый элемент, и ите- ратор, который указывает на элемент, следующий за последним. Однако нет не- посредственного способа получить этот второй итератор. Чтобы добраться до конца, необходимо взять итератор, указывающий на начало массива, азатем вос- пользоваться арифметикой итераторов. • Нельзя непосредственно копировать массивы. У массивов нет ни конструкто- ров копирования, ни операторов присваивания. При необходимости скопиро- вать массив придется вручную написать соответствующий цикл. • Нельзя передать массив по значению в качестве аргумента функции. Массив во- обще сложно передать в функцию. Обращаясь к массиву в выражении, вы ссы- лаетесь на первый элемент массива. Массивы являются частью системы типов Си и C++, но при этом наблюдается тенденция к их интерпретации в качестве указателей. 5.1.1. Класс массива Обычные массивы языка Си до сих пор широко используются, несмотря на суще- ствование изощренных классов контейнеров. Несложно определить простой класс оюск (блок) \ который сохраняет все полезные свойства массивов, одновременно уст рапяя их главные недостатки. ’Название block было предложено Эндрю Кентом С ipayripvu jSi; 97| ikho на шанце ( для похожего (но не идеи петого) класса
5.1. Простой контейнер 79 template <с1аьь Т, size_t N> struct block { Typedef 7 value_type, typedef value_type* pointer, typedef const value_type* const_pointer, typeoef value_type& reference, typedef const value_type& const_reference, typedef ptrdiff.t difference_type, typedef size_t size-type, typedef pointer iterator, typedef const_pointer const-iterator. iterator begin() { return data, } iterator end() { return data + N. } const-iterator begin() const { return data, } const-iterator end() const { return data + N, } reference operatorf](size_type n) { return data[n], } const.reference operator[](size_type n) const 1 return data[n], } size_type size() const { return N, } T datafNj. }, Как следует из названия, block — простой непрерывный блок элементов. Это немного больше, чем обертка вокруг массива языка Си. Вы можете объявить блок из десяти целых чисел следующим образом: block<int. 10> А, и воспользоваться обычной формой записи, например А[ 1 ], для доступа к элементу. Потребуется еще несколько штрихов для облегчения использования блока в обоб- щенных алгоритмах. Во-первых, блок поддерживает итераторы и диапазоны более явным образом, чем массивы языка Си. Все элементы блока содержатся в диапазоне [A. begin(), a end()). Во-вторых, у контейнера вроде block, как и у итераторов, есть несколько ассоциированных типов. Мы реализуем эти типы при помощи вложенных операторов tyoedef. В языке Си можно осуществлять итерацию по элементам массива типа Т с помо- щью указателя Т* (если вам требуется изменять значения элементов массива) или с помощью const Т* (если модификация не требуется). Это различие довольно легко с°хранить, так как блок — обертка для массива. В теле класса block мы определим два Типа: iterator и const-iterator. Кроме того, надо определить две различные версии ^е91п() и end(). Если А — модифицируемый блок, то [A begin(), A end()) - диапазон Изменяемых итераторов, а если А - константный блок, то [A begin(), A end()) — это
80 Глава 5. Контейнеры диапазон константных итераторов. Аналогично, есть две различные версии функции- члена operator!]. А[п] возвращает модифицируемую ссылку, если блок А является модифицируемым, и константную ссылку, если А — константный блок. Таким образом, класс block следует типичному шаблону из раздела 3.2. Он предо- ставляет согласованную пару итераторов — один изменяемый, а другой константный — с возможностью преобразования изменяемого итератора в константный. В нашем слу- чае такими итераторами являются старые знакомые Т* и const Т*. Для обеспечения общности мы определили их внутри block как iterator и const_iterator. Эти два вложенных типа наиболее важны. Как только мы устанавливаем, какие итераторы есть у класса block, все остальное, связанное с ними, следует автоматиче- ски. В классе block есть еще несколько ассоциированных типов, в частности ассоции- рованных с его итераторами. Так как block включает в себя два вида итераторов и у каждого итератора есть тип значения, разностный тип и тип указателя, можно предположить, что в теле класса olocK требуется определить еще восемь различных типов. Это не совсем так. Во-пер- вых, некоторые из данных восьми типов всегда должны быть идентичны друг другу, поэтому не нужно определять их дважды. Когда два итератора формируют такую пару, у них всегда должен быть одинаковый тип значения и разностный тип (помните, что тип значения, па который указывает итератор const Т*, есть Т, а не const Т). Во-вто- рых, есть еще один тип, напрямую не связанный ни с одним из итераторов блока. Это раз мерный тип (size type) для block. Подобно тому, как разностный тип используется для представления интервала между двумя итераторами, размерный тип использует- ся для подсчета количества элементов. Разностный тип всегда является типом со зна- ком, а размерный тип — всегда без знака (и более того, если разностный тип — это почти всегда ptrdif f_t, то размерный тип — это почти всегда si ze_t). Итак, перечислим все типы, включенные в описание block. 1. Типы итераторов, iterator и const_iterator (в нашем случае итераторы — это просто указатели). 2. Тин значения, value_type, — это тип объекта, хранящегося в block, совпадающий с типом значения, на которое указывают iterator и const_iterator. 3. Разностный тип, difference_type, совпадает с разностным типом для iterator и const_iterator. 4. Размерный гип, size_type, используется для представления числа элементов в блоке или индекса некоторого конкретного элемента. Кроме того, любое не- отрицательноезначениетипа 01 f ference_type может быть представлено с помо- щью типа size_ type. Основное различие между ними состоит в том, что size_type используется для подсчета, a dif ference_type — для вычитания. 5. Ссылочные типы, reference и const_reference. Тип reference используется для ссылки на объект типа value_type и совпадает со ссылочным типом, определен- ным для типа iterator Именно этот тип возвращает неконстантная версия operator[]. Тип const_reference — это константная ссылка на объект типа value_type, совпадающая со ссылочным типом, определенным для типа const_iterator. Именно этот тип возвращает константная версия operator[]. 6 Типы указателей, pointer и const_pointer. Тип pointer — это указатель на объект типа value_type, он совпадает с типом указателя iterator. Тип const pointer —
5.1. Простой контейнер 81 :-)io константный указатель на объект типа value_type, совпадающий с типом указателя const.i terator. 5,1.2. Как это работает Класс block очень прост. У него есть единственное поле — массив data. Однако в оп- ределенном смысле этот класс слегка необычен. Во-первых, количество элементов в блоке является частью его типа. Тип plock<int. 10> абсолютно отличается от типа block<int, 12>. Количество элементов фиксируется на этапе компиляции и не может быть изменено. Нельзя добавлять эле- менты в блок, присваивать block<int, 12> блоку blockdnt, 10> или передавать block<int, 12> в качестве аргумента функции, которая ожидает blockdnt, 10>. Коли- чество элементов в блоке — это параметр шаблона блока, а не конструктора. Данный пример иллюстрирует возможность языка, которая называется нетиповой параметр шаблона (nontype template parameter). Второй параметр шаблона — это не тип, а число. Нетиповые параметры шаблона появились в C++ уже в момент добавле- ния к языку шаблонов, но тем не менее они редко используются. Наш пример пока- зывает, что они имеют существенное значение. Внутри block с параметром шаблона М обращаются как с константой, известной на этапе компиляции. Ее можно использо- вать аналогично любой другой константе, в данном случае — для задания количества элементов массива. Это позволяет полю блока быть не просто указателем на массив, а собственно самим массивом. Все элементы класса block располагаются в одном не- прерывном блоке памяти — отсюда и название. Другой необычной чертой block является то, что создается впечатление отсутствия у него некоторых свойств: нет ни конструктора, ни деструктора, ни оператора при- сваивания. Однако компилятор по умолчанию создает свою версию каждой из этих функций, а присваивание одного блока другому — это простое копирование его эле- ментов. Гораздо более необычно то, что в block нет объявления public или private. Он объявлен в форме st ruct, и поэтому все его члены, включая сам массив data, являют- ся открытыми. На первый взгляд это противоречит самой идее инкапсуляции, но в данном кон- кретном случае абсолютно безвредно. Объявление поля data в открытом (public) раз- деле дает возможность клиентам модифицировать элементы данных напрямую, что можно делать, используя итераторы либо operator[], В конце концов в этом и за- ключалась идея класса block. Есть одна особая причина, по которой объявление поля data в открытом разделе просто необходимо. В Си можно проинициализировать struct так же, как и массив, поместив список инициализирующих значений внутрь фигурных скобок. То же с не- которыми ограничениями верно и для C++. Struct-класс не должен иметь опреде- ленных пользователем конструкторов, а также виртуальных функций-членов, не дол- жен наследовать от другого класса, и у него не должно быть ни приватных, ни защи- щенных членов . Класс block удовлетворяет этим ограничениям, поэтому блок можно объявить точно так же, как и массив: block*:iiit 6> А = {1, 4 2. 8. 5 7}, В ciaiiaapic C++ ши, vдовлелворяющий .ним ограничениям, называется агрегатным типом Aggregate type)
82 Глава 5. Контейнеры 5.1.3. Последние штрихи Б чок удовлетворяет нашим исходным требованиям. Это класс контейнера, который можно прошшциализировать, как обычный массив, при этом он столь же эффекту вен. В то же время это полноценный тип данных. Можно присвоить один блок друго- му. передать его по значению и обращаться к итераторам, указывающим на первый и следующий! за последним элементы. Вдобавок можно реализовать еще несколько возможностей, которые не являются жизненно необходимыми в плане осуществ- ления исходных требований, но делают block удобными для реализации некоторых целей. Первое и самое важное: вспомните, как в разделе 2.2 мы определили набор наибо- лее общих концепций, которые используются в других концепциях и моделируются большим количеством типов. Кроме того, мы определили регулярный тип, являю- щийся моделью таких концепций. К настоящему моменту block определен как модель двух концепций: Присваива- емого и Конструируемого по Умолчанию. Однако он не является моделью Сравнимо- го или Сравнимого. Если х и у — два объекта типа block<T, N>, их нельзя записать X -- у или X < у. Отсутствие этих свойств ничем не оправдано. И operateг==, и operateг< имеют со- вершенно разумные определения. Два блока х и у равны, если они равны поэлемент- но, то есть если каждый элемент х равен соответствующему элементу у. Аналогично, можно воспользоваться лексикографическим или “словарным” упорядочением, что- бы указать меныпее из двух значений х и у. Мы можем определить, что х меньше у, если \[0] < у[0] или если х[0] -- у[0] и х[1 ] < у[1 ] и т. д. Довольно просто реализовать оба оператора, operateг== и operateг<, в виде гло- бальных (не являющихся членами block) функций: " Mipia’p ^class т size_t N> поен epera'or--(const block<T,N>& x. const block<T,N>& y) j ' ” (sizej i. = 0. n < N, ++n) if (x datafnj 1 - у data[n]) return false return t rue, :criu-la!e cclass T, size_t N> boo’ ?pera1or<(const block<T,N>& x const block<T,N>& y) I ’or (size_t n - 0, n < N. ++n) if (x da;a[n] < у data[n]) retire i"ue. dsc if (y datafn] < x datafn]) к1 turn fa’i^e rP4irn । also * Обобпи HHi.iii a.iropiiiM I ex u og ! arai i ca i .ccwja r p (<. 232) определят! именно гакот 01 ношение
5 1. Простой контейнер 83 Строго говоря, этих функций недостаточно, чтобы гарантировать, что о1оск<Т. N> яв- ддется моделью =Сравнимого и <Сравнимого. Для этого требуется, чтобы operator— К operate г< были применимы к объектам типаТ. Таким образом, block<T, N> является моделью ^Сравнимого тогда и только тогда, когда Т является моделью ^Сравнимого, й р1оск<Т, N> является моделью <Сравнимого тогда и только тогда, когда Т является моделью <Сравнимого. Говоря более простым языком, в общем случае можно создать экземпляр блока с типом, для которого отсутствует ooerator==, если не требуется сравнивать два таких блока на равенство. Второе возможное улучшение заключается в том, чтобы предусмотреть еще один вид итератора. Если А — это блок, то диапазон [A. begin(), A end()) содержит все эле- менты А в порядке, в котором они хранятся в А. В разделе 3.2 мы познакомились с адап- тером итератора reverse_iterator (он описан на с. 360), который выдает элементы в обратном порядке. reverse_iterator<Iter> аналогичен Iter, за исключением того, что operateг++ для reverse_i terator<Iter> означает то же, что operator- - для Iter Мыможем определить еще две функции-члена, rbegin() и rend(), которые возвра- щаютреверсные итераторы. Тогда в [A rbegin(), A rend()) содержатся все элементы А точно так же, как в [A. begin(), A end()), но в противоположном порядке. Нам потре- буются две версии rbegin() по той же причине, что и две различные версии begin(). Выражение A rbegin() должно возвращать константный итератор, если А — констан- та, и изменяемый итератор в противном случае. template <class т size_t N> struc! block { typedei reverse_iterator<const_iterator> const_reverse_iterator, typedet reverse_iterator<iterator> reverse_iterator, reverse_iterator rbegin() { return reverse_iterator(end()), } reverse_iterator rend() { return reverse_iterator(begin()), } const_reverse_iterator rbegin() const { return const_reverse_iterater(end()), } const_reverse_iterator rend() const { return const_reverse_iterator(begin()), } }. Кроме того, можно создать несколько удобных функций-членов, которые, впрочем. Необязательны* swap(), обменивающая значения двух блоков; empty(). возвращаю- Щая значение true в том и только том случае, если в блоке не содержится ни одного Цемента: и max_size(). возвращающая значение, равное наибольшему количеству л^Ментов, которое можег содержать данный блок. Блок нс может расширяться или сокращаться, и поэтому функция max_size() не представляет большого интереса. На всегда возвращает то же значение, что и size(), и представляет реальный инте- только для контейнеров с переменным размером. Мы определим ее на будущее Ниду такого обобщения
84 Глава 5. Контейнеры template <class Т, size_t N> struct block { size_type max_size() const { return N, } bool empryO const { return N “ 0, } void swap(block& x) { for (size_t n = 0, n < N ++n) std swap(data[nl x dala[n]), Представим, наконец, полную версию класса block. Даже при всех улучшениях он намного проще изощренных структур данных STL, подобных deque и mao, но тем не менее довольно полезен. Простой класс вроде block часто является наиболее под- ходящим выбором. template <class Т size_t N> struct block f typedef T value_type, typedef value_type* pointer typedef const value_type* const-pointer. typedef value_type& reference. typedef const value_type& const_reference. typedef ptrdiff_t difference_type, typedef size_t size_type, typedef pointer iterator typedef const_pointer const-iterator, typedef std reverse_iterator<iterator> reverse_iterator, typedef std reverse_iterator<const_iterator> const_reverse_iterator iterator begin() { return data, } iterator end() { return data + N, } const_]terator begin() const { return data, } const-iterator end() const { return data + N } rcverse_iterator rbegin() { return reverse_iteratcr(end()). } reverse_]terator rend() { return reverse_iterator(begin()) } const_reverse_iterator roeginf) const { 'el urn const_rever se_iterator(end()),
5.2. Концепции контейнеров 85 const_reverse_iterator rend() const { return const_reverse_iterater(begin()), } reference cperatorf](size_type n) { return data[n], } const_reference operator[](size_type n) const { return data[n], } size_type size() const { return N, } size_type max_size() const { return N, } bool emptyO const { return N ” 0, } void swap(block& x) { for (size_t n = 0, n < N, ++n) std swap(data[n] x data[n]), } T data[N], }; template <class T size_t N> boo] operator==(const block<T,N>& x, const block<T,N>& y) { for (size_t n = 0, n < N, ++n) if (x data[n] 1= у data[n]) return false, return true } template <class T. size_t N> bool operator<(const block<T.N>& x, const block<T,N>& y) { for (size_t n = 0, n < N, ++n) if (x data[n] < у data[n]) return true, else if (y data[n] < x data[n]) return false return fdlse } 5-2. Концепции контейнеров Таким образом, следующий шаг очевиден. Мы определили полезный класс block 11 полагаем, что бпок является моделью некоторой общей концепции. Далее необхо- димо определить эту концепцию. Либо придумать определение другого класса, похо- жего на block; вопрос в том, насколько “похожими” они должны быть? При определе- нии класса block мы нс подчеркивали отличие функциональности, присущей всем Контейнерным классам, от функциональности, присущей только block. Сейчас мы Должны разделить эти две категории.
#6 _______________Глава 5. Контейнеры В классе block реализованы три главных категории функциональности. Он содер- жит элементы, предоставляет доступ к этим элементам и предоставляет операции, необходимые для того, чтобы блок являлся регулярным типом. 5.2.1. Содержание элементов Может показаться лишним утверждение, что “контейнер содержит свои элементы”, однако у этой формулировки далеко идущие следствия. Два блока не могут перекры- вать друг друга, п один элемент не может принадлежать более чем одному блоку. Каждый элемент является “подобъектом”, хранимым в блоке. Элементы создаются в момент создания блока и уничтожаются в момент уничтожения блока. Время жиз- ни элемента блока идентично времени жизни самого блока. Можно изменить значе- ние элемента, но нельзя создать новый, как нельзя уничтожить элемент, не уничто- жая при этом весь блок. Элемент блока является в буквальном смысле частью блока. Блок — это непрерыв- ная область памяти, и адрес каждого элемента находится в ее пределах. Столь бук- вальная реализация идеи о “содержании элементов” связана исключительно с кон- \рсл пыми деталями реализации блока. Мы не хотим давать контейнерам такое узкое определение, потому что иначе не сможем использовать динамическое выделение памяти. Однако мы можем выделить его самые важные свойства: 1. Два контейнера не могут перекрывать друг друга, и всякий элемент не может принадлежать более чем одному блоку. Конечно, можно поместить две копии объекта в два различных контейнера. Ограничение касается объектов, а не зна- чений. “Владелец” при этом может быть только один 2. Время жизни элемента не может превысить время жизни контейнера, частью которого он является. Элемент создается не раньше контейнера и уничтожается не позднее его. 3. Контейнер может быть контейнером фиксированного размера (fixed size), как block, либо контейнером переменного размера (variable size), в котором можно добавлять и уничтожать элементы после создания самого контейнера. Даже кон- тейнер с переменным размером “владеет” своими элементами, и все они уничто- жаются деструктором контейнера. Это можно выразить и другим способом. Все контейнеры, как, например, массивы я пика Си и контейнер block, имеют семантику значений, а не указателей. Элементы контейнера являются реальными объектами, а не просто адресами. Э го ограничение не гак уж серьезно. Указатели, в конце концов, сами являются объектами и, как и все другие обьекты, могут храниться в контейнерах. Например, вы можете использовать о^осКсЬаг*, 10>. Блок по-прежнему имеет семантику значений (каждый char* явля- ется уникальным объектом, хранящимся в блоке), но block “владеет” только указате- лями, а не тем, на что они указывают. Мы видели такой пример в главе 1, где созда- вался vector с указателями на строки таблицы. 5.2.2. Итераторы Существуют три различных способа доступа к элементам блока: 1 В теле класса bl ock определены типы iterator и const_iterator, и все элементы блока А содержатся в диапазоне [A begin(). A. end()). Такая форма особенно по-
5.2. Концепции контейнеров 87 лезна в обобщенных алгоритмах, потому что почти все обобщенные алгоритмы STL оперируют диапазонами итераторов. 2. Вложенные типы reverse.iterator и const_^everse_iterator — это типы “ревер- сных” итераторов, соответствующих iterator и const_iterator. Диапазон [A. rbegin(), A rend()) содержит те же элементы, что и диапазон [A begin(). A. end()), но в обратном порядке. 3. Если п — целое, то выражение А[п ] возвращает n-й элемент. Понятно, что первый способ является фундаментальным. Второй способ использует адаптер. Вложенные типы reverse_iterator и const_reverse_iterator — просто со- кращенная форма записи выражений reverse_iterator<iterator> И reverse_iterator<const_iterator> Аналогично, выражение А[л] является сокращенной формой записи A begin()[n J или, в случае с указателями, * (А. begin() + п). Важно, что для предоставления доступа к элементам блок определяет типы iterator Hconst_itera tor и функции-члены begin() и end(). Итераторы — это столь же фунда- ментальное понятие для контейнеров, как и для алгоритмов, причем они имею!' боль- шое значение в качестве основного интерфейса между алгоритмами и контейнерами. У всех контейнеров есть итераторы. Некоторые свойства итераторов класса block являются общими, а некоторые специфичны для блока. • Есть два различных типа итераторов: iterator и const_iteratcr Разница между ними состоит в том, что изменяемый блок предоставляет доступ к диапазону с помощью iterator, а константный блок — с помощью const_nerator. По опре- делению константным называется блок, элементы которого нельзя модифици- ровать, и таким образом, const_iterator — константный итератор. Он нс позво- ляет присваивать значение элементу, на который указывает. И наоборот iterator — это изменяемый итератор, позволяющий присваивать значение эле- менту. на который указывает. Не только block, но все контейнеры определяют типы iterator и const_iteratoi и функции-члены begin() nend().Tnn const_iteraror всегда является, как сле- дует из его названия, константным итератором, и всегда существует преобразо- вание из контейнерного итератора в константный. Если const_iterator всегда является константным итератором, естественно предположить, что iterator всегда является изменяемььм итератором, как в классе block, и что всегда должна быть возможность модифицировать значение, на ко- торое указывает итератор. Это предположение неверно. От контейнера не тре- буется, чтобы он предоставлял изменяемые итераторы, и iterator может предо- ставлять доступ либо для чтения и записи, либо только для чтения. Иногда для поддержки инвариантов классов необходимо ограничить или запретить доступ По записи. Это совсем не гипотетическая причина. Например, у ассоциативного контейнера set (с. 456) нет изменяемых итераторов. Для set типы iterator И const-iterator могу г быть одинаковы.
«8 Глава 5. Контейнеры • Типы iterator и const-iterator — непросто итераторы. Они являются Итератор рами Произвольного Доступа (из чего следует, что они являются и Двунапра^ ленными Итераторами и Однонаправленными Итераторами, так как Итератор Произвольного Доступа является развитием Двунаправленного Итератора, а Дву. направленный Итератор, в свою очередь, является развитием Однонаправленно- го Итератора). Тот факт, что итераторы блока являются Итераторами Произвольного Досту- па, важен ио двум причинам. Во-первых, можно определить, какие алгоритмы применимы к элементам блока. Итераторы Произвольного Доступа поддержива- ют больше алгоритмов, чем все остальные итераторы. Например, вы можете при- менить функцию sort (с. 293) к диапазону Итераторов Произвольного Доступа, ио не к диапазону Однонаправленных Итераторов. Во-вторых, это влияет на на- бор функций, предоставляемых классом block. Причина, по которой диапазон [A begiп(), A end()) имеет смысл, состоит в том, что итераторы блока являются Итераторами Ввода (поддержка диапазонов не входит в требования к Итераторам Вывода), а причина того, что диапазон | A rbegin(), A rend()) имеет смысл, состоит в том, что итераторы блока являют- ся Двунаправленными Итераторами. Движение в обратном направлении по диа- пазону итераторов возможно лишь в том случае, если итераторы поддерживают operator--. И наконец, доступ к элементу блока с помощью А[п], собственно, яв- ляется “произвольным доступом”; эта форма доступа имеет смысл, потому что у блока есть Итераторы Произвольного Доступа. Разумно предположить, что каждый контейнер должен иметь тип итератора, явля- ющегося моделью Итератора Ввода, потому что от контейнера, который не предо- ставляет способа обращения к его элементам, пользы немного, но мы не вправе тре- бовать, чтобы каждый тип итератора являлся моделью Итератора Произвольного До- ступа. Есть слишком много полезных структур данных, где произвольный доступ непрактичен. 5.2.3. Иерархия контейнеров Некоторые контейнеры, например block, могут и должны предоставлять Итераторы Произвольного Доступа. Другие же, однако, могут предоставлять только Двунаправ- ленные Итераторы или Однонаправленные Итераторы. Очевидно, “контейнер” — это не единая концепция, а несколько различных концепций. Мы можем классифициро- вать контейнеры по типу их итераторов. Кахкдый тип, являющийся моделью Контейнера (Container), определяет вложен- ные типы итераторов iterator и const_iterator, причем они оба являются моделями Итератора Ввода Оба типа могут быть одинаковыми. В любом случае const_iteratcr всегда является константным итератором и всегда возможно преобразование и-3 iterator в const_iterator. За исключением случая, когда эти два типа одинаковы, пре' ооразованис в обратном направлении невозможно Преобразование из константного итератора в изменяемый привело бы к серьезным нарушениям const-корректности. У каждого Контейнера есть функции-члены begin() и end(), и все его элементы содер' жатся в диапазоне [begin(), end()). Кроме того, каждый Контейнер имеет несколько вспомогательных типов: value_typo. pointer, const_pointer, reference, const_reference, difference_type и size_type. Они
5.2. Концепции контейнеров 89 определены для УД°бства, но не являются необходимыми. За исключением size_type, все они могут быть получены из типов iterator и const_iterator. Однонаправленный Контейнер — это Контейнер, итераторы которого являются Од- нонаправленными Итераторами, а Реверсивный Контейнер — это Однонаправленный Контейнер, имеющий Двунаправленные Итераторы. Тип, являющийся моделью Ревер- сивного Контейнера, должен определить два дополнительных вложенных типа, reverse_iterator и const_reverse_iterator, а также функции-члены, rbegin() и rend() Ц наконец, Контейнером Произвольного Доступа называется Реверсивный Контейнер, итераторы которого являются Итераторами Произвольного Доступа. Тип, являющий- ся моделью Контейнера Произвольного Доступа, имеет функцию-член орегатог[]. Ца рис. 5.1 показана эта иерархия. Контейнер (Итератор Ввода) Однонаправленный Контейнер (Однонаправленный Итератор) I Реверсивный Контейнер (Двунаправленный Итератор) I Контейнер Произвольного Доступа (Итератор Произвольного Доступа) Рис. 5.1. Иерархия концепций кошейнера В скобках пол названием каждой концепции указан пин итератора контейнера Все предопределенные классы контейнеров STL являются моделями Однонаправ- ленного Контейнера, и большинство из них являются моделями Двунаправленного Контейнера. Некоторые из них, например block, являются моделями Контейнера Про- извольного Доступа. 5*2.4. Тривиальный контейнер Класс block из раздела 5.1 представляет собой пример очень простого контейнера, но тем не менее это не самый простой контейнер в STL. Простейшая программа на та. которая ничего не делает: lnt main() {} Аналогично, простейший контейнер STL — тот, который ничего не содержит. У каждого контейнера есть функции-члены begin() и end(), возвращающие ите- раторы. Диапазон [begin(), end()) включает в себя все элементы контейнера. Трп- ниальнып контейнер (trivial container) — это контейнер, всегда содержащий пустой диапазон, а его размер всегда равен нулю: template <dass Т> struct trivial,.container {
90 Глава 5. Контейнеры typedef Т vaiuc_typc +ypeacf value_type* pointer, lypedef conot \zalue_type* corist_pointer. typedef value_type& reference, lypedef const value_type& const_reference, typedef value_type* iterator, typedef const value_type* const_iterator, lypedef ptrdiff_t difference.type. lypeoef size_t size_type const_]rerater begin() const { return 0. } const.]terator end() const { return 0, } iterator begin() { return 0, } iteraror end() { return 0, } size.type sjzef) const { return 0, } bool erupty() const { return true, } oi/e_tyoe max_size() const { return 0, } vend swap(t rivial_container&) {} Тривиальный контейнер — это курьезный случай, иллюстрирующий, как много н одновременно мало нужно сделать, чтобы удовлетворить требованиям к Контейне- ру, но кроме этого у него есть еще две реальные области применения: • Он удовлетворяет всем требованиям, предъявляемым к Контейнеру, и поэтому его можно использовать для тестирования обобщенных алгоритмов, аргумента- ми которых являются контейнеры. • Как видно из определения класса t rivial_container, контейнеры STL содержат множество однотипных кодов, детали которых легко упустить. Поэтому при на- писании нового контейнера STL в качестве отправной точки проще воспользо- ваться trivial_container, чем начинать с нуля. 5.3. Концепции контейнера переменного размера К iacc block представляет собой контейнер фиксированного размера. Количество элементов в блоке — константа, задаваемая при написании программы. Этот факт не является ни достоинством, ни недостатком. Контейнеры фиксированного размера иногда удобны, иногда нет. Не всегда знаешь, сколько элементов будет в контейнеро до того, как начнешь его заполнять. Концепции Контейнера (вместе с Однонаправленным Контейнером, Реверсивным Контейнером и Контейнером Произвольного Доступа) допускают возможность сушс' ствованпя контейнеров как с фиксированным, так и с переменным размером. ОднаК°
5.3 Концепции контейнера переменного размера 91 0ЛИ не могут обеспечить механизм добавления или удаления элементов (в против- ЛОМ случае контейнеры фиксированного размера вроде block не были бы моделями Контейнера). Это означает, что контейнеры переменного размера должны быть моде- лями какой-то другой концепции, являющейся развитием Контейнера. STL определяет два различных вида контейнеров переменного размера: Последо- вательность (Sequence)*’ и Ассоциативный Контейнер (Associative Container). Как Л в случае с концепцией самого Контейнера, каждая из этих концепций является кор- нем дерева своих собственных концептуальных иерархий. 5.3.1- Последовательности Последовательность (Sequence) как развитие концепции Однонаправленного Контей- нера — это наиболее очевидный тип контейнера переменного размера. Как и все дру- гие контейнеры, Последовательность представляет свои элементы в виде диапазона в строгом линейном порядке. Кроме того, она предполагает возможность ссылаться на любой элемент, добавлять или удалять элементы в любом месте этого диапазона. Таким образом, Последовательность содержит свои элементы не в некотором пред- определенном порядке, а наоборот, дает вам инструмент, позволяющий расположить элементы в любом требуемом порядке. функции-члены insert и erase, собственно, и являются отличительной чертой По- следовательности. Если S — последовательность, ар — итератор, указывающий на не- который элемент S, то S erase( р) изымает этот элемент из S и удаляет сто Аналогич- но, S. insert(p. х) создает новый объект, копию х, и вставляет его в S сразу перед эле- ментом, на который указывает р. Почему перед р, а не после? Потому что диапазоны итераторов асимметричны. Существует элемент, следующий за последним, но нет элемента, предшествующего первому. Вы можете поместить элемент в самое начало, вставив его перед S begin(), или в самом конце, вставив его перед S end(). Это было бы невозможно, если бы S insert(р, х) вставлял х после р, а не перед р. Эти две простые функции-члена поднимают два важных вопроса. • Что происходит с другими элементами Последовательности, когда вы вставля- ете или удаляете элементы? Мы, конечно, вправе ожидать, что их значения нс пострадают. Если у нас есть значения (1, 2, 3) и мы вставили 7 перед вторым элементом, то результатом будет (1, 7, 2, 3). В противном случае эта операция вряд ли заслуживала бы названия insert (‘‘вставка”)- Но проблема заключается не только в значениях. Элемент Контейнера — не просто значение, это объект, который существует в конкретной области памяти. Хотя вставка и удаление элемента не изменяют Другие значения в Последовательности, они могут повлиять на то, какой итера- тор на какой объект указывает. Эту проблему можно сформулировать более конкретно, задав вопрос: что про- исходит с итераторами, которые указывают на элементы в Последовательности, после того как мы вставили или удалили элемент? Допустим, S — это Последо- вательность, а р1 и р2 — итераторы, указывающие на два различных элемента S •) “п ^азва,,че “нос k °СЛеЛовательнос i т°ром конкрс'1 ’Юдина I ел ьность” иногда приводи! к некоторой путанице Обычное значение с юва ib” в шкрорма! икс и других областях — и port о набор объемов, ра< по юженных в не- м порядке Однако в STL.no! общий термин hciio.ii»<усня в качесте катания онре- Им1С,1НО1"| В нашей книге везде, где Последовательность нсно.и» iveic я в кач< с ни* концепции ется в виду нс общее, а совершенно конкретно юхническое шаиение
92 Глава 5 Контейнеры Теперь предположим, что мы вставляем новый элемент перед р1 или удаляем элемент, на который указывает р1. Что происходит с р2? Он по-прежнему укц. зывает на тот же элемент, что и раньше? Или другой вопрос: указывает ли он ца что-нибудь вообще? Может ли так случиться, что объект, на который он раньще указывал, теперь просто исчез? • Каковы накладные расходы при использовании insert и erase? Являются ли они простыми и быстрыми операциями или же потенциально требуют значитель- ных ресурсов? Предположим, например, что элемент вставляется где-то в сере- дине Последовательности из тысячи элементов. Каких затрат времени следует ожидать? Выполнится ли эта операция в тысячу раз медленнее, если в Последо- вательности будет миллион элементов? На оба вопроса ответ один: все зависит от реализации. Ответы на эти вопросы и отли- чают одну Последовательность от другой. В STL есть три класса Последовательностей vector, list и deque, а основное различие между ними состоит в типе предоставля- емых ими итераторов, семантике неопределенных итераторов и вычислительной сложности insert и erase. Например, у класса vector есть Итераторы Произвольного Доступа, и свои элемен- ты он хранит в одной непрерывной области памяти. Он похож на Ь1оск за исключени- ем того, что его размер может изменяться. Если вы вставляете новый элемент в сере- дину вектора, то вы должны переместить остальные элементы в направлении конца вектора, чтобы освободить место. Это означает, что insert — потенциально медлен- ная операция (время исполнения линейно зависит от количества элементов, находя- щихся между точкой вставки и концом вектора) и что она влияет на другие итерато- ры, указывающие на вектор. И напротив, list — эго контейнер, основанный на свя- занных узлах, итераторы которых — Двунаправленные Итераторы. Добавление нового элемента в список не требует перемещения существующих элементов и не делает не- допустимыми какие-либо другие итераторы. Другие формы insert и erase В Последовательности insert и erase являются перегруженными функциями-члена- ми. Кроме вариантов, работающих с одним элементом, Последовательность содер- жит и другие их варианты, вставляющие и удаляющие сразу целый диапазон. Если вам требуется вставить или удалить целый диапазон, обычно многоэлементная вер- сия работает быстрее (во всяком случае, не медленнее), чем многократный вызов од- ноэлементного варианта. Итак, если V — vector, a L — list, вы можете вставить все элементы L в начало V следующим образом: V insert(V begi..(). L begin(). L end()), Последовательности имеют конструкторы, аналогичные многоэлементным версиям insert. Вставка в начало или конец Во многих программах не возникает необходимости во вставке или удалении элемеН' тов в произвольные места Последовательности, а только лишь в самое начало иД11 в самый конец (например, чтение элементов в буфер). Вставка в начало или конец " важный частный случай.
5.3 Концепции контейнера переменного размера 93 Это особый случай еще и потому, что вставка элементов в начало или конец зачас- jq наампого быстрее, чем вставка в произвольное место. Например, время вставки Змеята в начало вектора будет иметь сложность O(N), где N — размер вектора, вставка элемента в конец — очень быстрая операция. Это приводит к появлению еще двух концепций, являющихся развитием концепции Последовательности По- следовательность с Концевой Вставкой (Back Insertion Sequence), например vector, — это Последовательность, для которой существует способ вставки и удаления элемен- та в конце за амортизированное константное время (amortized constant tune) Последовательность с Начальной Вставкой (Front Insertion Sequence) имеет три спе- циальные функции-члена: front, возвращающую первый элемент в контейнере, push-front, вставляющую элемент в начало, и pop_f ront, удаляющую первый элемент Последовательность с Концевой Вставкой имеет три соответствующих метода: Ьас^. push__backHpop_back. Семантика вставки и перезаписи При первом знакомстве с STL одной из наиболее типичных ошибок является, напри- мер, код: int А[5] = {1. 2, 3. 4, 5}, vector<int> V. сору (А, А + 5. V begin()), Предложенная запись попытки скопировать пять элементов в vector совершенно не- корректна. Если вы попытаетесь это исполнить, программа аварийно завершит работу. Поразмыслив, легко понять, в чем заключается проблема. Алгоритм сору копируем элементы из одного диапазона в другой. В нашем случае копируются элементы из диапазона [А, А+5) в диапазон [V begin(), V. begin()+5). Это означает, что выполняет- ся присваивание *(V begin()) = А[0], затем *(V. begin()+1)=А[1]ит.д. Это не может работать! Вектор V пуст, и элементы, которым мы пытаемся присвоить значения, не су- ществуют. Другой аргумент заключается в том, что у сору нет способа добавления новых эле- ментов в V, потому что сору видит не весь V, а только его итераторы. Таким образом, копирование в диапазон итераторов V имеет семантику “перезаписи”. Происходит присваивание новых значений существующим элементам V, а не вставка новых эле- ментов. Для вставки нового элемента в V необходимо вызвать одну из функций-чле- нов V. На самом депо есть способ использования сору для вставки новых элементов в По- следовательность с помощью адаптера insert_iterator (с. 347) или одного из связан- Hbix адаптеров: f ront_insert_iterator либо back_insert_iterator. Итератор insert iterator конструируется из конкретной Последовательности. Это Итератор в°Да, и присваивание ему значения при помощи insert-iterator эквивалентно ставке этого значения в базовую Последовательность Адаптеры позволяют иметь итераторы, которые реализуют семантику вставки, а не Резаписп. Вот пример корректного использования сору для вставки значений vector: lot А[5] - {1 2 3. 4 5} vector<w> v С°РУ(А. А + 5 back_]nserter(V)).
94 Глава 5. Контейнеры 5.3.2. Ассоциативные контейнеры Последовательность не подразумевает какого-либо упорядочения. Можно вставцТ1 эчемент в любое место Последовательности. Есть другой вид контейнеров пере. менного размера, гарантирующий упорядоченность своих элементов в соответствии с некоторым правилом. Работая с подобным контейнером, вы не можете вставить элемент в конкретное место, а просто добавляете его — и контейнер сам размещает эчемент. Зачем может понадобиться контейнер, упорядочивающий свои элементы? Самая очевидная причина состоит в том, чтобы быстрее найти элемент. Если вы ищете эле- мент в упорядоченном диапазоне из N элементов, то лучший способ — воспользоваться функциями линейного поиска find или find_if. Затраты на поиск элемента составля- ют O(N). В среднем вы должны будете проверить половину элементов диапазона, прежде чем найдете искомый. Но если элементы организованы специальным обра- зом в структуру данных, предназначенную для облегчения поиска, то можно умень- ши гь затраты с O(N) до O(log TV) по крайней мере. Например, если элементы всегда упорядочены в порядке возрастания, то вместо линейного поиска вы сможете вос- пользоваться двоичным поиском (lower_bound). Ассоциативный Контейнер (Associative Container) — это контейнер переменного размера, который поддерживает эффективный поиск элементов (значений) иоключу (key). Каждый элемент в Ассоциативном Контейнере имеет соответствующий ему кчюч, и Ассоциативный Контейнер может быстро найти элемент по этому ключу. Базовыми операциями над Ассоциативными Контейнерами являются поиск, встав- ка и удаление. Вы, однако, могли бы выполнять данные операции с Последовательно- стью. Ассоциативный Контейнер отличается, во-первых, наличием функций-членов, явно поддерживающих названные операции, и во-вторых, эффективностью опера- ций: в среднем O(logA). Ассоциативные Контейнеры сложнее Последовательностей. Есть несколько момен- юв, которыми классы Ассоциативных Контейнеров отличаются друг от друга. • У каждого элемента Ассоциативного Контейнера есть ключ, и поиск элементов осуществляется по ключу. Как у всех контейнеров, у Ассоциативного Контейне- ра есть тип значения. Кроме того, есть дополнительный тип - тип ключа. Каж- дое значение типа value_tyoe связано с некоторым значением типа key_type. Существует много способов связи значения с ключом. Например, тип значе- ния может быть записью с несколькими полями, а ключ может быть одним и-3 этих полей. В STL определены две концепции Ассоциативного Контейнера, ко- торые предоставляют два способа связи. Простым Ассоциативным Контейнеров (Simple Associative Container) называется Ассоциативный Контейнер, у которого va] de type и key_type — это один и тот же тип. Ключом элемента является сам элемент. Парным Ассоциативным Контейнером (Pair Associative Container) на- зывается Ассоциативный Контейнер, value_type которого имеет вид pair<cons' Key, Т>. Значением Парного Ассоциативного Контейнера является pai г, а ключом первое поле pair Почему pair<const Key, Т>, а нс просто pair<Key, Т>? По тем же самым причП' нам, почему нельзя поместить новый элемент в произвольное место Ассоциа- тивного Контейнера. Позиция элемента в Ассоциативном Контейнере определя- ется его ключом, и поэюму изменение ключа элемента привело бы к повреждС'
5.3 Концепции контейнера переменного размера 95 нию внутренней структуры Ассоциативного Контейнера. В Парном Ассоциатив- ном Контейнере, где первое поле элемента является ключом, оно должно быть неизменно. В Простом Ассоциативном Контейнере ключом элемента является сам элемент, и его значение нельзя изменять. Элементы можно добавлять и удалять, но менять их текущее значение запрещено. Простой Ассоциативный Контейнер обеспечивает только константные итераторы, но не изменяемые. • Что происходит, когда вы вставляете элемент в Ассоциативный Контейнер, а в нем уже присутствует элемент с таким ключом? Может ли Ассоциативный Контей- нер содержать два различных элемента с одинаковыми ключами? Множественный Ассоциативный Контейнер (Multiple Associative Container) мо- жет, а Уникальный Ассоциативный Контейнер (Unique Associative Container) — нет. Во Множественный Ассоциативный Контейнер элемент добавляется всегда Контейнер не проверяет наличие элемента с таким же ключом. А попытка доба- вить элемент в Уникальный Ассоциативный Контейнер будет тщетна, если в кон- тейнере уже есть элемент с таким же ключом. • В каждом Ассоциативном Контейнере элементы организованы при помощи клю- ча. И есть по крайней мере два различных направления развития Ассоциативно- го Контейнера, которые представляют два способа организации элементов. Хе- шированным Ассоциативным Контейнером (Hashed Associative Container) назы- вается Ассоциативный Контейнер, элементы которого располагаются в виде хеш-таблицы. Одним из его вложенных типов является функциональный объект, используемый в качестве функции хеширования. Аналогично, Сортированным Ассоциативным Контейнером (Sorted Associative Container) называется Ассоци- ативный Контейнер, чьи элементы всегда отсортированы в порядке возрастания ключа. У него также есть тип вложенного функционального объекта, Бинарный Предикат, который используется для сравнения двух ключей. Данные три отличия взаимно независимы. Например, контейнерный класс STL set является моделью Сортированного Ассоциативного Контейнера, Уникального Ассоци- ативного Контейнера и Простого Ассоциативного Контейнера. То есть элементы set образуют диапазон, который всегда отсортирован в порядке возрастания, ключом элемента является сам элемент, и никакие два элемента set не равны друг другу. Ана- логично, контейнерный класс multimap является моделью Сортированного Ассоциа- тивного Контейнера, Множественного Ассоциативного Контейнера и Парного Ассоциа- тивного Контейнера. Ассоциативный Контейнер, как и Последовательность, должен определить функ- ции insert и erase. Так же, как и в случае Последовательности, можно вставлять и уда- лять элементы либо по одному, либо целым диапазоном. Единственное сушествен- н°е отличие состоит в том, что в случае Последовательности функция-член получает аРгумент, который указывает, куда вставить элемент (или диапазон), а в случае Ассо- циативного Контейнера — нет. Для того чтобы вставить один элемент в Ассоциатив- Ный Контейнер, запишем: С inserf(x) аЧтобы вставить диапазон — С insert(f i)
96 Глава 5. Контейнеры Предположим в качестве простого примера, что L — это 1 ist<int>. Можно вставит^ все элементы L в set одним оператором: ser<int> S, S insert(L beginO, L end()) Множество S будет содержать все элементы L, отсортированные в порядке возраста- ния с удаленными дублирующимися значениями. У Ассоциативных Контейнеров тоже есть функции-члены поиска элементов по клю- чу. Например, выражение С f±nd(k) возвращает итератор, который указывает на элемент с ключом к (или С end(), если такого элемента нет), а выражение 0 count(к) возвращает количество элементов, ключи которых равны к. Для Уникального Ассоци- ативного Контейнера возвращаемое значение всегда либо нуль, либо единица. 5.3.3. Аллокаторы Многие контейнеры, например block, используют динамическое выделение памяти. Так, list — это связный список узлов, и в динамической памяти под каждый узел выделяется память. Иногда полезно управлять способом выделения памяти под контейнер. Например, хорошо бы иметь возможность выбора между надежной многонитевой схемой выде- ления и более быстрой, но безопасной только при наличии одной-единствснной нити управления схемой. Или при желании использовать не обычную динамическую па- мять, а выделенную заранее. Это один из примеров более общей проблемы предоставления клиенту возможно- сти управлять некоторыми аспектами поведения класса, и мы уже знаем решение, можно инкапсулировать поведение в виде параметра шаблона. Так, алгоритмы вроде f ind_if и sort, а также контейнерные классы вроде set используют функциональные объекты. В нашем случае нельзя воспользоваться функциональным объектом, потому что выделение памяти задействует несколько различных операций. Как минимум, оно включает в себя функции выделения и освобождения памяти, которые должны быть согласованы друг с другом (нельзя выделять память при помощи new и освобождать ее с помощью free). Но идея остается прежней: стратегия управления памятью кон- тейнера определяется параметром шаблона, и этот параметр, Аллокатор (Allocator) (с. 176), используется во всех низкоуровневых операциях выделения и освобожд^' ния памяти. Аллокаторы не являются частью требований к Контейнеру (или Последовательно- сти, или Ассоциативному Контейнеру). Однако все предопределенные контейнерный классы STL позволяют задавать аллокаторы в качестве параметров шаблона. В большинстве случаев вообще не приходится думать об аллокаторах. Можно за- давать их с помощью параметров стандартных контейнеров, но всегда есть значений параметра по умолчанию. Очень редко возникают ситуации, вынуждающие восполь- зоваться нестандартным аллокатором.
5.4. Заключение 97 5.4. Заключение КзК видно из рис. 5.2, полная иерархия концепций контейнеров довольно сложна. Возникает вопрос: действительно ли необходима такая сложность, есть ли польза от оПределения концептуальной иерархии и классов контейнеров в качестве моделей концепций. Определение иерархии действительно полезно. Во-первых, она реализует систе- матику контейнеров, благодаря этому легко понять, например, почему интерфейсы vector и list очень похожи друг на друга и почему они не могут быть совершенно идентичны. Во-вторых, она позволяет писать обобщенные алгоритмы, которые рабо- тают непосредственно с контейнерами. Контейнер Однонаправленный Последовательность с Начальной Вставкой Последовательность с Концевой Вставкой Контейнер Контейнер Рис. 5.2. Иерархия концепций Контейнера, вк'иочая концепции Последовательность и Ассоциативный Контейнер
98 Глава 5. Контейнеры 5.4.1. Каким контейнером воспользоваться? Ввиду того что все контейнеры STL являются моделями одной-единственной когь цепции, они предоставляют очень схожую функциональность. Несколько различных классов контейнеров могут показаться подходящими для решения вашей задачи. Вы обнаружите, что можно изменить используемый контейнер, переписав в программе одну строчку (например, typedef). В качестве общего правила можно отметить, что наилучшим выбором обычно яв- ляется самый простой из приемлемых классов. Если вы полагаете, что операция встав- ки в середину контейнера будет производиться не часто, тогда, скорее всего, наибо- лее эффективным выбором будет vector (или, если не требуется изменение размера контейнера и если его размер известен на этапе компиляции, вы можете сделать еще один шаг и воспользоваться классом block). Однако если предполагается, что вставка будет частой операцией, то следует воспользоваться контейнерами вроде list или si 1st, которые созданы специально для этого. 5.4.2. Определение вашего собственного контейнера Предопределенных контейнерных классов STL иногда бывает недостаточно, и при- ходится писать свои собственные. Как мы видели в примере с блоком, контейнер не обязан быть слишком сложным. Концепция Контейнера накладывает относительно немного требований. Однако контейнерные классы переменного размера, Последо- вательность и Ассоциативный Контейнеру достаточно сложны. Если вы планируете написать новую Последовательность или Ассоциативный Контейнер, сначала убеди- тесь, что хорошо понимаете специфические требования, изложенные в главе 9.
Часть II Справочное руководство: концепции STL
6 Базовые концепции Концепции, описанные в этой главе, имеют всеобщий характер. Они применимы к лю- бой обобщенной библиотеке, а не только к STL. Большинство встроенных типов язы- ке! C++ и многие типы, реализованные в STL, являются моделями этих концепций. Регулярный тип (regular type)— это тип, который является моделью Присваива- емого, Конструируемого по Умолчанию и Сравнимого. Определяемый пользователем тип должен быть регулярным, если нет важных причин для того, чтобы он не являлся моделью одной из этих концепций. 6.1. Присваиваемый Тип является Присваиваемым (Assignable), если объекты этого типа можно копиро- вать, а переменным этого типа присваивать значения. Копия объекта должна быть эквивалентна оригиналу. Она должна иметь то же са- мое значение. Можно предположить, что постусловием присваивания является ра- венство. Это не совсем так. Проблема заключается в том, что тип, который является моделью Присваиваемого, не обязательно также является моделью Сравнимого; во- обще, нс обязательно, что какой-либо operateг== существует. Если тип является моделью как Присваиваемого, так и Сравнимого, то копия х действительно должна быть равна х. Является развитием Эта концепция не является развитием каких-либо других концепций STL Система обозначений X Тип, который является моделью Присваиваемого. х, у Объекты типа X. Допустимые выражения • Конструктор копирования Хбх)
6.2. Конструируемый по Умолчанию 101 Возвращаемый тип: X Постусловие: Х(х) является копией х. • Конструктор копирования X х(у), X х = у Постусловие: х является копией у. • Оператор присваивания х = У Возвращаемый тип: Х& Постусловие: х является копией у. Модели Почти все встроенные типы языка C++ являются моделями Присваиваемого, за ис- ключением const Т. Например, int — это модель Присваиваемого (можно копировать и присваивать значения типа int), a const int — нет. Если х объявлено с типом const int, то х = 7 — недопустимое выражение. Можно скопировать переменную типа const int, но ей нельзя что-либо присвоить. Точно так же тип paircconst int, int> не является Присваиваемым. 6.2. Конструируемый по Умолчанию Тип является Конструируемым по Умолчанию (Default Constructible), если у него есть конструктор по умолчанию, то есть если можно создать объект этого типа, не иници- ализируя его каким-либо конкретным значением. Является развитием Эта концепция не является развитием каких-либо других концепций STL. Система обозначений X Тип, который является моделью Конструируемого по Умолчанию. х» У Объект типа X. Допустимые выражения Конструктор по умолчанию Х() Возвращаемый тип: X Конструктор по умолчанию X X Заметим, что форма х х = Х() ^обязательно является допустимым выражением, поскольку использует конструк- Р Копирования. Она допустима для всех регулярных типов, так как они являются
102 Глава 6. Базовае концепции моделями как Конструируемого по Умолчанию, так и Присваиваемого, но не допусти- ма для типов, являющихся только моделями Конструируемого по Умолчанию. Модели Почти все встроенные типы языка C++ являются моделями Конструируемого по Умолчанию. Вы можете, например, объявить переменную типа int без инициализа- ции. Многие классы STL, такие как vector<int>, также являются Конструируемыми по Умолчанию. 6.3. =Сравнимый Тип является =Сравнимым (Equality Comparable), если объекты этого типа можно сравнивать на равенство с помощью operator== и если operator== является отноше- нием эквивалентности (equivalence relation). Отношения эквивалентности определя- ются ниже. Они представляют собой формализацию обычных интуитивных свойств равенства. Является развитием Эта концепция не является развитием каких-либо других концепций STL. Система обозначений X Тип, который является моделью ^Сравнимого. х, у, z Объект типа X. Допустимые выражения • Равенство X == у Возвращаемый тип: Преобразуемый к bool. Предусловие: х и у находятся в области определения operator==. (Термин область определения (domain) используется здесь в обыч- ном математическом смысле этого слова.) • Неравенство X ' = у Возвращаемый тип: Предусловие: Преобразуемый к bool. х и у находятся в области определения operateг==. Семантика: Эквивалентно записи ’ (х == у). Инварианты Выражения, включающие в себя значения, =Сравнимые, должны удовлетворять сле- дующим инвариантам, которые определяют отношение эквивалентности. • Тождественность Из &х -= следует, что х == у. • Рефлексивность X X.
6.4. Упорядочение 103 • Симметричность Из х == у следует, что у == х. • Транзитивность Из х == у и у == z следует, что х == z. Отношения эквивалентности являются фундаментальным понятием в математике. Это любое отношение, которое удовлетворяет инвариантам рефлексивности, сим- метричности и транзитивности. Модели Все встроенные числовые типы и типы указателей языка C++ являются моделями =Сравнимого. Таковы также многие типы STL, например vector<int>. Вообще, vector<T> является =Сравнимым тогда и только тогда, когда Т является =Сравнимым. 6.4. Упорядочение 6-4.1. <Сравнимый Тип является <Сравнимым (LessThan Comparable), если он упорядоченный. Можно сравнить два объекта этого типа с помощью operator<, причем operate г< задает согла- сованное упорядочение. С технической точки зрения, operateг< должен порождать частичное упорядоче- ние (см. ниже), однако это не строгое требование. Будучи развитием концепции <Срав- нимого, концепция Строго Слабо Сравнимого налагает более сильное требование на упорядочение, порождаемое оператором operators Является развитием Эта концепция не является развитием каких-либо других концепций STL. Система обозначений X ' Тип, который является моделью <Сравнимого. х» У» z Объект типа X. Допустимые выражения • Меньше X < у Возвращаемый тип: Преобразуемый к bool. Предусловие: х и у находятся в области определения operator^ (Термин об- ласть определения используется здесь в обычном матема- тическом смысле этого слова.) * Больше X > у Возвращаемый тип: Преобразуемый к bool. Предусловие: х и у находятся в области определения operators Семантика: Эквивалентно записи у < х.
104 Глава 6. Базовае концепции • Меньше или равно X у Возвращаемый тип: Преобразуемый к bool. Предусловие: х и у находятся в области определения operators Семантика: Эквивалентно записи ! (у<х). • Больше или равно X >- у Возвращаемый тип: Преобразуемый к bool. Предусловие: х и у находятся в области определения operators Семантика: Эквивалентно записи 1 (х < у). Из этих четырех операторов только один является действительно фундаменталь- ным Остальные — просто удобные сокращения. В качестве фундаментального мож- но произвольно выбрать любой из них. Здесь мы остановились на определении трех других операторов в терминах operateг<. Инварианты Выражения, включающие в себя значения, <Сравнимые, должны удовлетворять сле- дующим инвариантам. • Нерефлексивность X < X должно быть ложным. • Антисимметричность Из х < у следует, что ! (у < х). • Транзитивность Из х < у и у < z следует, что х < z. Эти инварианты являются определением частичного упорядочения (partial order- ing) — отношения, у которого отсутствует рефлексивность, но есть транзитивность. Антисимметричность — это теорема, а не аксиома. Она вытекает из отсутствия ре- флексивностп и наличия транзитивности. Модели Встроенные числовые типы в языке C++ являются моделями <Сравнимого. Кроме того, моделями являются и типы указателей, даже при условии, что сравнение двух произвольных указателей не обязательно является допустимым. Вот почему мы требуем, чтобы х и у были в области определения opera tore. В случае указателей х п у находятся в области определения operateг<, если они указывают в один и тот же массив. 6.4.2. Строго Слабо Сравнимый Тип относится к модели Строго Слабо Сравнимого (Strict Weakly Comparable), есД11 он является моделью <Сравнимого, и если operateг< удовлетворяет не только требе' вапиям частичного упорядочения, но также и более строгим требованиям строго?0 слабого упорядочения (strict weak ordering).
6.4. Упорядочение 105 формальное определение строгого слабого упорядочения приводится ниже. По существу строгое слабое упорядочение проявляется в случае, если два элемента об- ладают свойством, по которому ни один их них не меньше, чем другой, что позволяет оасценивать их в некотором смысле эквивалентными друг другу. Определение строгого слабого упорядочения может показаться абстрактным й усложненным, и можно подивиться, зачем мы вообще беспокоимся об определе- нии. Концепция Строго Слабо Сравнимого важна по двум причинам. Во-первых, ал- горитмы, которые используют какое-либо отношение упорядочения (например, ал- горитмы сортировки), полагаются на свойства строгого слабого упорядочения. Кон- цепция <Сравнимого не обеспечивает достаточных гарантий корректной работы алгоритма сортировки. Во-вторых, любое приемлемое отношение упорядочения, ко- торое можно придумать, будет, скорее всего, строгим слабым упорядочением. Является развитием «^Сравнимого (с. 103) Система обозначений X Тип, который является моделью Строго Слабо Сравнимого. х, у, z Объект типа X. Определения Два объекта хну эквивалентны, если отношения х < у и у < х одновременно являются ложными. (Заметим, что в силу отсутствия рефлексивности, каждый объект х всегда эквивалентен сам себе. Также надо отметить, что если х эквивалентен у, то у эквива- лентен х.) Полным упорядочением называется отношение упорядочения, обладающее таким свойством: для любых двух объектов х и у возможно либо х < у, либо у < х, либо х == у Допустимые выражения Только те, что определены в требованиях к <Сравнимому. Инварианты В дополнение к инвариантам, определенным в требованиях к <Сравнимому, должен выполняться следующий инвариант. Транзитивность эквивалентности Если х эквивалентен у, а у эквивалентен z, то х должен быть эквивалентен z. Все ЭТО можно записать в следующем виде: из '(х < у) && 1(у < х) && '(у < Z) && '(Z < у) следует, что ’(х < Z) && '(z < х) ^тематически данная запись означает, что определенные выше отношения элсмен- в заслуживают своего имени. Это есть отношение эквивалентности. Строгое слабое °Рядоченис разделяет значения на совокупности классов эквивалентности (equi- впее classes). В пределах одного класса все значения эквивалентны друг другу
106 Глава 6. Базовае концепции Полное упорядочение — специальный вид строгого слабого упорядочения. Спещь фичность заключается в том, что это строгое слабое упорядочение со специальны.^ свойством, согласно которому каждый класс эквивалентности содержит одно едиц. ственное значение. Модели Встроенные числовые типы в языке C++ являются моделями Строго Слабо Сравни- мого. В самом деле, operator^ для целых чисел обеспечивает полное упорядочение, а не только строгое слабое упорядочение. Строгие слабые упорядочения, не являющиеся полными упорядочениями, пред- ставляют собой чрезвычайно общую категорию — гораздо более общую, чем пред- ставляется при рассмотрении формального определения. Любое сравнение, рассмат- ривающее объекты, которые необходимо сравнить только частично, а не полностью, скорее всего, будет строгим слабым упорядочением. Например, сравнение строк, не- чувствительное к регистру, — это строгое слабое упорядочение. Аналогично, если у класса есть два поля — first_name (имя) и last_name (фамилия), а его ope rate г< рас- сматривает только last_name, то это модель Строго Слабо Сравнимого. Всякий раз, когда два значения х и у содержат одинаковые фамилии, но разные имена, они будут эквивалентны (одновременно х < у и у < х ложно), но не идентичны.
Итераторы Итераторы — центральная идея STL. Они представляют собой обобщение указате- лей в том смысле, что это объекты, указывающие на другие объекты и, как подсказы- вает имя, позволяющие выполнять итерации по диапазону объектов. Именно итераторы дают возможность отделить контейнеры от алгоритмов, опери- рующих с контейнерами. Большинство алгоритмов STL работают с диапазонами ите- раторов, а не с самими контейнерами непосредственно. Так как все контейнеры STL имеют итераторы, то один и тот же алгоритм можно применять ко многим типам кон- тейнеров. Контейнеру нужно только обеспечить способ доступа к его элементам по- средством итераторов. Концепция Тривиального Итератора — это абстракция, соответствующая понятию объекта, указывающего на какой-то другой объект. Прочие пять концепций в этой главе представляют объекты, которые указывают на другие объекты и могут выпол- нять итерации по диапазону объектов. Основное различие между концепциями за- ключается в том, что они поддерживают разные формы итерации. 7.1. Тривиальный Итератор Тривиальный Итератор (Trivial Iterator) представляет собой объект, который может °Ь1ТЬ разыменован для обращения к другому объекту. Арифметические операции, такие как инкремент и сравнение, могут не поддерживаться. Тривиальный Итератор является таковым, потому что это итератор, не выполняющий итерации! Является развитием Присваиваемого (с. 100), =СравнимЪго (с. 102). Ассоциированные типы Тип значения Тип значения, полученный при разыменовании Тривиального Итератора. Система обозначений X Тип, который является моделью Тривиального Итератора.
108 Глава 7. Итераторы Т Тип значения X. х, у Объект типа X. t Объект типа Т. Определения • Тип, который является моделью Тривиального Итератора, может быть изменя- емым, при этом значения, ссылающиеся на объекты этого типа, могут быть изме- нены, или же константным, если они не могут быть изменены. Например, int* - это изменяемый тип итератора, a const int* — константный тип итератора. Если тип итератора изменяемый, то тип его значения есть модель Присваиваемого, но обратное не обязательно верно. • Тривиальный Итератор может иметь сингулярное значение: результаты большин- ства операций, включая сравнение на равенство, не определены. Единственная операция, для которой гарантируется поддержка, — это присваивание несингу- лярного итератора сингулярному итератору. • Тривиальный Итератор может иметь значение, которое можно разыменовать, и в результате его разыменования образуется вполне определенное значение. Ра- зыменуемые итераторы всегда несингулярны, но обратное неверно. Например, указатель за последний элемент является несингулярным (он связан с конкрет- ным массивом, и с ним можно производить четко определенные операции), од- нако при этом его нельзя разыменовать. • Разымснуемый итератор можно сделать недопустимым в результате выполнения операции, после которой! итератор становится неразыменуемым, или сингулярным Например, если р — указатель, то операция delete р делает р недействительным. Допустимые выражения В дополнение к выражениям, определенным в Присваиваемом и =Сравнимом, долж- ны быть допустимы следующие выражения: • Разыменование *х I [редусловие: х может быть разыменован. Возвращаемый тип: Преобразуемый кТ’\ • Присваивание при разыменовании *х = t Требования к типу: X — тип изменяемого итератора. Предусловие: х может быть разыменован. Постусловие: *х — копия t. • Доступ к члену класса х->т ) Требование к тину. возвращаемому выражением »х. сформулированное как “преобразуемый к т (convertible to Т), правп н>нсс, чем просто Т, но।ому чю фактически возвращаемы!! тип *х обычно нс т а сс ылка пли const-ссылка иаТ Иногда iimcci смысл возвращать некий замещающий объект вмес ю обьск- • а. на который копией |уальпо указывает пгераюр Замещающие обвею ы являются подробностями реал’1' зацпи. а не час 1ыо ишерфейса, полому они нс иоявляклся в фебовапиях к Тривиальному Итератору
7.2. Итератор Ввода 109 Требования к 1ииу: Т — inn, для которого определено выражение t m. Предусловие: х может быть разыменован. Семантика: Эквивалентно (*х). т. Первоначальная версия HP STL не определяла operateг-> для итераторов. В стандарте языка C++ он есть, но не все реализации STL поддерживают его, что зависит от одной из особенностей языка C++, которая еще недостаточно широко поддерживается компиля- торами C++. Между тем пока придется писать (*х) m вместо х->т. Никакой дополни- тельной функциональности operate г-> не обеспечивает; это только сокращение. Гарантии сложности Все операции в требованиях к Тривиальному Итератору должны выполняться за кон- стантное амортизированное время. Инварианты Выражения, использующие Тривиальные Итераторы, должны соответствовать следу- ющему инварианту: • Тождественность х==у тогда и только тогда, когда &*х ==&*у. Модели Фактически в языке Си есть Тривиальные Итераторы, хотя они не различаются систе- мой типов языка Си. Указатель на объект, который не является частью массива, есть Тривиальный Итератор, потому что только указатель в массив может быть инкремен- тирован. Все типы итераторов, определенные в STL, имеют операции инкремента, то есть все типы итераторов STL являются моделями других концепций итераторов, а не толь- ко Тривиального Итератора. 7.2. Итератор Ввода Итератор Ввода (Input Iterator) — это итератор, который можно разыменовать, чтобы обратиться к некоторому объекту, и инкрементировать, чтобы получить следующий итератор в последовательности. Не требуется, чтобы Итератор Ввода был изменяемым, а также поддерживал обыч- НУЮ семантику разыменования, которая допускает использование многопоточных алгоритмов. Например, не гарантируется, что через один и тот же самый Итератор 8вода 1 можно пройти дважды. Является развитием Тривиального Итератора (с. 107). Ассоциированные типы Тип значения typename iterator_traits<X> value_type Тип значения, получаемого в результате разыменования Итератора Ввода
110 Глава 7. Итераторы • Разностный тип typename iterator_traits<X>.:difference_type Знаковый целый тип, который используется для представления расстояния меж- ду итераторами или количества элементов в диапазоне. • Ссылочный тип typename iterator_traits<X> reference Ссылка на тип значения итератора. Это изменяемая ссылка, если итератор из- меняемый, и const-ссылка, если итератор константный. (Если тип значения Т, то тип ссылки обычно Т& или const Т&.) • Тип указателя typename iterator^raits<X> -pointer Указатель на тип значения итератора. Это изменяемый указатель, если итера- тор изменяемый, и const-указатель, если итератор константный. (Если тип зна- чения — Т, то тип указателя обычно Т* или const Т*.) • Категория итератора typename iterator_traits<X>-:iterator_category ^Один из тегов итератора: input_iterator_tag, forward_iterator_tag, bidirectional_iterator_tag или random_access_iterator_tag. Категория итера- тора — это тег, соответствующий наиболее специфичной (most specific) концеп- ции, моделью которой является итератор. Например, int* — это модель Итератора Произвольного Доступа. Это также модель Двунаправленного Итератора, Однона- правленного Итератора и т. д. Его категория — это random_access_iterator_tag, потому что Итератор Произвольного Доступа — наиболее конкретное понятие, моделью которого является int*. (Итератор Произвольного Доступа является раз- витием всех других концепций итераторов.) Система обозначений X Тип, который является моделью Итератора Ввода. Т Тип значения X. 1, J Объект типах. t Объект типа Т. Определения • Итератор называется итератором за последний элемент (past to end), если он указывает за последний элемент контейнера. Значения за последний элемент несингулярны и неразыменуемы. • Итератор допустйм (valid ), если он разыменуемый или указывает за последний элемент. • Итератор 1 можно инкрементировать, если существует “следующий” итератор’ то есть операция ++1 определена. Итераторы за последний элемент нельзя иН' крементировать.
7.2. Итератор Ввода 111 • Итератор Ввода j достижим (reachable) из Итератора Ввода i, если после приме- нения operator++ к i конечное число раз будет получено i == j • Запись [i, j) относится к диапазону итераторов, начинающемуся с 1 и продол- жающемуся вплоть до j, не включая j. • Диапазон [1, j) является допустимым диапазоном, если 1 и j — допустимые ите- раторы, a j достижим из 1. Каждый итератор в допустимом диапазоне [х, j) мо- жет быть разыменован, a j либо разыменуем, либо указывает за последний эле- мент. Поскольку инкрементирование возможно лишь для разыменуемых итера- торов, то каждый итератор в диапазоне разыменуем. Допустимые выражения В дополнение к выражениям, определенным в Тривиальном Итераторе, должны быть допустимы следующие выражения. • Разыменование *1 Возвращаемый тип: Преобразуемый к Т. Семантика: Возвращает элемент, на который указывает 1. Предусловие: 1 может быть инкрементирован. • Преинкремент ++1 Возвращаемый Предусловие: тип: Х& 1 может быть разыменован. Постусловие: 1 может быть разыменован или указывать за последний элемент. После выполнения ++1 не требуется, чтобы копии старого значения х были разыменуемы или чтобы они на- ходились в области определения operator—. Тип, который является моделью Итератора Ввода, не обязан поддержи- вать более одного активного итератора на одном диапазоне. Постинкремент (void) ]++ Предусловие: Семантика: 1 может быть разыменован. Эквивалентно (void) ++х. Постусловие: 1 может быть разыменован или указывать за последний эле- мент. Постинкремент и разыменование Возвращаемый тип: Т Предусловие: i может быть разыменован. )Для Итератора Ввода из i == j не обязательно следует ++i == ++] Концепция Однонаправленного Ите- т°ра снимает эго ограничение
112 Глава 7. Итераторы Семантика: Эквивалентно: Tt = *1, ++1 return t, Постусловие: 1 может быть разыменован или указывать за последний элемент. Гарантии сложности Все операции выполняются за константное амортизированное время. Модели Почти все типы итераторов STL являются моделями Итератора Ввода, и то же самое верно для указателей. Тип, подобный deque<int>. iterator, однако, является моде- лью не только Итератора Ввода, но и других концепций итераторов Пример типа итератора, который является моделью Итератора Ввода и не являет- ся моделью других концепций итераторов, — это ist ream_iterator (с. 351). Этот тип действительно имеет странную и ограничительную семантику, которая требуется от Итератора Ввода. 7.3. Итератор Вывода Итератор Вывода (Output Iterator) — это тип, обеспечивающий механизм для сохра- нения последовательности значений, но не обязательно доступ к ней. Итераторы Вы- вода в некотором смысле обратны Итераторам Ввода, но у них гораздо более ограни- чивающий интерфейс. Они не обязательно обеспечивают доступ к членам или равен- ство, и у них не обязательно есть ассоциированный разностный тип и тип значения. Кроме того, Итераторы Вывода имеют те же ограничения, что и Итераторы Ввода. Они не поддерживают многопроходные алгоритмы, и не гарантируется, что можно пройти через один и тот же Итератор Вывода дважды. Итератор Вывода можно представить себе в виде ленты: можно записать значение в текущую позицию и продвинуться к следующей позиции, но нельзя прочитать зна- чение и нельзя вернуться назад или перемотать ленту в начало. Является развитием Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Заметим, что Итератор Вывода не является развитием ^Сравнимого (с. 102): в требо- ваниях к Итератору Вывода нет ope rate г==. Отметим также, что, в отличие от всех других концепций итератора, Итератор Вывода не является развитием Тривиального Итератора Ассоциированные типы • Категория итератора typename iterator_traits<X> iterator_category Один из тегов итератора: output_iterator_tag, forward_iterator_tag- bid] rectiobal_iterator_tag или random_access_iterator_tag. См. требования к Ите- ратору Ввода (с. 109), где приводится подробное описание.
7.3. Итератор Вывода ИЗ Заметим, что у Итератора Вывода нет многих ассоциированных типов, которые есть Р других концепциях. В частности, у него не определен ассоциированный тип значе- ния. Другие типы итераторов, включая Тривиальный Итератор и Итератор Ввода, име- ют понятие о типе значения, которое возвращается, когда итератор разыменовывает- ся. Это понятие не применимо к Итераторам Вывода, так как для них оператор разы- менования (унарный operator*) не возвращает определенного значения. Оператор разыменования можно использовать только в контексте присваивания, например а *х = t. Хотя Итераторы Ввода и Итераторы Вывода — приблизительно симметричны, су- ществует важная асимметрия между операциями доступа и сохранения значений. Для Итератора Ввода operator* должен возвращать некоторый уникальный тип, но для Итератора Вывода в выражении *х = t нет причины, по которой operator должен при- нимать уникальный тип. Следовательно, Итератор Вывода не нуждается в каком-либо “типе значения”. Система обозначений X Тип, который является моделью Итератора Вывода. х, у Объект типа X. Определения • Если х — это Итератор Вывода типа X, то выражение *х = t сохраняет значение t в х. Заметим, что operators как и другие функции языка C++, может быть пере- гружен. Он фактически может быть даже шаблоном функции. Таким образом, t может иметь один из нескольких различных типов. Тип Т принадлежит набору типов значений X, если для объекта t типа Т выражение *х = t определено и не требуется применять к t какие-либо нетривиальные преобразования. • Итератор Вывода может быть сингулярным, это означает, что результаты боль- шинства операций, включая копирование и присваивание при разыменовании, не определены. Единственная операция для сингулярного итератора, которая обязательно обеспечивается, — это присваивание несингулярного итератора син- гулярному. • Итератор Вывода можно разыменовывать, то есть операция присваивания для него определена. Итераторы, которые можно разыменовать, всегда несингуляр- ны, но несингулярные итераторы не обязательно могут быть разыменованы. Допустимые выражения п ° Дополнение к выражениям, определенным в Присваиваемом, должны быть до- пустимы следующие выражения. Конструктор копирования х У(х), х у = х Предусловие: х является несингулярным. Постусловие: Выражение *у = t имеет то же поведение, что и выражение *х = t. (Заметим, что в любой момент должна быть активна только одна копия конкретного Итератора Вывода. То есть
114 Глава 7. Итераторы после создания и использования копии у Итератора Вывода х исходный итератор х больше не должен использоваться.) Оператор присваивания у = X Возвращаемый тип: Х& Предусловие: х является несингулярным. Постусловие: Выражение *у = t имеет то же самое поведение, что и выра- жение *x = t. Присваивание при разыменовании *х = t Требования к типу: t может быть преобразован к типу из набора типов значе- ний/. Возвращаемый тип: Предусловие: Результат не используется. х может быть разыменован. Если присваивание итератору х уже производилось, то после него должен был быть ин- кремент. (Ожидается, что перед первым инкрементом х должно было быть присваивание. Любой другой порядок операций приводит к неопределенному поведению. Напри- мер, *x = t; ++х; *x = t2; ++х — это допустимая последователь- ность операций, а *х = t; ++х; ++х; *x = t2; — нет.) Преинкремент ++х Возвращаемый тип: Х& Предусловие: х может быть разыменован. Было присваивание с участи- ем х. Если х был инкрементирован, то после этого было присваивание. Постусловие: х указывает на следующее место, куда может быть поме- щено значение. Постинкремент (void) х++ Возвращаемый тип: void Предусловие: х может быть разыменован. Было присваивание с участи- ем х. Если х был инкрементирован, то присваиваний с тех пор не было. Семантика: Эквивалентно (void) ++х. Постусловие: х указывает на следующее место, куда может быть поме- щено значение. Постинкремент и присваивание *х++ = t Требования к типу: t может быть преобразован к типу из набора типов значе- ний X. Возвращаемый тип: Результат не используется.
7.4. Однонаправленный Итератор 115 Предусловие: х может быть разыменован. Если ранее было присваивание с участием х, то после него производился инкремент. Семантика: Эквивалентно: *х = t ++х, Постусловие: х указывает на следующее место, куда может быть поме- щено значение. Следует отметить, что выражение *х = t непременно должно быть определено, но вы- ражение *х само по себе не обязательно возвращает какое-либо полезное значение. Таким образом, Итератор Вывода не всегда указывает на что-нибудь; он всего лишь посредник, который может использоваться при присваивании. Если вы реализуете Итератор Вывода классах, то разумная реализация выражения *x=t состоит в том, чтобы X:: operator* возвращал объект некоторого закрытого клас- са Х_ргоху, у которого определен метод X_proxy:: operator Обратите внимание- вы можете перегрузить X_proxy. :operator= или определить его в виде шаблона-члена. Такая реализация позволяет присваивать более одного типа с помощью Итератора Вывода класса X. Гарантии сложности Все операции выполняются за константное амортизированное время. Модели Следующие типы являются примерами Итераторов Вывода: • f ront_insert_iterator (с. 342) • back_insert_iterator(с. 345) • insert_iterator (с. 347) • ostream_iterator (с. 354) 7.4. Однонаправленный Итератор Однонаправленный Итератор (Forward Iterator) соответствует обычному интуитив- ному представлению о линейной последовательности значений. Однонаправленные Итераторы (в отличие от Итераторов Ввода и Итераторов Вывода) можно использо- вать в многопроходных алгоритмах. Однако Однонаправленные Итераторы, как под- сказывает их имя, не позволяют пройти по диапазону в обратном направлении. Тип, являющийся моделью Однонаправленного Итератора, может быть изменяемым либо константным, как определено в требованиях к Тривиальному Итератору (с. 107). Является развитием конструируемого по Умолчанию (с. 101), Итератора Ввода (с. 109), Итератора Вывода (с. 112). Ассоциированные типы Тс Же, что и для Итератора Ввода. Система обозначений Тип, который является моделью Однонаправленного Итератора Т Тип значения X.
116 Глава 7. Итераторы 1 , j Объект типах, t Объект типа Т. Допустимые выражения Однонаправленный Итератор не определяет каких-либо новых выражений поми\10 тех, что определены для Итератора Ввода. Однако некоторые ограничения, действу- ющие для Итератора Ввода, ослаблены. В частности, инкремент Однонаправленного Итератора не делает недопустимыми копии старого значения. Гарантируется, чТо если 1 и j могут быть разыменованы и i == j, то ++i == ++]. Следовательно, можно пройти через один и тот же Однонаправленный Итератор дважды. • Конструктор по умолчанию X х, Х() Постусловие: х может быть сингулярным. • Преинкремент Возвращаемый тип: Х& Предусловие: 1 может быть разыменован. Семантика: 1 изменяется, чтобы указать на следующее значение. Постусловие: • Постинкремент 1++ 1 может быть разыменован или указывать за последний элемент. &i == &++1. Если i == j, то ++i == ++j. Возвращаемый тип: X Предусловие: 1 может быть разыменован. Семантика: Эквивалентно: X tmp = 1, ++i, return tmp, Постусловие: • Разыменование 1 может быть разыменован или указывать за последний элемент. Возвращаемый тип: Если X — изменяемый тип итератора, то возвращаемы*1 тип — это модифицируемое lvalue. Если X — константный тип итератора, то возвращаемый тип — rvalue или const lvalue. (Возвращаемый тип — это тип ссылки X, обычно Т& или const Т&.) Семантика: Возвращает элемент, на который указывает i. Предусловие: i может быть инкрементирован. Гарантии сложности Операции с Однонаправленными Итераторами выполняются за константное амортИ' зированное время.
7.5. Двунаправленный Итератор 117 глеДУЮШие типы являются примерами Однонаправленных Итераторов: • slis^<int> iterator • slist<double> const-iterator • hash_set<string> iterator • char* (также является моделью Двунаправленного Итератора и Итератора Про- извольного Доступа) 7.5. Двунаправленный Итератор двунаправленный Итератор (Bidirectional Iterator) — это итератор, который может быть как инкрементирован, так и декрементирован. Требование, согласно которому Двунаправленный Итератор может быть декрементирован, — единственное, что отли- чает Двунаправленные Итераторы от Однонаправленных Итераторов. Является развитием Однонаправленного Итератора (с. 115). Ассоциированные типы Те же, что и у Однонаправленного Итератора. Система обозначений X Тип, который является моделью Двунаправленного Итератора. Т Тип значения X. 1, j Объект типа X. t Объект типа Т. - Допустимые выражения В дополнение к выражениям, определенным в Однонаправленном Итераторе, долж- ны быть допустимы следующие выражения. • Предекремент --1 Возвращаемый тип: Предусловие: Х& 1 может быть разыменован или указывать за последний эле- мент. Существует такой разыменуемый итератор j, что 1 -= ++1 Семантика: Постусловие: j. ’ ’ j. 1 изменяется, чтобы указать на предыдущее значение. 1 может быть разыменован. &i == &--1. Если i == j, то --1 == - -j. Если j может быть разыменован, a i == ++j, то 1 — — 1 Постдекремент 1 J • Возвращаемый тип: Предусловие: X 1 может быть разыменован или указывать за последний элемент. Существует такой разыменуемый итератор j, что 1 ==++].
118 Глава 7. Итераторы Семантика: Эквивалентно: X tmp = 1, --1, return tmp, Гарантии сложности Операции с Двунаправленными Итераторами выполняются за константное амортизи- рованное время. Инварианты Выражения, использующие Двунаправленные Итераторы, должны соответствовать следующему инварианту: • Симметрия инкремента и декремента Если 1 может быть разыменован, то ++i, --1. является нулевой (пустой) операцией. Аналогично, если операция —i опреде- лена, то — 1. ++i; является нулевой (пустой) операцией. Модели Следующие типы являются примерами Двунаправленных Итераторов: • list<int>:iterator • set<string> :iterator • char* (также является моделью Итератора Произвольного Доступа) 7.6. Итератор Произвольного Доступа Итератор Произвольного Доступа (Random Access Iterator) — это итератор, который обеспечивает инкремент и декремент (как и Двунаправленный Итератор), а также ме- тоды для продвижения вперед и назад на шаги произвольного размера и методы для сравнения двух итераторов, причем эти методы выполняются за константное время. Итераторы Произвольного Доступа обеспечивают все операции хорошо знакомой арифметики указателей языка Си. Является развитием Двунаправленного Итератора (с. 117), Строго Слабо Сравнимого (с. 104). Ассоциированные типы Те же, что в Двунаправленном Итераторе. Система обозначений X Тип, который является моделью Итератора Произвольного Доступа- Т Тип значения X. D Тип разности X. i,j Объект типа X.
7.6. Итератор Произвольного Доступа 119 Объект типа Т. Объект типа D. допустимые выражения р дополнение к выражениям, определенным в Двунаправленном Итераторе, должны быть допустимы следующие выражения. • Сложение итераторов 1 += п Возвращаемый тип: Х& Предусловие: С учетом 1 должно быть л разыменуемых или указываю- щих за последний элемент итераторов, следующих после i (если п положительно), или -п — предшествующих i (при отрицательном л). Семантика: Если п > 0, то эквивалентно выполнению операции ++1 всего л раз. Если п < 0, то эквивалентно выполнению операции -1 всего | п | раз. Если п == 0, то 1 += л является пустой операцией. Заме- тим, что слово “эквивалентно” просто означает, что i += л выда- ет тот же итератор, как если бы i инкрементировать (де- крементировать) п раз. Это не значит, что именно так должен быть реализован ope rato г+=; фактически его нельзя так реали- зовывать. Гарантируется, что 1 += л выполняется за констант- ное амортизированное время, независимо от величины л. Постусловие: 1 может быть разыменован или указывать за последний элемент. • Сложение итераторов 1 + п п + 1 Возвращаемый тип: X Предусловие: То же, что и для 1 += л Семантика: Эквивалентно: X tmp = 1, return tmp += п, Постусловие: Формы 1 + п и л +1 идентичны. Результат может быть разыменован или указывать за по- следний элемент. Вычитание итераторов 1 -= п Возвращаемый Предусловие: тип: Х& С учетом 1 должно быть л разыменуемых или указываю- щих за последний элемент итераторов, предшествующих i (если л положительно), или - л — следующих после 1 (при Семантика: отрицательном л). Эквивалентно i += (-л). Постусловие: 1 может быть разыменован или указывать за последний элемент.
120 Глава 7. Итераторы • Вычитание итераторов 1 - п Возвращаемый тип: Предусловие: Семантика: X То же, что и для i -= п Эквивалентно: X tmp - 1, return tmp -= n, Постусловие: Результат может быть разыменован или указывать за по- следний элемент. • Разность 1 - j Возвращаемый тип: D Предусловие: 1 достижим из j или ] достижим из 1 либо одновременно. Семантика: Возвращает такое число п, что i == j + п. • Меньше 1 < ] Возвращаемый тип: Преобразуемый к bool. Предусловие: i достижим из j или j достижим из 1 либо одновременно. Полезно сравнить это предусловие с предусловием, исполь- зующимся в описании Строго Слабо Сравнимого: 1 и j долж- ны быть в области определения operator^ В сущности здесь задается такая область определения: пара итераторов, один из которых достижим из другого. Это напоминает обычное правило языка Си о возможности сравнения двух указате- лей, только если они указывают на один и тот же массив. Семантика: Та же, что описана в Строго Слабо Сравнимом. Все другие операции сравнения (>, <= и >=) определены в терминах operators поэтому и они имеют семантику, описанную в Строго Слабо Сравнимом. • Доступ к элементу 1[п] Возвращаемый тип: Преобразуемый к Т. Предусловие: i + п существует и может быть разыменовано. Семантика: Эквивалентно * (i + п) *}. • Присваивание элемента 1[п] = t Требования к типу: X является изменяемым типом итератора. > В языке Си есть небольшая синтаксическая странность если р — указатель, то выражения р[' -» и п[р] эквивалентны Эта эквивалентное гь, однако, не гарантируется для любых Итераторов Произвол^ ного Доступа Нужна только поддержка 1 [л] Эю несущественное ограничение, так как эквивален i nod1’ р[г] н п[р] используется только в соревнованиях на самый залу тайный код па языке Си
7.6. Итератор Произвольного Доступа 121 Возвращаемый тип: Предусловие: Семантика: Постусловие: Преобразуемый к Т 1 + п существует и может быть разыменовано. Эквивалентно * (i + n) = t. 1 [ п ] является копией t. Гарантии сложности рее операции с Итераторами Произвольного Доступа выполняются за константное амортизированное время. Эта гарантия сложности является единственной причиной сушествования Итератора Произвольного Доступа как отдельной концепции. Каждая операция в арифметике итераторов может быть определена для Двунаправленных Итераторов, чем, собственно, и занимаются алгоритмы distance (с. 190) и advance (с. 192). Различие состоит в следующем: реализация Двунаправленных Итераторов выполняется за линейное время, а Итераторы Произвольного Доступа требуются для того, чтобы обеспечивать доступ к элементам за амортизированное константное вре- мя. Это очень важно для алгоритмов, которые специально используют тот или иной тип итераторов. Инварианты Выражения, использующие Итераторы Произвольного Доступа, должны соответство- вать следующим инвариантам. • Симметрия сложения и вычитания Если 1 + п определено, то 1 += П, 1 -= п. и (1 + п) - п, являются нулевыми операциями. Точно так же, если i - п определено, то 1 п, 1 += п, и (1 - п) + п, являются нулевыми операциями. • Связь между сложением и расстоянием Если 1 - j определено, то i == j + (i - j). • Расстояние и достижимость Если 1 достижим из j, то 1 - j > 0. Модели Любой тип указателя моделирует Итератор Произвольного Доступа. Указатели — са- Ые важные модели Итератора Произвольного Доступа. Кроме того, следующие типы ^Кже являются моделями Итератора Произвольного Доступа: vector<int>'-iterator • vector<int> const_iterator • deque<int>. iterator • deque<int>..const-iterator
8 Функциональные объекты Функциональный объект (function object), или функтор (functor), — это любой объект, который может быть вызван с помощью обычного синтаксиса вызова функции. Функ- циональный объект представляет собой указатель на функцию. Объект любого класса, у которого есть метод ope rato г(), также является функциональным объектом. Однако, например, указатель на нестатический метод не является функциональным объектом. Основные концепции функциональных объектов — это Генератор, Унарная Функ- ция и Бинарная Функция; они описывают объекты, которые можно вызвать, соответ- ственно, как f(),f(x)Hf(x,y). (Этот список, безусловно, можно продолжить тернар- ной функцией и т. д., но алгоритмы STL обычно не требуют функциональных объек- тов с более чем двумя аргументами.) Все остальные концепции функциональных объектов, определенные в STL, являются развитием этих трех концепций. Функциональные объекты, которые возвращают тип bool, представляют собой важ- ный частный случай. Унарная Функция, возвращающая тип bool, называется Предика- том, а Бинарная Функция, возвращающая тип bool, называется Бинарным Предикатом. Существует важная, но очень тонкая разница между функциональными объекта- ми и адаптируемыми функциональными объектами (adaptable function object). Вооо- ще функциональный объект имеет ограничения на тип своего аргумента, иногда до- вольно нетривиальные. Например, operator() может быть перегружен, может быть шаблоном-членом или тем и другим. Нет необходимости стремиться узнать из про- граммы, каковы эти ограничения. Адаптируемый функциональный объект точно определяет, каковы типы его аргУ' ментов и каков тип возвращаемого значения. Для этого у него есть вложенные типы, которые можно использовать в программах. Если тип F0 — модель Адаптируемого Генератора, то он должен определить вложенный тип FO. result_type. Аналогично, если тип F1 — модель Адаптируемой Унарной Функции, то он должен определитЬ вложенные типы F1 • . argument_type и F1: • result_type; наконец, если тип F2 — №' дель Адаптируемой Бинарной Функции, то он должен определить вложенные типЫ F2 first_argument_type, F2 second_argument_typeиF2‘ result_type. Адаптируемые функциональные объекты называются “адаптируемыми” потомУ’ что они могут быть использованы функциональными объектами-ядяшлердзш, то есть
8.1. Основные функциональные объекты 123 функциональными объектами, которые трансформируют другие функциональные объекты или как-то иначе ими манипулируют. В STL определяется множество раз- личных функциональных объектов-адаптеров, включая unary_negate, unary_compose, *ind1stHbind2nd. 8.1. Основные функциональные объекты 8.1.1. Генератор функциональный объект есть объект, который вызывается так, как если бы он был обычной функцией языка C++, а Генератор (Generator) представляет собой функцио- нальный объект, вызываемый без аргументов. Является развитием Присваиваемого (с. 100) Ассоциированные типы • Тип результата Тип, который возвращается при вызове Генератора. Система обозначений F Тип, который является моделью Генератора. Result Тип результата F. f Объект типа F. Определения • Диапазон Генератора — это набор всех возможных значений, которые он может возвращать. Допустимые выражения • Вызов функции Ю Возвращаемый тип: Result Семантика: Возвращает некоторое значение типа Result. Заметим, что два разных вызова f могут вернуть разные результаты. Генератор может обращаться к локальному состоянию, выполнять ввод/вывод и т. д. Выражение f () может даже изменить состояние f, то есть operator() не обязательно является const-методом. Например, f мог бы быть псевдо- Постусловие: случайным генератором чисел. Возвращаемое значение находится в диапазоне f. ^°Дели ^°бая функция, которая возвращает значение и не получает аргументов, ведет себя к Генератор. Например, указатель на функцию типа
124 Глава 8. Функциональные объекты 1DI (*)() есть модель Генератора. 8.1.2. Унарная Функция Функциональный объект есть объект, который вызывается так, как если бы он бы;1 обычной функцией языка C++, а Унарная Функция (Unary Function) — это функцио- нальный объект, вызываемый с одним аргументом. Является развитием Присваиваемого (с. 100) Ассоциированные типы • Тип аргумента Тип аргумента Унарной Функции. • Тип результата Тип, который возвращается при вызове Унарной Функции. Система обозначений F Тип, который является моделью Унарной Функции. X Тип аргумента F. Result Тип результата F. f Объект типа F. х Объект типа X. Определения • Область определения Унарной Функции представляет собой набор всех допусти- мых значений для ее аргумента. • Диапазон Унарной Функции — набор всех возможных значений, которые она мо- жет возвращать. Допустимые выражения • Вызов функции f(x) Возвращаемый тип: Result Предусловие: х находится в области определения f. Семантика: Вызывает f, передавая х в качестве аргумента, и возвращает значение типа Result. Заметим, что два разных вызова могут вернуть разные результаты, даже если f оба раза вЫ' зывается с одинаковым аргументом. Унарная Функция м°' жет обращаться к локальному состоянию, выполнять ввод вывод и т. д. Выражению f (х) даже разрешается изменить состояние f, то есть operator^) не обязательно является const-методом. Постусловие: Возвращаемое значение находится в диапазоне f.
8.1. Основные функциональные объекты 125 додели Любая функция, которая получает один аргумент и возвращает значение, работает Унарная Функция. Например, указатель на функцию типа double (*)(double) является моделью Унарной Функции. Более интересным примером представляется класс subt ractive_rng (с. 396). У объек- та типа subtractive_rng есть локальное состояние, а два следующих друг за другом вызова subt ractive_rng обычно возвращают неодинаковые результаты. 8.1.3. Бинарная Функция функциональный объект есть объект, который вызывается так, как если бы он был обычной функцией языка C++, а Бинарная Функция (Binary Function) — это функцио- нальный объект, вызываемый с двумя аргументами. Является развитием Присваиваемого (с. 100) Ассоциированные типы • Тип первого аргумента Тип первого аргумента Бинарной Функции. • Тип второго аргумента Тип второго аргумента Бинарной Функции. • Тип результата Тип, который возвращается при вызове Бинарной Функции. Система обозначений F X Y Result f X У Тип, который является моделью Бинарной Функции. Тип первого аргумента F. Тип второго аргумента F. Тип результата F. Объект типа F. Объект типа X. Объект типа Y. Определения Область определения Бинарной Функции есть набор всех упорядоченных пар (х, у), которые являются допустимыми значениями для ее аргументов. Диапазон Бинарной Функции есть набор всех возможных значений, которые она может возвращать. ^°пустимые выражения Вызов функции f(x. у)
126 Глава 8. Функциональные объекты Возвращаемый тип: Предусловие: Семантика: Постусловие: Result Упорядоченная пара (х, у) находится в области определи ния f. Вызывает f, передавая х и у в качестве аргументов, и воз- вращает значение типа Result. Заметим, что два разных вызова f могут возвращать разные результаты, даже если ? оба раза вызывается с одинаковыми аргументами. Бинар. ная Функция может обращаться к локальному состоянию, выполнять ввод/вывод и т. д. Выражению f (х, у) даже раз- решается изменить состояние f, то есть operate г () не обя- зательно является const-методом. Возвращаемое значение находится в диапазоне f. Модели Любая функция, которая получает два аргумента и возвращает значение, ведет себя как Бинарная Функция. Например, указатель на функцию типа double (*)(double, double) есть модель Бинарной Функции. 8.2. Адаптируемые функциональные объекты 8.2.1. Адаптируемый Генератор Адаптируемый Генератор (Adaptable Generator) — это Генератор с вложенным typedef, который определяет тип его результата. Вложенный typedef позволяет ис- пользовать функциональные объекты-адаптеры — в этом состоит единственная раз- ница между Адаптируемым Генератором и любыми другими Генераторами. Напри- мер, указатель на функцию Т (*f)() — это модель Генератора, но не Адаптируемого Генератора: выражение f: • result_type не имеет смысла, если f является указателем на функцию. Является развитием Генератора (с. 123). Ассоциированные типы • Тип результата R result_type Тип, который возвращается при вызове Адаптируемого Генератора. Система обозначений F Тип, который является моделью Адаптируемого Генератора. Допустимые выражения Только те, что определены для Генератора.
8.2. Адаптируемые функциональные объекты 127 по сеое еИого Генератора щодели ' не содержит каких-либо типов, являющихся моделями Адаптиру- . Приведем пример написанного пользователем Адаптируемого Генератора: struct counter { typedef int result_type, counter(result-type init = 0) n(init) {} result-type operator()() { return n++, } result_Type n. }; 8.2.2. Адаптируемая Унарная Функция Адаптируемая Унарная Функция (Adaptable Unary Function) — это Унарная Функция с вложенными объявлениями typedef, которые задают тип ее аргумента и тип резуль- тата. Объявления typedef позволяют использовать функциональные объекты-адап- теры, такие как unary_negate и unary_compose, — именно в них состоит единственная разница между Адаптируемой Унарной Функцией и любой другой Унарной Функцией. Например, указатель на функцию Т (*f )(Х) — это Унарная Функция, но не Адаптиру- емая Унарная Функция. Выражения f: :argument-type и f:: result-type не имеют смыс- ла, если f — указатель на функцию. Когда вы определяете класс, который является моделью Адаптируемой Унарной Функ- ции, необходимо предусмотреть вложенные типы. При этом проще всего унаследовать ваш класс от базового класса unary_function. Это пустой класс без методов и полей, содержащий только объявления типов. Он существует лишь для того, чтобы сделать определение Адаптируемых Унарных Функций более удобным. Класс unary_f unction очень похож на базовый класс iterator. Является развитием Унарной Функции (с. 124). Ассоциированные типы • Тип аргумента F: :argument_type Тип аргумента F. Тип результата F result_type Тип, который возвращается при вызове Адаптируемой Унарной Функции. Система обозначений F Тип, который является моделью Адаптируемой Унарной Функции. ^°Пустимые выражения °ЛьКо те, что определены для Унарной Функции.
128 Глава 8. Функциональные объекты Модели Следующие типы являются примерами Адаптируемых Унарных Функций: • negate • identity • pointer_to_unary_function Адаптер pointer_to_unary_function особенно интересен, потому что он позволяет использовать указатель на функцию в качестве Адаптируемой Унарной Функции. Указатель на функцию Result (*f )(Arg) не является Адаптируемой Унарной Функци- ей, а ptr fun(f) — является таковой. 8.2.3. Адаптируемая Бинарная Функция Адаптируемая Бинарная Функция (Adaptable Binary Function) — это Бинарная Функ- ция с вложенными определениями typedef, которые определяют типы ее аргументов и тип результата. Эти вложенные объявления позволяют использовать функцио- нальные объекты-адаптеры, такие как binderlst и binder2nd, — именно в них состоит единственная разница между Адаптируемой Бинарной Функцией и любой другой Бинарной Функцией. Например, указатель на функцию Т (*f)(X, Y) — это Бинарная Функция, но не Адаптируемая Бинарная Функция. Когда вы определяете класс, который является моделью Адаптируемой Бинарной Функции, нужно предусмотреть вложенные типы. Самый простой способ — унасле- довать класс от базового класса binary_function. Это пустой класс без методов и по- лей, содержащий только описания типов. Он существует лишь для того, чтобы сделать определение Адаптируемых Бинарных Функций более удобным. Класс binary_f unction очень похож на базовый класс iterator. Является развитием Бинарной Функции (с. 125). Ассоциированные типы • Тип первого аргумента F first_argument_type Тип первого аргумента F. • Тип второго аргумента F second_argument_type Тип второго аргумента F. • Тип результата F result_type Тип, который возвращается Адаптируемой Бинарной Функцией. Система обозначений F Тип, который является моделью Адаптируемой Бинарной функШ'114,
8.3. Предикаты 129 допустимые выражения Только те, что определены для Бинарной Функции. Додели gTL содержит множество Адаптируемых Бинарных Функций, в основном представля- ющих собой арифметические и логические операции. Следующие типы являются примерами Адаптируемых Бинарных Функций: • plus • multiplies • less • equal.to • pointer_to_binary_function 8.3. Предикаты 8.3.1. Предикат Предикат (Predicate) — это Унарная Функция, которая возвращает логическое значе- ние true (истина) или false (ложь) в зависимости от некоторого условия. Один из примеров Предиката — функция, получающая аргумент типа int и возвращающая true, если этот аргумент положительный. Является развитием Унарной Функции (с. 124). Ассоциированные типы У Предиката те же ассоциированные типы, что и у Унарной Функции, но с одним до- полнительным ограничением: тип результата Предиката должен быть преобразу- емым к bool. Система обозначений F Тип, который является моделью Предиката. * Тип аргумента F. f Объект типа F. х Объект типа X. Допустимые выражения Вызов функции f(x) Преобразуемый к bool. х находится в области определения f. Возвращает true, если условие, которое представляет со- бой f, выполняется, или false в противном случае. Возвращается либо true, либо false. Возвращаемый тип: Предусловие: Семантика: Постусловие:
130 Глава 8. Функциональные объекты Модели Функция, которая получает единственный аргумент и возвращает true или falSe как, например, функции классификации символов в стандартной библиотеке язьц^ Си, является Предикатом. Например, указатель на функцию типа bool (*)(char) есть модель Предиката. 8.3.2. Бинарный Предикат Бинарный Предикат (Binary Eradicate) есть Бинарная Функция, которая возвращает логическое значение true (истина) или false (ложь) в зависимости от некоторого условия. Один из примеров Бинарного Предиката — это функция, которая получает два аргумента и проверяет, равны ли они. Бинарные Предикаты важны, поскольку многие алгоритмы STL параметризуют не- которые аспекты своего поведения в виде Бинарных Предикатов. Таковы, например, алгоритмы search_n (с. 219) и mismatch (с. 230). Является развитием Бинарной Функции (с. 125). Ассоциированные типы У Бинарного Предиката те же ассоциированные типы, что и у Бинарной Функции, но с одним дополнительным ограничением: тип результата Бинарного Предиката дол- жен быть преобразуем к bool. Система обозначений F Тип, который является моделью Бинарного Предиката. X Тип первого аргумента F. У Тип второго аргумента F. f Объект типа F. х Объект типа X. у Объект типа У. Допустимые выражения • Вызов функции f(x, у) Возвращаемый тип: Преобразуемый к bool. Предусловие: Упорядоченная пара (х, у) находится в области определения Семантика: Возвращает true, если условие, которое представляет с° бой f, выполняется, и false в противном случае. Постусловие: Возвращается либо true, либо false. Модели Следующие типы являются примерами Бинарного Предиката. Первый из них — Tlirl указателя на функцию, а другие два — классы функциональных объектов, определяя' пые в STL:
8.3. Предикаты 131 • bool(*)(int,int) • equal_to<string> • less_equal<double> g 3.3- Адаптируемый Предикат дотируемый Предикат (Adaptable Predicate) — это Предикат, который является так- Адаптируемой Унарной Функцией. Таким образом, это Унарная Функция, возвраща- емый тип которой преобразуем к bool и которая содержит вложенные определения typedef s, соответствующие типу ее аргумента и возвращаемому типу. Является развитием Предиката (с. 129), Адаптируемой Унарной Функции (с. 127). Ассоциированные типы Только те, что ассоциированы с Предикатом и Адаптируемой Унарной Функцией. Адаптируемый Предикат должен иметь вложенный тип result_type, и этот тип ре- зультата должен быть преобразуем к bool. Допустимые выражения Только те, что определены в требованиях к Предикату и Адаптируемой Унарной Функции. Модели Следующие классы функциональных объектов STL являются примерами Адапти- руемого Предиката: • logical_not<bool> • binder1st<equal_to<int>> Указатель на функцию не является моделью Адаптируемого Предиката. Указатели на Функции не являются классами, и поэтому у них не может быть вложенных типов. 8-3.4. Адаптируемый Бинарный Предикат Адаптируемый Бинарный Предикат (Adaptable Binary Predicate) — это Бинарный Пре- дикат, который является также Адаптируемой Бинарной Функцией. Таким образом, Бинарная Функция, возвращаемый тип которой преобразуем к bool и содержит °Женные определения typedef, соответствующие типам ее аргументов и возвраща- еМомутипу. Является развитием Парного Предиката (с. 130), Адаптируемой Бинарной Функции (с. 128). Ас Тированные типы ДИей^0^’ ЧТ° ассоц11ИРованы с Бинарным Предикатом и Адаптируемой Бинарной Функ- 'Адаптируемый Бинарный Предикат должен иметь вложенный тип result_type, ттип результата должен быть преобразуем к bool.
132 Глава 8. Функциональные объекты Допустимые выражения Только те, что определены в требованиях к Бинарному Предикату и Адаптируемой нарной Функции. Модели В STL есть много классов, которые являются моделями Адаптируемого БинарноГо Предиката; эти классы обеспечивают проверку тех или иных отношений между дВу мя значениями. Вот, например, некоторые классы Адаптируемых Бинарных Предик тов STL: • less<T> • greater<T> • equal_to<T> • logical_and<T> 8.3.5. Строгое Слабое Упорядочение Строгое Слабое Упорядочение (Strict Weak Ordering) — это Бинарный Предикат, срав- нивающий два объекта и возвращающий истинность того, что первый предшествует второму. Этот предикат должен соответствовать стандартному математическому оп- ределению строгого слабого упорядочения. Точные требования излагаются ниже, но приблизительно они заключаются в том, что Строгое Слабое Упорядочение должно удовлетворять тем же требованиям, что и обычное отношение порядка типа “меньше чем”. Например, если а меньше Ь, то b не меньше а. Концепция Строгого Слабого Упорядочения и концепция Строго Слабо Сравнимого работают со строгим слабым упорядочением, но по-разному. Первая представляет собой набор требований к упорядочению, заданному оператором operate г< (требова- ния относятся к типу аргументов оператора operator<). Вторая является набором тре- бований к упорядочению, заданному функциональным объектом. Эти две концепции взаимно дополняют друг друга, потому что алгоритмы, исполь- зующие отношение порядка, зачастую существуют в двух вариантах, когда одна версия сравнивает значения с помощью operator^ а другая использует предоставленный пользователем функциональный объект. При этом первая версия использует концегг цшо Строго Слабо Сравнимого, а вторая — концепцию Строгого Слабого Упорядочения Является развитием Бинарного Предиката (с. 130). Ассоциированные типы У Строгого Слабого Упорядочения те же ассоциированные типы, что и у Бинарно^0 Предиката, но с одним дополнительным ограничением: типы первого и второго арп ментов должны быть одинаковы. Система обозначений F Тип, который является моделью Строгого Слабого Упорядочений X Тип аргументов F.
8.3. Предикаты 133 f X, У>2 Объект типа F. Объект типа X. Определения д а объекта х и у являются эквивалентными, если f(x, у) и f (у, х) одновременно зозвРащаЮТ f alse* Отметим, что объект всегда (в соответствие с инвариантом нереф- лексйвн°сти’ описанным ниже) эквивалентен самому себе. допустимые выражения • Вызов функции f(x. у) Возвращаемый тип: Преобразуемый к bool. Предусловие: Упорядоченная пара (х, у) находится в области определе- ния f. Возвращает t rue, если х предшествует у, и false в против- ном случае. Возвращаемое значение — либо t rue, либо false. Семантика: Постусловие: Инварианты Выражения, содержащие значения Строгого Слабого Упорядочения, должны соответ- ствовать следующим инвариантам. • Нерефлексивность f (х, х) должно возвращать false. • Антисимметрия Из f (х, у) следует, что 1 f (у, х). • Транзитивность Из f (х, у) и f (у, z) следует, что f(x, z). • Транзитивность эквивалентности Эквивалентность в вышеуказанном смысле транзитивна. Если х эквивалентно У» а у эквивалентно z, то х эквивалентно z. Это означает, что эквивалентность соответствует математическому определению отношения эквивалентности. Первые три аксиомы: нерефлексивность, антисимметрия и транзитивность — явля- **** определением частичного упорядочения. Транзитивность эквивалентности Обуется по определению Строгого Слабого Упорядочения. Полное упорядочение ^Ть Упорядочение, которое соответствует более сильному условию: эквивалент- сть представляет собой то же, что и равенство. Определение полного упорядочения очень простое, но, несмотря на простоту, ал- Ритмы STL его не используют. Требования полного упорядочения излишне строги резмерно ограничительны. Строгие Слабые Упорядочения, не обладающие полным °Рядочением, встречаются довольно часто. Одним из самых известных примеров ^ется нечувствительное к регистру сравнение строк.
134 Глава 8. Функциональные объекты Модели Следующие типы являются примерами Строгого Слабого Упорядочения: • less<int> • greater<int> • less<string> • greater<stnng> Вообще говоря, функциональные объекты less и greater связывают концепции Строгого Слабого Упорядочения и Строго Слабо Сравнимого. Классы less<T> и g reatе г<Т> — это модели Строгого Слабого Упорядочения тогда и только тогда, когда Т — модель Строго Слабо Сравнимого. Отметим, что less_equal<T> и greater_equal<T> не являются моделями Строгого Слабого Упорядочения. Строгое Слабое Упорядочение — это упорядочение, которое ведет себя как отношение “меньше чем”, а не как отношение “меньше или равно” Строгое Слабое Упорядочение f должно соответствовать условию, что f (х, х) воз- вращает false. 8.4. Специализированные концепции 8.4.1. Генератор Случайных Чисел Генератор Случайных Чисел (Random Number Generator) есть функциональный объект, который используется для генерации случайной последовательности целых чисел. Таким образом, если f — это Генератор Случайных Чисел, a N — положительное целое число, то f (N) возвращает целое число, которое меньше N и больше либо рав- но 0. Если f вызывается много раз с одним и тем же значением N, то полученная по- следовательность чисел равномерно распределена в диапазоне [О, N). Генераторы случайных чисел — деликатная проблема. Хороший генератор случай- ных чисел должен соответствовать многим статистическим характеристикам поми- мо равномерного распределения. См. раздел 3.4 у Кнута [Knu98a], где обсуждается случайная последовательность, и раздел 3.2, где описано несколько алгоритмов, ко- торые можно использовать для реализации генераторов случайных чисел. Является развитием Унарной Функции (с. 124). Ассоциированные типы • Тип аргумента Тип аргумента Генератора Случайных Чисел. Этот тип должен быть целочисленны*1 • Тип результата Тип, который возвращается при вызове Генератора Случайных Чисел. } Равномерней* распределение означает, что все числа в диапазоне |0, N) встречаются с равной час11 юй Вероятность получения каждого конкретного значения равна 1/N
8.4. Специализированные концепции 135 Снстема обозначений р Тип, который является моделью Генератора Случайных Чисел Integer Тип аргумента F. t Объект типа F. N Значение типа Integer. ОпреДеления • Область определения Генератора Случайных Чисел (набор допустимых значений его аргумента) есть набор чисел, которые больше нуля и меньше некоторого мак- симального значения. • Диапазон Генератора Случайных Чисел — это набор неотрицательных целых чи- сел, которые меньше, чем аргумент Генератора Случайных Чисел. Инварианты Выражения, использующие Генератор Случайных Чисел, должны соответствовать следующему инварианту. • Равномерное распределение В пределе, если f вызывается много раз с одним и тем же аргументом N, каждое целое число в диапазоне [О, N) будет встречаться одинаковое количество раз. 8.4.2. Функция Хеширования Функция Хеширования (Hash Function) есть Унарная Функция, которая используется Хешированными Ассоциативными Контейнерами (Hashed Associative Containers) (с. 171). Она получает единственный аргумент и преобразует его в результат типа size_t. Функция Хеширования должна быть детерминированной, то есть возвращае- мое значение должно зависеть только от аргумента, а равные аргументы должны по- рождать равные результаты. Производительность Хешированного Ассоциативного Контейнера критически зави- сит от его Функции Хеширования. В случае Функции Хеширования важно минимизи- ровать коллизии, то есть ситуации, когда два разных аргумента преобразуются в одно и то же значение функции хеширования. Также важно, чтобы распределение хеш- Значений было равномерным. Вероятность того, что Функция Хеширования возвра- щает какое-либо конкретное значение типа size_t, должна быть приблизительно та- кже, что и вероятность возвращения ею любого другого значения. Наконец, Функ- ЦняХеширования должна быть быстрой. Естественно, между этими тремя целями есть определенное противоречие. Те ТРемление к равномерности и минимизации коллизий имеет смысл только в кон- Мь ТеспециФического набора входных значений. Простой пример: предположим, что Хещируем шесть строк: “aardvark”, “trombone”, “history”, “diamond”, “forthright” 6^onable”. в этом случае приемлемой (и быстрой) функцией хеширования было Хеш ЗЯТПе пеРВ0Г0 символа каждой строки. С другой стороны, предположим, что мы ЧаебРУем “ааа1001”’ “аааЮЮ”, “аааООП”, “ааа1100”, “ааа0101” и “ааа0110”. В этом слу- дСсоОльше подойдет другая функция хеширования. Именно поэтому Хешированные Фун ^Иативные Контейнеры параметризуются функцией хеширования, ни одна хеш- не будет наилучшей для всякого приложения.
136 Глава 8. Функциональные объекты Заметим также, что Функция Хеширования не знает о количестве “ведер” (buckets) которые использует Хешированный Ассоциативный Контейнер. Функция Хеширование отвечает только за преобразование своего аргумента в число — возможно, очень бо^ь шое. Задача Хешированных Ассоциативных Контейнеров — превращать это больщ0е число в адрес конкретного “ведра”. Является развитием Унарной Функции (с. 124). Ассоциированные типы « Только те, что определены для Унарной Функции. Однако есть одно дополнительное ограничение: тип результата должен быть size_t. Инварианты В дополнение к инвариантам, определенным в требованиях к Унарной Функции, дол- жен быть выполнен следующий инвариант. • Детерминированная функция Возвращаемое значение зависит только от аргумента, а не от истории вызовов Функции Хеширования. Для любой конкретной Функции Хеширования возвраща- емое значение одно и то же, если передается один и тот же аргумент. Модели Единственная Функция Хеширования, реализованная в STL, — это класс hash (с. 395).
9 Контейнеры Контейнеры представляют собой объекты, которые содержат в себе другие объекты (элементы), управляют ими, а также предоставляют итераторы, позволяющие к этим объектам обращаться. Концепции контейнеров STL распадаются на три широкие категории. Во-первых, есть очень общая концепция — собственно Контейнер (Container) и три другие, менее общие концепции, классифицирующие контейнеры по типу предоставляемых ими итераторов. Все контейнеры STL являются моделями концепции Контейнера. Вто- рая концепция — это Последовательность (Sequence), описывающая контейнеры ди- намически изменяемого размера, в любом месте которых можно вставлять и удалять элементы. Третья концепция — Ассоциативный Контейнер (Associative Container), описывающая контейнеры, специально оптимизированные для поиска элемента по значению. И наконец, некоторые контейнеры можно параметризовать схемой выделения па- мяти с помощью параметра шаблона; многие контейнеры используют концепцию Аллокатора (Allocator). Эта концепция не является развитием концепции Контейне- ра, но тем не менее включена в эту главу ввиду своей тесной связи с контейнерами. 9.1. Общие концепции контейнеров 9-1.1. Контейнер Контейнер (Container) — объект, который хранит в себе другие объекты (его элемен- ) и у которого есть методы доступа к этим элементам. В частности, у каждого типа, ^аляющегося моделью Контейнера, есть ассоциированный тип итератора, который *но использовать для прохода по элементам Контейнера. концепции контейнера можно классифицировать по типам связанных с ними ите- ^т°ров. Контейнер, представляя собой самую общую концепцию контейнера, требу- лишь, чтобы тип связанного с ним итератора являлся моделью Итератора Ввода. Ответственно, вы не можете предполагать об итераторах Контейнера того, чего ^Ьзя предположить об Итераторах Ввода.
138 Глава 9. Контейнеры Нет гарантии, что элементы Контейнера хранятся в каком-либо конкретном рядке. В действительности, порядок может быть разным при каждой следующей итс< рации по элементам Контейнера. Кроме того, нет гарантии, что в каждый момент вр^ мени может быть более одного активного итератора (другие концепции контейнер0в например Однонаправленный Контейнер (с. 143), предоставляют большие гарантии) Контейнер “владеет” своими элементами, то есть время жизни элемента, храняще, гося в контейнере, не может превосходить времени жизни самого Контейнера. Эт0 лишь кажется серьезным ограничением. Если вам требуется менее жесткая семанти- ка владения, то можно использовать контейнер указателей. Указатели, в конце кон- цов, тоже объекты, и, как и все остальные, их можно поместить в Контейнер. Контей- нер по-прежнему будет владеть своими элементами — самими указателями, но не тем на что они указывают. Является развитием Присваиваемого (с. 100) Ассоциированные типы • Тип значения X value_type Тип объекта, хранимого в Контейнере, который должен быть Присваиваемым, но не обязательно Конструируемым по Умолчанию. • Ссылочный тип X reference Тип, который ведет себя как ссылка на тип значения Контейнера. Обычно ссы- лочный тип — это просто value_type&. (“Умные ссылки”, определяемые пользо- вателем и предоставляющие какую-либо дополнительную функциональность, вообще говоря, использовать нельзя. Определяемый пользователем тип не мо- жет иметь семантику, полностью совпадающую с семантикой ссылок C++. Язык C++ не поддерживает переопределение operator. (с точкой) — оператора доступа к членам). • Константный ссылочный тип X .const-reference Тип, который ведет себя как константная ссылка на тип значения Контейнера- Обычно это const value_type&. • Тип указателя X pointer Тип, который ведет себя как указатель на тип значения Контейнера. Тип указа' теля — это обычно value_type*. Необходимо, чтобы тип указателя имел точно такую же семантику, как в случае указателей C++, но он не должен быть именно указателем. “Умные указатели”, в отличие от “умных ссылок”, можно использО' вать. Язык C++ разрешает пользовательским типам определять operator* и operators (оператор разыменования и оператор доступа к члену класса чере3 указатель).
9.1. Общие концепции контейнеров 139 . Константный тип указателя X*-const-pointer Тип, который ведет себя как константный указатель на тип значения Контейне- ра. Обычно это const value_type*. • Тип итератора X*:iterator Итератор, указывающий на элементы Контейнера. Ожидается, что тип значения итератора является типом значения Контейнера, а его ссылочный тип и тип ука- зателя — это ссылочный тип и тип указателя Контейнера. Тип итератора должен быть моделью Итератора Ввода (с. 109). Заметим, что тип итератора должен быть только Итератором Ввода, который предоставляет очень слабый набор гарантий. В частности, все алгоритмы с Ите- раторами Ввода должны быть однопроходными. Концепция Однонаправленно- го Контейнера предоставляет более строгую гарантию того, что тип итератора является моделью Однонаправленного Итератора. • Константный тип итератора X:: const-iterator Константный итератор, указывающий на элементы Контейнера. Ожидается, что тип значения константного итератора является типом значения Контейнера, а его ссылочный тип и тип указателя — это константный ссылочный тип и констапт- . ный тип указателя Контейнера. Как и тип итератора, тип константного итерато- ра должен являться моделью Итератора Ввода. Тип итератора и тип константно- го итератора должны иметь один и тот же разностный тип и одну и ту же катего- рию итератора, а также должно существовать преобразование из типа итератора в тип константного итератора. Заметим, что типы итератора и константного итератора могут совпадать Не требуется, чтобы каждый тип Контейнера имел связанный с ним тип изменя- емого итератора. Например, классы set и hash_set не предоставляют изменяемых итераторов. Если iterator и const-iterator являются одним и тем же типом, то, следова- тельно, reference и const_reference, а также, соответственно, pointer и const_pointeг должны иметь один и тот же тип. Разностный тип X: difference_type Целочисленный тип со знаком, используемый для представления расстояния между двумя итераторами в Контейнере. Этот тип должен быть тем же, что и разностный тип итератора. Размерный тип -size_type Целочисленный тип без знака, в который помещаются все неотрицательные зна- чения разностного типа Контейнера.
140 Глава 9. Контейнеры Система обозначений X Тип, являющийся моделью Контейнера. а, ь Объект типах. Т Тип значения X. Определения • Размер (size) Контейнера — это количество элементов, которое он содержит. Раз, мер Контейнера — неотрицательное число. • Область (area) Контейнера — это общее количество байтов, которое он занимает или, конкретнее, сумма областей элементов, плюс накладные расходы самого Контейнера. Если тип Т значения Контейнера является простым типом (а не кон- тейнерным типом), то область Контейнера ограничена сверху произведением размера контейнера на sizeof (Т) и на некоторую константу. То есть если а — это контейнер с простым типом значения, то его область будет O(a. size()). • Контейнер переменного размера — это контейнер, предоставляющий методы для вставки и/или удаления элементов. Размер контейнера может изменяться. Кон- тейнер фиксированного размера — это контейнер, размер которого остается по- стоянным в течение всей его жизни. Размер типов контейнера фиксированного размера определяется на этапе компиляции. Допустимые выражения В дополнение к выражениям, определенным для Присваиваемого, допустимы следу- ющие выражения. • Конструктор копирования Х(а) Возвращаемый тип: X Постусловие: Х(). size() == a.size(). В Х() содержатся копии всех эле- ментов а. • Конструктор копирования X 0(a). Постусловие: b size() == a. size(). В b содержатся копии всех элемен- тов а. • Оператор присваивания b = а Требования к типу: b — изменяемый контейнер. Возвращаемый тип: Х& Постусловие: b. size() == a size(). В b содержатся копии всех элемен- тов а. • Деструктор а -Х() Семантика: Вызывается деструктор каждого элемента, и если под эле- мент была выделена память, то она освобождается.
9.1. Общие концепции контейнеров 141 Начало диапазона a begmO Возвращаемый тип: iterator, если а — изменяемый контейнер, const_iteraior в противном случае. Семантика: Возвращает итератор, указывающий на начальный элемент контейнера. Постусловие: Либо а. begin () может быть разыменовано, либо указывает за конец контейнера, что происходит тогда и только тогда, когда a. size() ==0. Конец диапазона a.endO Возвращаемый тип: iterator, если а — изменяемый контейнер, const_iterator в противном случае. Семантика: Возвращает итератор, указывающий за последний элемент контейнера. Постусловие: a end() указывает за конец. Два итератора a begin() и a. end() равны между собой, только когда a size() == 0. • Размер a.sizeO Возвращаемый тип: size_type Семантика: Возвращает размер контейнера а, то есть количество эле- ментов, содержащихся в нем. Постусловие: 0 <a.size() < a.max_size(). * Максимальный размер a.max_size() Возвращаемый тип: size_type Семантика: Возвращает верхнюю границу допустимого размера кон- тейнера. Обратите внимание, что max_size возвращает оцен- ку верхней границы, а не строгую верхнюю границу. То есть нет гарантии, что вы в действительности сможете создать контейнер типа X размера a.max_size(), но утверждается, что вы не сможете создать контейнер размера больше чем max_size(). В случае контейнера фиксированного размера a. size() и a. max_size() возвращают одно и то же значение. Постусловие: 0<а size()<a.max_size(). Проверка на наличие элементов а empty() Возвращаемый тип: Преобразуемый к bool. Семантика: Эквивалентно a size() == 0 (но, возможно, быстрее). Одна из причин, по которой эта функция-член существует, со- стоит в том, что a size() должна выполняться за время O(N), тогда как время выполнения empty — 0(1).
142 Глава 9. Контейнеры • Обмен значениями a swap(b) Требования к типу: а и 0 - изменяемые контейнеры. Возвращаемый тип: void Семантика: Эквивалентно: X tmp = а, а = Ь, b = tmp. (Но почти всегда быстрее. Если а и b содержат по N эле- ментов, то в случае обмена значениями между двумя кон- тейнерами с помощью operator потребуется присваи- ваний. Однако обычно удается реализовать функцию-член swap Контейнера так, что оценка сложности a swap(b) рав- на 0(1), а не 0(N).) Гарантии сложности • Затраты на конструктор копирования, оператор присваивания и деструктор ли- нейно зависят от размера контейнера. • begin() и end() выполняются за константное амортизированное время. • Затраты на size() линейно зависят от размера контейнера (для многих контей- неров, таких как vector и deque, затраты Hasize() составляют 0(1), что удовлет- воряет требованию 0(N)). • max_size() и empty() выполняются за константное амортизированное время. • Затраты на swap() линейно зависят от размера контейнера. (Однако для боль- шинства контейнеров они составляют константное амортизированное время. Их следует определять как 0(1), если нет веских причин, препятствующих этому.) Инварианты Выражения, в которых используются Контейнеры, должны удовлетворять следующим инвариантам. • Допустимый диапазон Для любого Контейнера а диапазон [a. begin(), a end()) является допустимым- (Однако гарантируется только то, что этот диапазон состоит из Итераторов Вво- да, а порядок элементов в диапазоне не определен. Концепция Однонаправлен- ного Контейнера предоставляет диапазоны Однонаправленных Итераторов и обес- печивает дополнительные гарантии упорядоченности элементов.) • Размер диапазона a size() == distanced begin(), a end()) • Полнота При итерации по элементам диапазона [a. begin (), a. end()) обращение произоП' дет к каждому элементу а, причем только один раз.
9.1. Общие концепции контейнеров 143 додели каждый контейнерный класс STL является моделью Контейнера. Например, таковы vector, slist и map. в действительности все классы контейнеров STL являются моделями Однонаправ- нного Контейнера. В STL нет ни одного класса, который был бы моделью только Контейнера. Примером класса, являющегося моделью Контейнера, но не Однонаправ- ленного Контейнера, мог бы послужить “самонастраивающийся массив” (self-adjusting array) или» в терминологии Кнута [Knu98b], “самоорганизующийся файл” (self- organizing file) — класс, который часто используемые элементы автоматически пере- вешает ближе к началу. 9.1.2. Однонаправленный Контейнер Однонаправленный Контейнер (Forward Container) — это Контейнер, элементы кото- рого расположены в определенном порядке. Порядок не может меняться случайным образом от итерации к итерации. Гарантия неизменности порядка позволяет опреде- лить отношение поэлементного равенства (если тип элемента контейнера является =Сравнимым) и отношение лексикографического упорядочения (если тип элемента контейнера является <Сравнимым). Итераторы Однонаправленного Контейнера отвечают требованиям к Однонаправ- ленному Итератору. Следовательно, Однонаправленные Контейнеры поддерживают многопроходные алгоритмы и допускают наличие нескольких активных итераторов водном контейнере одновременно. Является развитием Контейнера (с. 137), ^Сравнимого (с. 102), <Сравнимого (с. 103). Ассоциированные типы Только типы, которые определены в Контейнере. Однако требования к итераторам более строгие. Тип итератора должен быть моделью Однонаправленного Итератора (с. 115). Система обозначений X Тип, являющийся моделью Однонаправленного Контейнера. а» Ь Объект типа X. т Тип значения X. Допустимые выражения Кроме тех, что определены в Контейнере, должны быть допустимы следующие выра- жения. Равенство а == b Требования к типу: Т является =Сравнимым. Возвращаемый тип: Преобразуемый к bool. Семантика: Возвращает истинность того, что a size()== b size(), а каж- дый элемент а равен соответствующему элементу Ь.
144 Глава 9. Контейнеры • Неравенство а ’ - b Требования к типу: Т является =Сравнимым. Возвращаемый тип: Преобразуемый к bool. • Меньше а < b Требования к типу: Т является <Сравнимым. Возвращаемый тип: Преобразуемый к bool. Семантика: Эквивалентно: lexicographical_compare(a begin(), a end(), b begin(), b end()) • Больше a > b Требования к типу: T является <Сравнимым. Возвращаемый тип: Преобразуемый к bool. • Меньше или равно а <= b Требования к типу: Т является <Сравнимым. Возвращаемый тип: Преобразуемый к bool. • Больше или равно а >= b Требования к типу: Т является <Сравнимым. Возвращаемый тип: Преобразуемый к bool. Гарантии сложности Затраты на выполнение операторов сравнения линейно зависят от размера контейнера. Инварианты В дополнение к инвариантам, описанным для Контейнера, должны удовлетворяться следующие инварианты. • Упорядочение При двух различных итерациях по элементам Однонаправленного Контейнера произойдет обращение ко всем его элементам в одном и том же порядке при усло- вии, что в промежутке не производились операции, приводящие к изменению элементов. Модели Все контейнерные классы STL являются моделями Однонаправленного Контейнер3* В этот список, например, входят: • vector • list
9.1. Общие концепции контейнеров 145 • slist • deque • set • hash_set • map • hash_map • multiset • hash_multiset • multimap • hashjnultimap 9,1.3. Реверсивный Контейнер Реверсивный Контейнер (Reversible Container) — это Однонаправленный Контейнер, итераторы которого являются Двунаправленными Итераторами. У него есть дополни- тельные функции-члены и вложенные типы, обеспечивающие итерацию в прямом и обратном направлении. Является развитием Однонаправленного Контейнера (с. 143). Ассоциированные типы Вводятся два новых типа. Кроме того, типы итератора и константного итератора долж- ны удовлетворять более строгим требованиям, чем для Однонаправленного Контей- нера. Итераторы и реверсивные итераторы должны быть Двунаправленными Итера- торами. • Тип реверсивного итератора X::reverse_iterator Реверсивный адаптер итератора (с. 360), базовым типом которого является ите- ратор X: iterator. Инкремент объекта типа reverse_iterator приводит к переме- щению по контейнеру в обратном направлении. Реверсивный адаптер итератора отображает operator++ на operator--. Тип константного реверсивного итератора X const_reverse_iterator Реверсивный адаптер итератора, базовым типом которого является итератор X:: const_iterator. (Обратите внимание, что типы итератора и константного ите- ратора Контейнера могут быть одним и тем же типом. Контейнер не обязан пре- доставлять изменяемые итераторы. Из этого следует, что типы реверсивного итератора и константного итератора также могут совпадать.) Система обозначений V Тип, являющийся моделью Реверсивного Контейнера. Ь Объект типа X.
146 Глава 9. Контейнеры Допустимые выражения В дополнение к выражениям, определенным в Однонаправленном Контейнере, дол^ ны быть допустимы следующие выражения. • Начало диапазона a rbeginO Семантика: Постусловие: ЭквивалентноX:. reverse_iterator(a.end()). a rbegin() либо разыменуемый, либо указывает за послед- ний элемент. Второй случай реализуется тогда и только тог да, когда a. size() ==0. • Конец диапазона a rend() Семантика: Постусловие: ЭквивалентноX:: reverse_iterator(a.begin()). а. rend() указывает за последний элемент. Гарантии сложности rbegin() и rend() выполняются за константное амортизированное время. Инварианты В дополнение к инвариантам, определенным в требованиях к Однонаправленному Контейнеру, должны удовлетворяться следующие инварианты. • Допустимый диапазон [a. rbegin(), a. rend()) является допустимым диапазоном. • Эквивалентность диапазонов Расстояние между a.begin() и a.end() равно расстоянию между a rbeginO и а. rend (), и оба этих диапазона содержат одни и те же элементы. Модели Следующие типы являются примерами Реверсивных Контейнеров: • vector<double> • list<string> • set<int> 9.1.4. Контейнер Произвольного Доступа Контейнер Произвольного Доступа (Random Access Container) — это Реверсивный Кон- тейнер, типом итератора которого является Итератор Произвольного Доступа. Он обес- печивает доступ к произвольному элементу за константное амортизированное время либо с помощью итераторов, либо непосредственно при помощи функции-члена operatorf]. Является развитием Реверсивного Контейнера (с. 145).
9.2. Последовательности 147 дссоциир°ваннь|е ТИПЫ Только те, что определены в Реверсивном Контейнере. Однако требования к типу ите- ратора ужесточаются: он должен быть Итератором Произвольного Доступа. Система обозначений % Тип, являющийся моделью Контейнера Произвольного Доступа. а> ь Объект типа X. j Тип значения X. Допустимые выражения • Jipcryn к элементу а[п] Требования к типу: п должен быть преобразуем к типу size_type. Возвращаемый тип: reference, если контейнер изменяемый, и const_reference в противном случае. Предусловие: 0 < n < a. size(). Семантика: Возвращает n-й элемент от начала контейнера. Элемент, возвращаемый выражением а [ п ], — тот же, что получается после инкремента a. begin(), выполненного п раз, и разы- менования результирующего итератора. Гарантии сложности Доступ к элементу выполняется за константное амортизированное время. Модели Моделями Контейнера Произвольного Доступа являются контейнеры STL vector и deque, а также класс block из раздела 5.1. Наоборот, классы связанных списков типа list и slist не являются моделями Контейнера Произвольного Доступа. У этих клас- сов нет функций-членов operate г [ ], и их итераторы не являются Итераторами Произ- вольного Доступа. 9.2. Последовательности 9-2.1. Последовательность Последовательность (Sequence) — это Контейнер изменяемого размера, элементы кото- рого расположены строго в линейном порядке. Концепция Последовательности предс- тавляет много новых функций-членов, поддерживающих вставку и удаление как от- ^ольных элементов, так и диапазонов — в произвольных местах последовательности. Является развитием Однонаправленного Контейнера (с. 143), Конструируемого по Умолчанию (с. 101). А ссоциированные типы °лько те, что определены в Однонаправленном Контейнере.
148 Глава 9. Контейнеры Система обозначений X Тип, являющийся моделью Последовательности, а. b Объект типах. Т Тип значения X. t Объект типа Т. р, q Объекты типа X. .iterator. п Объект типа, преобразуемого к X •. size_type. Определения • Если а — Последовательность, то р — допустимый итератор в а при условии, что он является допустимым итератором и достижим из a. begin(). • Если а — Последовательность, то [р, q) — допустимый диапазон в а при условии, что [р, q) является допустимым диапазоном, ар — допустимым итератором в а Допустимые выражения Кроме выражений, определенных в Однонаправленном Контейнере, должны быть до- пустимы следующие выражения. • Заполняющий конструктор X(n, t) Возвращаемый Предусловие: тип: X п >0. Семантика: Постусловие: Создает последовательность, содержащую п копий t. Размер последовательности равен п. Каждый элемент по- следовательности является копией t. • Заполняющий конструктор Х(п) Требования к типу: Возвращаемый тип: Предусловие: Семантика: Постусловие: Т — Конструируемый по Умолчанию. X п>0. Эквивалентно Х( п, Т()), то есть создает последовательность из п элементов, проинициализированных значением по умолчанию. Размер последовательности равен п. Каждый элемент по- следовательности является копией Т(). Конструктор по умолчанию Х() Возвращаемый тип Семантика: X Эквивалентно X (0, t), то есть создает последовательность. Постусловие: не содержащую ни одного элемента. Размер последовательности равен нулю. Конструктор диапазона Х(1 ])
9.2. Последовательности 149 Требования к типу: 1 и j являются Итераторами Ввода, типы значений кото- рых должны быть преобразуемы к Т. Возвращаемый тип: X Предусловие: [i, j) — допустимый диапазон. Семантика: Создает последовательность, являющуюся копией диапа- зона [i, j). Постусловие: Размер результирующей последовательности равен distance( i, j). Каждый элемент результирующей последо- вательности равен соответствующему элементу входного диапазона. Вставка a insert(p, t) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: X. iterator Предусловие: р является допустимым итератором в а; a size()<a max_size(). Семантика: Вставляет копию t перед р. Есть два важных особых слу- чая: а. insert (а. begin(), t) вставляет элемент в начало (пе- ред первым элементом), a a. insert(a.end(), t) добавляет элемент в конец. Постусловие: a. size() инкрементируется на 1; *(а. insert(p, t)) являет- ся копией t. Относительный порядок элементов, находив- шихся до этого в последовательности, остается прежним. Внимание', данное условие не гарантирует, что итератор, до- пустимый в а, останется допустимым после вставки или удаления. Например, нет гарантии, что р — итератор, пере- даваемый insert, — останется допустимым. В некоторых случаях итераторы остаются допустимыми и продолжают указывать на те же элементы, что и раньше. В других слу- чаях вставка элемента в последовательность приводит к пе- рестановке существующих элементов для выделения мес- та под новый элемент, и итератор, который указывал на эле- мент последовательности, может после операции указывать на другой элемент или вообще не указывать ни на какой. Детали различаются в каждом конкретном классе, реали- зующем последовательность. Вставка с заполнением а insert(p, n, t) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: Предусловие: void р — допустимый итератор в а; п > 0; Семантика: a.size()+n < a max_size(). Вставляет п копий t перед р. Гарантируется, что это не мед- леннее, чем вызов 1 nse rt (р, t) всего п раз. В некоторых слу- чаях операция может быть значительно быстрее.
150 Глава 9. Контейнеры Постусловие: a size() инкрементируется на п. Относительный порядок оо тальных элементов последовательности остается прежним Вставка диапазона a insert(p. 1, ]) Требования к типу: а — изменяемый контейнер; i и j — Итераторы Ввода, тип значений которых преобразуем к Т. Возвращаемый тип: void Предусловие: р — допустимый итератор в а; [1, j) — допустимый диапа- зон; a. size() +distance(i, ]) < a max_size(). Семантика: Вставляет копию диапазона [i, j) перед р. Постусловие: a. size()инкрементируется Hadistance(i. j). Относитель- ный порядок остальных элементов последовательности остается прежним. Стирание a erase(p) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: Предусловие: iterator р можно разыменовать в а. Семантика: Уничтожает элемент, на который указывает р, и удаляет его из последовательности. Возвращаемое значение — это итератор, указывающий на элемент, что непосредственно следует за удаленным элементом, или равный а. end (), если такого элемента не существует. Постусловие: a. size() декрементируется на 1. Относительный порядок остальных элементов последовательности остается неиз- менным. Обратите внимание, что erase, как и в случае с insert, может привести к тому, что значения итераторов, указывающих в последовательность, станут недопустимы- ми. Естественно, a. erase( р) оказывает воздействие на сам р, но может также оказать влияние и на другие итераторы. Стирание элемента, как и вставка, может привести к пере- мещению других элементов последовательности. Таким об- разом, не следует предполагать, что а. erase( р) возвращает р+ 1. Стирание диапазона a erase(p, q) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: iterator Предусловие: [р, q) — допустимый диапазон в а. Семантика: Уничтожает все элементы в диапазоне [р, q) и удаляет их из а. Возвращаемое значение — итератор, указывающий на элемент, что непосредственно следует за удаленными, илП равный a end(), если такого элемента не существует. Ка^ и в случае одноэлементной версии erase, не следует делать
9.2. Последовательности 151 предположений, что возвращаемое значение обязательно равно q. Постусловие: a.size() декрементируется Hadistance(p, q). Относитель- ный порядок других элементов последовательности остал- ся прежним. • Начальный элемент a front() Возвращаемый тип: reference, если а — изменяемый контейнер, в противном случае const_ ref е rence. Предусловие: 1 a empty() Семантика: Эквивалентно *(а. begin()). Два из этих выражений включают в себя диапазоны общих Итераторов Ввода. По- этому вы можете вставить, например, все элементы li st о в vectoro следующим об- разом: V insert(V begin(), L begin(), L end()) Поскольку это возможно с любым типом Итератора Ввода, функция-член insert долж- на быть шаблоном-членом — функцией-членом, определенной в виде шаблона. Очевидное решение: при написании класса Последовательности определить две различные версии insert: обыкновенную функцию-член, которая вставляет п копий значения, и шаблон-член, который вставляет диапазон [1, j): template <class Т>’ class my_sequence { public. void insert(iterator p, size_type n, const value_type& t), template <class Inputlterator> void insert(iterator p, Inputiterator first, Inputiterator last), В основном это правильная идея, но есть одна техническая деталь, которая немного Усложняет задачу (в том случае, если вы собираетесь сами реализовать новую После- довательность). Предположим, вы работаете с Последовательностью, типом значений которой яв- Ляется int, например vector<int>. Теперь предположим, что вы написали: v-insert(V begin(), 100, 0). Невидно, было намерение вставить 100 копий нуля. Именно это и обещается запол- зшей версией insert. К сожалению, добиться того, чтобы реализация Последова- льности работала правильно, — довольно нетривиальная задача. При наличии двух ^приведенных версий insert компилятор C++ выберет не первую, а вторую. ^Причина в том, что оба значения, 100 и 0, имеют тип int. Первая версия принима- тип size_t, и поэтому совпадает не полностью, тогда как вторая, принимающая а аргумента одинакового типа, совпадает полностью. Фундаментальная причина стоит в том, что компилятор C++ ничего не знает о концепциях. У версии insert
152 Глава 9. Контейнеры для диапазонов есть параметр шаблона по имени Inputiterator (то есть “Итератор Ввода”. — Примеч. пер.), но с точки зрения языка в этом имени нет ничего особенного Компилятор не знает, что аргументы insert в действительности должны быть Итера, торами Ввода. Есть два способа правильной реализации insert. Во-первых, вы можете перетру, зить ее с дополнительными типами. Кроме void insert(iterator, size_t. const value_type&), вы также можете определить void insert(iterator, int, const value_type&), void inscrt(itorator. long, const value_type&), и т. д для всех целочисленных типов. Это прямолинейное, но утомительное решение. Во-вторых, вы могли бы воспользоваться вариацией на тему используемой в неко- торых алгоритмах диспетчеризации по типу. Проверьте, не является ли аргумент insert целочисленным, а если является, не пытайтесь воспользоваться версией для диапазонов. Сделать такую проверку позволит класс numeric_limits из стандартной библиоте- ки C++. Он возвращает информацию в виде значения типа bool, которое можно преоб- разовать в тип с помощью фиктивного класса dummy с нетиповым параметром шаблона: template <bool х> struct dummy {}, template <class T> class my_sequence { orivate void fill_insert(iterator, size_type, const value_type&), template <class Inputlter> void range_insert(iterator, Inputlter, Inputlter), template <class Number> void insert(iterator p, Number n, Number t, dummy<true>) { fill_insert(p, n, t), } template <class Inputlter> void insert(iterator p, Inputlter f, Inputlter 1, dummy<false>) { range_insert(p, f, 1), public' void insert(iterator p, size_type n, const value_type& t) { fill_insert(p, n, t), } template <class Inputlter> void insert(iterator p. Inputlter f, Inputlter 1) { insert(p, f, 1, dummy<std numeric_limits<lnputlter> is_integer>()).
9.2. Последовательности 153 Гарантии сложности • Затраты на заполняющий конструктор, заполняющий конструктор по умолча- нию и конструктор диапазона имеют линейную зависимость. • Затраты на время исполнения одноэлементных форм insert и erase зависят от конкретной последовательности. • Многоэлементные формы insert и erase имеют линейную зависимость. • функция-член f ront выполняется за константное амортизированное время. Модели Такие контейнеры STL, как vector, deque, list и slist, являются моделями Последо- вательности. Они отличаются друг от друга в основном типами своих итераторов (vector и deque предоставляют Итераторы Произвольного Доступа, list — Двуна- правленные Итераторы, si 1st — Однонаправленные Итераторы) и величиной наклад- ных расходов, связанных с исполнением insert и erase. Обычно наилучшим выбором является vector. Это простейший стандартный кон- тейнерный класс и, следовательно, у него наименьшие накладные расходы, связан- ные с итераторами и доступом к элементам. Так как время вставки элемента в vecto г линейно зависит от количества элементов между точкой вставки и концом vector, то в ситуациях с частыми вставками иногда предпочтительней использовать другие последовательности. Если вы выполняете большое количество вставок как в начало, так и в конец последовательности, хорошим выбором может стать deque, а если вы выполняете много вставок в середину — то list или slist. Почти во всех других си- туациях vecto г наиболее эффективен. 9.2.2. Последовательность с Начальной Вставкой Последовательность с Начальной Вставкой (Front Insertion Sequence) — это Последо- вательность, для которой операции вставки элемента в начало или доступа к первому элементу выполняются за константное амортизированное время. У Последователь- ности с Начальной Вставкой есть специальные функции-члены, предоставляющие со- кращенную форму записи операций. Является развитием Последовательности (с. 147). Ассоциированные типы Только те, что определены для Последовательности. Система обозначений X Тип, являющийся моделью Последовательности с Начальной Вставкой а Объект типа X. Т Тип значения X. Объект типа Т. Допустимые выражения Дополнение к выражениям, определенным для Последовательности, должны быть допустимы следующие выражения.
154 Глава 9. Контейнеры • Начальный элемент^ a front() Возвращаемый тип: reference, если а — изменяемый контейнер, в противном случае const,reference. Предусловие: ’a emptyO Семантика: Эквивалентно * (а. beg i n ()). • Поместить элемент в начало a push_front(t) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: void Семантика: Эквивалентно a. insert(a. begin(), t). • Извлечь начальный элемент a pop_f ront() Требования к типу: а — изменяемый контейнер. Возвращаемый тип: void Предусловие: 1 а. empty () Семантика: Эквивалентно а. erase(a. begin()). Вам может показаться странным, что pop_f ront() возвращает тип void, ведь в книгах по структурам данных сообщается, что рор() обычно удаляет значение и возвращает его. Есть две причины, по которым мы избегаем такого определения pop_f ront. Во- первых, это не обязательно. Если вы хотите получить значение, которое собираетесь удалить, то можно сначала просто вызвать front. Во-вторых, это неэффективно. В от- личие от front, операция pop_front не может возвратить ссылку, потому что после удаления элемента не остается ничего, на что она могла бы ссылаться. Пришлось бы возвращать по значению, а это создает не всегда нужную и потенциально дорогую копию элемента. Гарантии сложности Все три операции, введенные Последовательностью с Начальной Вставкой: front, push_front и pop_f ront, выполняются за константное амортизированное время. Это, собственно, и является единственной причиной выделения Последовательно- сти с Начальной Вставкой в отдельную концепцию. За исключением дополнительных гарантий сложности, эти три операции не предоставляют больше никакой функцио- нальности сверх begin, insert и erase. Не каждая последовательность должна опреде- лять такие операции, но гарантируется, что если они существуют, то эффективны. Инварианты Кроме определенных в требованиях к Последовательности должен быть удовлетво- рен следующий инвариант. } В деистви тельное ти меюд front определен в Последовательности, потому что всегда есть возм()Ж носи» реализовать его за константное амортизированное время. Для удобства определение повторяете я здесь, так же как и определения push_front и pop_front
9.2. Последовательности 155 • Симметричность push и pop push_front, за которой следует pop_f ront, является нулевой операцией. Додели Контейнеры STL list, slist и deque являются моделями Последовательности с На- чальной Вставкой. Напротив, контейнер STL vector не является таковой. У вектора gcTb операция front, но нет операций push_front или pop_front. Вставка и стирание в начале vector не являются операциями, выполняемыми за константное время. 9,2.3. Последовательность с Концевой Вставкой Последовательность с Концевой Вставкой (Back Insertion Sequence) — это Последова- тельность, для которой добавление элемента в конец или доступ к последнему эле- менту выполняется за константное амортизированное время. У Последовательности с Концевой Вставкой есть специальные функции-члены, обеспечивающие сокращен- ную форму записи этих операций. Является развитием Последовательности (с. 147). Ассоциированные типы Только те, что определены для Последовательности. Система обозначений X Тип, являющийся моделью Последовательности с Концевой Вставкой. а Объект типа X. Т Тип значения X. t Объект типа Т. Допустимые выражения В Дополнение к определенным в Последовательности должны быть допустимы сле- дующие выражения. Последнее значение a back() Возвращаемый тип: reference, если а — изменяемый контейнер, в противном случае const_ ref е rence. Предусловие: ! а. empty() Семантика: Эквивалентно * (- - а еnd ()). Поместить элемент в конец a push_back(t) Требования к типу: а — изменяемый контейнер. Возвращаемый тип: void Семантика: Эквивалентно a. insert (a. end(), t).
156 Глава 9. Контейнеры • Извлечь последний элемент а рор_Ьаск() Требования к типу: а — изменяемый контейнер. Возвращаемый тип: void Предусловие: 'a emptyO Семантика: Эквивалентно a erase(--a end()). Аналогично pop_f ront в Последовательности с Начальной Вставкой (с. 153) pop_back определен так, что возвращает void, а не удаленный элемент. Если бы pop_back воз- вращал значение, это приводило бы к созданию не всегда необходимой копии. Функ- ция не может возвращать ссылку, потому что элемент, на который она ссылается уничтожен. Гарантии сложности Все три операции, введенные Последовательностью с Концевой Вставкой: back, push_back и pop_back, выполняются за константное амортизированное время. Это, собственно, и является единственной причиной выделения Последовательно- сти с Концевой Вставкой в отдельную концепцию. За исключением гарантий сложно- сти, эти три операции не предоставляют никакой дополнительной функционально- сти сверх end, insert и erase. Не каждая последовательность должна определять эти операции, но гарантируется, что если они существуют, то эффективны. Инварианты Кроме определенных в требованиях к Последовательности должен быть удовлетво- рен следующий инвариант. • Симметричность push и pop push_back, за которой следует pop_back, является нулевой операцией. Модели Такие контейнеры STL, как vector, list и deque, являются моделями Последова- тельности с Концевой Вставкой. Напротив, контейнер STL slist не является таковым. У него нет функций-членов back, push_back и pop_back. Это однонаправленный спи- сок, который предоставляет Однонаправленные Итераторы, а не Двунаправленные Ите- раторы, и доступ к последнему элементу slist не выполняется за константное время 9.3. Ассоциативные контейнеры 9.3.1. Ассоциативный Контейнер Ассоциативный Контейнер (Associative Container) — это Контейнер изменяемого ра3 мера, поддерживающий эффективный поиск элементов (значений) по ключам. поддерживает вставку и удаление элементов, но, в отличие от Последовательности» не предоставляет механизма вставки элемента в конкретную позицию. Причина отсутствия такого механизма состоит в том, что конкретное расположи ние элементов Ассоциативного Контейнера — это инвариант класса. Например,
9.3. Ассоциативные контейнеры 157 рйты Сортированного Ассоциативного Контейнера всегда хранятся в порядке возра- стация, а элементы Хешированного Ассоциативного Контейнера — в соответствии с функцией хеширования. Было бы бессмысленно разрешать произвольный выбор положения элемента. Как все контейнеры, Ассоциативный Контейнер содержит элементы типа value_type. Кроме того, у каждого элемента Ассоциативного Контейнера имеется ключ (key) типа Key t УРе- В некоторых Ассоциативных Контейнерах, которые называются Простыми дссоциативными Контейнерами, типы значения и ключа совпадают — и элементы яв- ляются своими собственными ключами. В других видах контейнеров ключ — некото- рая часть значения. Так как элементы хранятся в соответствии со своими ключами, важно, что ключи неизменны. Для Простого Ассоциативного Контейнера значение по- вешенного в контейнер элемента неизменно, тогда как в других типах Ассоциативных Контейнеров, например в Парном Ассоциативном Контейнере, сами элементы могут быть изменены, но те их части, которые являются ключами, не могут быть модифи- цированы. То есть тип значения Ассоциативного Контейнера не обязательно является Присваиваемым. Этот факт имеет важное следствие. Так как элементы Ассоциативного Контейнера не могут быть модифицированы произвольным образом, то, следовательно, у Ассо- циативного Контейнера не может быть изменяемых итераторов. Проблема заключа- ется в том, что по определению изменяемый итератор должен обеспечивать присваи- вание (он поддерживает выражение *1 = t), но это нарушило бы правило неизменя- емости ключей. В Простом Ассоциативном Контейнере, где элементы являются ключами, вложен- ные типы iterator и const_iterator имеют одинаковую функциональность (и даже могут быть одного и того же типа). Можно удалить элемент из Простого Ассоциатив- ного Контейнера, но отсутствует возможность модифицировать его прямо в контей- нере. Другие типы Ассоциативных Контейнеров позволяют использовать изменяемые элементы и предоставляют итераторы, посредством которых элементы можно моди- фицировать. До тех пор, пока вы не затрагиваете ключа, вы можете свободно менять значение элемента. Например, Парный Ассоциативный Контейнер имеет два различ- ных вложенных типа iterator и const_iterator. Даже в этом случае iterator не явля- йся изменяемым, поскольку нельзя написать *i = t, но в то же время он и не совсем константный. Например, если 1 имеет тип map<int, double>. iterator, то возможна ^пись i->second = 3. Для Уникального Ассоциативного Контейнера гарантируется, что никакие два эле- мента не имеют одинаковых ключей. Во Множественном Ассоциативном Контейнере ^Решается хранить несколько элементов с одним и тем же ключом. Подразумеваемое под “одним и тем же” зависит от типа Ассоциативного Контейне- ?а* При этом равенство ключей не обязательно, потому что концепция Ассоциативно- онтейнера не требует, чтобы ключи были =Сравнимы. Например, в Сортированном с°циативном Контейнере, где элементы располагаются в порядке возрастания в со- отствии с объектом Строгое Слабое Упорядочение, два ключа считаются одинако- если ни один из них не меньше другого. & СТь много различных способов организации элементов в структурах данных та- М образом, чтобы легко осуществлялся поиск элементов по ключу. Наиболее важ- еструктуры данных — поисковые деревья и хеш-таблицы.
158 Глава 9. Контейнеры Является развитием Однонаправленного Контейнера (с. 143). Ассоциированные типы Кроме типов, определенных в требованиях к Однонаправленному Контейнеру, введен один новый тип. • Тип ключа X key_type Тип ключа, ассоциированного с X: :value_type. Заметим, что тип ключа и тип значения могут совпадать (а в Простом Ассоциативном Контейнере они всегда совпадают). Система обозначений X Тип, являющийся моделью Ассоциативного Контейнера. а Объект типа X. Т Тип значениях. t Объект типаХ: :value_type. к Объект типа X.: key _t у ре. р, q ОбъекттипаХ: :iterator. Определения • Если а — Ассоциативный Контейнер, то р является допустимым итератором в а, при условии, что он допустимый итератор, достижимый из a. begin(). • Если а — Ассоциативный Контейнер, то [р, q) является допустимым диапазоном в а, при условии, что [р, q) — допустимый диапазон, ар — допустимый итератор в а. Допустимые выражения • Конструктор по умолчанию Х() х а. Семантика: Создает пустой контейнер. Постусловие: Размер контейнера равен нулю. • Поиск a find(k) Возвращаемый тип: iterator, если а — изменяемый контейнер, и const_iterator в противном случае. Семантика: Возвращает итератор, указывающий на элемент, ключ ко- торого равен к. Заметьте, что таких элементов может быть более одного. Если это так, то не определено, на какой эЛе' мент указывает возвращаемый итератор. ВозвращаеМ°е значение равно a. end(), если такого элемента не суше~ ствует.
9.3. Ассоциативные контейнеры 159 Постусловие: Возвращенное значение равно либо а. end (), либо являет- ся итератором, указывающим на элемент, ключ которого равен к. . Подсчет a.count(k) Возвращаемый тип: Семантика: size_type Возвращает количество элементов, ключи которых рав- ны к. Постусловие: Возвращенное значение не отрицательно и меньше или рав- но a.sizeO (для Уникального Ассоциативного Контейнера возвращаемое значение всегда либо 0, либо 1). Диапазон равенства a.equal_range(k) Возвращаемый тип: pai r<ite rato г, 1 te ratoг>, если а — изменяемый контейнер, и pair<const_iterator, const_iterator>B противном случае. Семантика: Возвращает все элементы, ключи которых равны к. А имен- но, возвращает такую пару Р, что диапазон [Р. first, Р. second) содержит все элементы, ключи которых равны к. Обратите внимание на один нюанс, связанный с этой функ- цией: если у двух элементов одинаковый ключ, то между ними не должно быть элементов с другими ключами. Хра- нение элементов с одинаковыми ключами последователь- но друг за другом — это инвариант Ассоциативного Контей- нера. Если в а не содержится таких элементов, то возвра- щается пустой диапазон. Постусловие: distance(P. first, Р second) равняется a. count (к). Если р — итератор, разыменуемый в диапазоне [Р. first, Р. second), то р указывает на элемент, ключ которого равен к. Если q — итератор, разыменуемый в а, значит, либо q лежит в диапа- зоне [Р. f i rst, Р. second), либо q указывает на элемент, ключ которого не равен к. Стирание ключа a erase(k) Возвращаемый тип: Семантика: void Уничтожает все элементы, ключи которых равны к, и уда- Постусловие: ляет их из а. Размер а декрементируется на а. count (к). В а не содержится ни одного элемента с ключом к. Стирание элемента а erase(p) Возвращаемый тип: Предусловие: void р — разыменуемый итератор в а.
160 Глава 9. Контейнеры Семантика: Постусловие: • Стирание диапазона a erase(p, q) Уничтожает элемент, на который указывает р, и удадЯет его из а. Размер а декрементируется на единицу. Возвращаемый тип: void Предусловие: [р, q) — допустимый диапазон в а. Семантика: Уничтожает все элементы в диапазоне [р, q) и удаляет их Постусловие: По а. Размер а декрементируется на distance(p, q). Возникает вопрос, почему в этом списке есть методы для поиска элементов и удале* ния элементов, но нет ни одного метода для их вставки. Ответ заключается в том, что вставка элементов в Уникальный Ассоциативный Контейнер означает нечто совершен- но отличное от вставки во Множественный Ассоциативный Контейнер. Методы встав- ки элемента определены в этих двух концепциях. Гарантии сложности • Средняя сложность для поиска и диапазона равенства в худшем случае лога- рифмическая. • Средняя сложность для count не хуже O(log(size()) + count (к)). • Средняя сложность для удаления по ключу не хуже O(log(size()) + count(k)). • Средняя сложность для удаления элемента — константное амортизированное время. • Средняя сложность для удаления диапазона не хуже О(log(si ze ()) + N), где N - количество элементов в диапазоне. Отметим, что эти требования относятся к среднестатистической сложности, а не к худ- шим случаям. Некоторые ассоциативные контейнеры ужесточают эти требования до оценок для худших случаев. Инварианты В дополнение к требованиям, определенным к Последовательности, должны быть удовлетворены и следующие инварианты. • Непрерывное хранение Все элементы с одинаковыми ключами должны соседствовать друг с друг01М' То есть если р и q — итераторы, указывающие на элементы с одинаковыми клЮ' чами, и если р предшествует q, то все элементы из диапазона [р, q) имеют одина- ковые ключи. • Неизменность ключей Каждый элемент Ассоциативного Контейнера имеет неизменный ключ. Объекты могут добавляться и удаляться, но элемент Ассоциативного Контейнера никогД3 не модифицируется таким образом, чтобы при этом изменился его ключ.
9.3. Ассоциативные контейнеры 161 Следуюшие . set додели классы являются моделями Ассоциативного Контейнера: • multiset . hash_set • hash_multiset • map • multimap . hashjnap • hashjnultimap 9.3.2. Уникальный Ассоциативный Контейнер Уникальный Ассоциативный Контейнер (Unique Associative Container) — это Ассоциа- тивный Контейнер, ключи элементов которого уникальны. Ни у каких двух элементов Уникального Ассоциативного Контейнера нет одинаковых ключей. Это означает, что “вставка” элемента в Уникальный Ассоциативный Контейнер все- гда условна: если контейнер уже содержит элемент с ключом, равным ключу элемен- та!, то t не помещается в контейнер. Является развитием Ассоциативного Контейнера (с. 156). Ассоциированные типы Только те, что определены для Ассоциативного Контейнера. Система обозначений Тип, являющийся моделью Уникального Ассоциативного Контейнера. Объект типа X. Тип значения X. Объект типаХ: :value_type. Объект типа X:: key_type. ОбъекттипаХ: iterator. Допустимые выражения Дополнение к определенным в Ассоциативном Контейнере должны быть допусти- 14 следующие выражения. Конструктор диапазона X(l. J) х а(1, J), Требования к типу: 1 и j — Итераторы Ввода, типы значений которых преобра- зуемы к Т. Предусловие: [i, j ) — допустимый диапазон. а Т t к P»q
162 Глава 9. Контейнеры Семантика: Создает Ассоциативный Контейнер, содержащий все эЛь менты из [i, j) с уникальными ключами. Постусловие: size() меньше или равно distance(i, j). (“Меньше или ра^ но”, а не “равно”, потому что в [i, j) может быть несколько элементов с одинаковыми ключами. При этом будет добав< лен только первый такой элемент.) • Вставка элемента a insert(t) Возвращаемый тип: pair<X. iterator, bool>. Семантика: Вставляет t в а в том и только том случае, если в а не содер- жится уже элемент с таким же ключом, как у t. Возвраща- ется пара Р, второй элемент которой имеет тип bool. Если в а уже содержится элемент, ключ которого совпадает с ключом t, то Р. first — итератор, указывающий на этот элемент, а Р. second равно false. Если в а еще не содержится такой элемент, то Р. f i rst — итератор, указывающий на но- вый вставленный элемент, а Р. second равно t rue. Таким об- разом, Р. second равно истинности того, что t действитель- но вставлен в а. Постусловие: Р. first — это разыменуемый итератор, указывающий на элемент, ключ которого такой же, как у t. Размер а инкре- ментируется на 1 тогда и только тогда, когда Р second рав- няется t rue. • Вставка диапазона a insert(i. j) Требования к типу: 1 и j — Итераторы Ввода, типы значений которых преобра- зуемы к Т. :ыи тип: Предусловие: [i, j) является допустимым диапазоном. Семантика: Эквивалентно a insert(t) для каждого объекта t из диа- пазона [i, j). То есть каждый объект из этого диапазона вставляется в а при условии, что в а еще не содержится эле- мент с таким же ключом. Постусловие: Размер контейнера а инкрементируется не более чем на distance(i,j). Гарантии сложности • Средняя сложность для вставки элемента в худшем случае логарифмическая. • Средняя сложность для вставки диапазона не хуже O(N log(size() + N))> г;1С N — число элементов в диапазоне. Инварианты Кроме определенных в требованиях к Ассоциативному Контейнеру должен быть уД°ь летворен следующий инвариант.
9.3. Ассоциативные контейнеры 163 • Уникальность Никакие два элемента не имеют одинаковых ключей, то есть для каждого значе- ния к типа key_type значение a. count (к) равно нулю или единице. Щодвли СлеДУющие Классы контейнеров STL являются моделями Уникального Ассоциатив- ного Контейнера: . set • map • hash_set • hash.map 9.3.3. Множественный Ассоциативный Контейнер Множественный Ассоциативный Контейнер (Multiple Associative Container) — это Ас- социативный Контейнер, который может содержать более одного элемента с одним ключом. То есть это Ассоциативный Контейнер, не имеющий ограничений Уникально- го Ассоциативного Контейнера. Вставка во Множественный Ассоциативный Контейнер является безусловной, как и вставка в Последовательность: insert не проверяет, содержатся ли в контейнере эле- менты, ключи которых совпадают с ключом вставляемого элемента. Является развитием Ассоциативного Контейнера (с. 156). Ассоциированные типы Только те, что определены в требованиях к Ассоциативному Контейнеру. Система обозначений X Тип, являющийся моделью Множественного Ассоциативного Кон- тейнера. а Объект типа X. Т Тип значения X. * Объект типа X. : val ue_type. Объект типа X:: key_type. Q Объект типах, iterator. Допустимые выражения Кроме выражений, определенных в требованиях к Ассоциативному Контейнеру, дол- ы быть допустимы и следующие выражения. Конструктор диапазона Х(1. J) Х а<1. ]). Требования к типу: 1 и j — это Итераторы Ввода, типы значений которых долж- ны быть преобразуемы к Т. Предусловие: [i, j) — допустимый диапазон.
164 Глава 9. Контейнеры Семантика: Создаст Ассоциативный Контейнер, содержащий вес менты из диапазона [ i, j). Постусловие: Размер контейнера равен distance(i, j). Каждый элемецт из диапазона [i, j) находится в контейнере. • Вставка элемента a insert(t) Возвращаемый тип: X- iterator Семантика: Без проверок вставляет t в а. Возвращаемое значение — ите- ратор, указывающий на вставленный элемент. Постусловие: Размер а инкрементируется на 1. Значение a count(t) ин- крементируется на 1. • Вставка диапазона a insert(i j) Требования к типу: i и j — это Итераторы Ввода, типы значений которых долж- ны быть преобразуемы к Т. Возвращаемый тип: void Предусловие: [i, j) — допустимый диапазон. Семантика: Эквивалентно a. insert(t) для каждого объекта t из диа- пазона [i, j). То есть каждый объект из этого диапазона вставляется в а. Постусловие: Размер а инкрементируется Hadi st an ce(i, j). Гарантии сложности • Средняя сложность вставки одного элемента в худшем случае логарифмическая. • Средняя сложность вставки диапазона не хуже O(7V log(size() + TV))» где N~~ количество элементов в диапазоне. Модели Следующие классы контейнеров STL являются моделями Множественного Ассоциа- тивного Контейнера: • multiset • multimap • hash_multiset • hashjnultimap 9.3.4. Простой Ассоциативный Контейнер Простой Ассоциативный Контейнер (Simple Associative Container) — это Ассоциатив ный Контейнер, элементы которого являются своими собственными ключами. Значе пие, хранимое в Простом Ассоциативном Контейнере, — это только ключ, а не кл10 плюс некоторые данные. Является развитием Ассоциативного Контейнера (с. 156).
9.3. Ассоциативные контейнеры 165 дсСОцИИр°ваннь|е типы Только те, что определены в требованиях к Ассоциативному Контейнеру. Однако Про- стой Ассоциативный Контейнер вводит два новых ограничения на типы. • Тип ключа X::key_type Тип ключа, ассоциированного с X:: value_type. Типы key_type и value_type должны совпадать. • Итератор X..iterator Тип итератора, используемого для прохода по элементам Простого Ассоциатив- ного Контейнера. Типы X.: iterator и X:: const-iterator должны иметь одинако- вое поведение и даже могут быть одним и тем же типом. Простой Ассоциативный Итератор не предоставляет изменяемых итераторов, поскольку ключи Ассоциа- тивного Контейнера всегда неизменяемы. Ключи нельзя модифицировать, а зна- чения Простого Ассоциативного Контейнера сами по себе являются ключами, следовательно, значения в Простом Ассоциативном Контейнере не могут моди- фицироваться вообще. Система обозначений X a k P,q Тип, являющийся моделью Простого Ассоциативного Контейнера. Объект типа X. Объект типа X:: key_type. ОбъекттипаХ: iterator. Допустимые выражения Только те, что определены для Ассоциативного Контейнера. Инварианты • Неизменяемость элементов Элементы Простого Ассоциативного Контейнера неизменяемы. Объекты можно вставлять и удалять, но их нельзя модифицировать на месте. В любом Ассоциа- тивном Контейнере неизменяемы ключи. В Простом Ассоциативном Контейнере ключи сами же и есть элементы, поэтому элементы неизменяемы. ^°Дели Сд еДующие классы контейнеров STL являются моделями Простого Ассоциативного к°нтейнера: • set * Multiset hash-set hashjilultiset
166 Глава 9. Контейнеры 9.3.5. Парный Ассоциативный Контейнер Парный Ассоциативный Контейнер (Pair Associative Container) — это Ассоциативны^ Контейнер, который ассоциирует ключ с некоторым другим объектом. Тип значенця Парного Ассоциативного Контейнера имеет вид pai r<const key_type, mapped_type>> key_type — тип ключа контейнера. Является развитием Ассоциативного Контейнера (с. 156). Ассоциированные типы Кроме тех, что определены в требованиях к Ассоциативному Контейнеру, вводится еще один тип. Более того, Парный Ассоциативный Контейнер включает в себя новое ограничение на типы. • Тип ключа X -key-type Тип ключа элемента. • Отображаемый тип X mapped_type Тип данных элемента. Парный Ассоциативный Контейнер можно рассматривать в ви- де ассоциативного массива, хранящего значения типа mapped_type, или в виде ото- бражения из области значений типа key_type в область значений типа mapped_type. Отображаемый тип элемента иногда называют типом данных (data type). • Тип значения X value_type Тип объекта, хранящегося в контейнере. Тип значения должен иметь вид pair<const key_type, mapped_type>. Причина, по которой тип значения имеет вид paircconst key_type, mapped_type>, а не pair<key_type. mapped-type>, — это инвариант неизменяемости ключей Ассоциатив- ного Контейнера. Часть объекта типа mapped_type в Парном Ассоциативном Контейне- ре можно изменять, а часть типа key_type — нельзя. Следовательно, Парный Ассоциа- тивный Контейнер не может предоставить изменяемые итераторы (как это определе- но в требованиях к Тривиальному Итератору), потому что тип значения изменяемого итератора должен быть Присваиваемым, a pai r<const key_type, mapped_type> таковым не является. Однако Парный Ассоциативный Контейнер предоставляет итераторы, ЯВ' ляющиеся не целиком константными: они позволяют вам писать, например, такие выражения: i->second = d. Система обозначений X Тип, являющийся моделью Парного Ассоциативного Контейнера- а Объект типа X. t ОбъекттипаХ: value_type. к Объект типа X: : key_type.
Только те, что 9.3. Ассоциативные контейнеры 167 ОбъекттипаХ- mapped_type. ОбъекттипаХ: .iterator. выражения определены для Ассоциативного Контейнера. щоделч СлеДУющие контейнерные классы STL являются моделями Парного Ассоциативного Контейнера: • шар • multimap • hashjnap • hashjnultimap 9.3.6. Сортированный Ассоциативный Контейнер Сортированный Ассоциативный Контейнер (Sorted Associative Container) — это Ассо- циативный Контейнер, который сортирует элементы в порядке возрастания ключа, используя объект Строгое Слабое Упорядочение. Так как Строгое Слабое Упорядоче- ние создает отношение равенства, мы можем сказать, что два ключа имеют одинако- вое значение, если ни один из них не меньше другого. Сортированный Ассоциативный Контейнер гарантирует, что сложность базовых опе- раций Ассоциативного Контейнера никогда не хуже логарифмической. Является развитием Ассоциативного Контейнера (с. 156), Реверсивного Контейнера (с. 145). Ассоциированные типы В Дополнение к тем, которые определены в требованиях к Ассоциативному Контейне- ру и Реверсивному Контейнеру, вводятся два новых типа. • Сравнение ключей X .key_compare Тип функционального объекта: Строгое Слабое Упорядочение, которое исполь- зуется для сравнения ключей. Тип аргумента — X. : key_type. Сравнение значений X value_compare Тип Строгого Слабого Упорядочения, которое используется для сравнения значе- ний. Тип аргумента — X •. value_type. Сравнение величин осуществляется путем сравнения их ключей. Два объекта типа value_type сравниваются путем переда- чи их ключей функциональному объекту типа key_compa re. Ретема обозначений V Тип, являющийся моделью Сортированного Ассоциативного Кон- тейнера.
168 Глава 9. Контейнеры а Объект типа X. Т Тип значения X, X- value_type. t Объект типах value_type. к Объект типах. key_type. р, q Объект типах iterator. с Объект типаХ: key_compare. Допустимые выражения В дополнение к определенным в Ассоциативном Контейнере и Реверсивном Контей- нере должны быть допустимы следующие выражения. • Конструктор по умолчанию Х() X а. Семантика: Создает пустой контейнер с key_compa re () в качестве функ- ции сравнения. • Конструктор по умолчанию с функцией сравнения Х(с) X а(с), Семантика: Создает пустой контейнер, используя с как функцию срав- нения. • Конструктор диапазона X(l. J) X a(i, J). Требования к типу: i и j — это Итераторы Ввода, типы которых преобразуемы к типу Т. Предусловие: [i, j) — допустимый диапазон. Семантика: Эквивалентно: Ха: a insert(i, j), (Результат insert — то есть будут вставлены все элементы из [i, j) или только уникальные элементы — зависит от того, является X моделью Множественного Ассоциативного Кон- тейнера или Уникального Ассоциативного Контейнера.) • Конструктор диапазона с функцией сравнения X(i, j, с) X a(i, J, с), Требования к типу: i и j являются Итераторами Ввода, типы которых преобрЯ' зуемы к типу Т. Предусловие: [i, j) — допустимый диапазон. Семантика: Эквивалентно: X а(с), a insert(i, j),
9.3. Ассоциативные контейнеры 169 • Сравнение ключей a key_comp() Возвращаемый тип: Семантика: • Сравнение значений a value_comp() Возвращаемый тип: Семантика: Постусловие: • Нижняя граница a. lower_bound(k) Возвращаемый тип: Семантика: Постусловие: • Верхняя граница a upper_bound(k) Возвращаемый тип: Семантика: Постусловие: * Диапазон равенства а equal_range(k) Возвращаемый тип: key_compare Возвращает функциональный объект, сравнивающий клю- чи. Этот объект задается при конструировании а и не мо- жет быть изменен впоследствии. value.compare Возвращает функциональный объект, сравнивающий зна- чения, который получается из объекта сравнения ключей. Если t1 и t2 — это объекты типа value_type, а к1 и к2 — со- ответствующие им ключи, то a value_comp( )(t1. t2) эквивалентно a key_comp()(k1, k2) iterator, если a — изменяемый контейнер, и const-iterator в противном случае. Возвращает итератор, указывающий на начальный элемент, ключ которого не меньше к, и a. end(), если такого элемен- та не существует. Если в контейнере а имеются элементы с ключом к. lower_bound возвращает значение, указывающее на первый такой элемент. iterator, если а — изменяемый контейнер, и const-iterator в противном случае. Возвращает итератор, указывающий на начальный элемент, ключ которого больше к, и a end(), если такого элемента не существует. Если в контейнере а имеются элементы с ключом к, то upper bound возвращает итератор, указывающий непосред- ственно за последний такой элемент. pairciterator, iterateг>, если a — изменяемый контей- нер, и pair<const_iterator, const_iterator> в противном случае.
170 Глава 9. Контейнеры возвращает пару Р, где н. first — это a iower_oouna(Kl а Р. second — a. upper_bound(k). Определение удовлетвори ет семантике, описанной в требованиях к Ассоциативно^ Контейнеру, но ужесточает эти требования. Если а не Cfj держит элементов с ключом к, то a equal_range( к) воззри щает пустой диапазон, указывающий на место, где стояди бы эти элементы, если бы они существовали. Ассоциату ный Контейнер в этой ситуации только лишь требует, чтобы возвращаемое значение было произвольным пустым диа- пазоном. Все элементы с ключами к находятся в диапазоне [ Р. f i rsx Р. second). Семантика: Постусловие: • Вставка с подсказкой a insert(p, t) Возвращаемый тип: iterator Предусловие: р — несингулярный итератор в а. Семантика: Та же, что у a. insert (t). Если X — модель Множественного Ассоциативного Контейнера, то вставка безусловна, а воз- вращаемое значение — это итератор, указывающий на вставленный элемент. Если X — модель Уникального Ас- социативного Контейнера, то t вставляется в а, только если в нем нет элементов с таким ключом. Возвращаемое значе- ние — это итератор, указывающий на вставленный элемент (если он был вставлен) или на элемент, ключ которого сов- падает с ключом t. Аргумент р — всего лишь подсказка. Он указывает на предполагаемое место вставки t. Подсказка оказывает влияние только на производительность опера- ции, а не на ее корректность. Гарантии сложности Сортированные Ассоциативные Контейнеры имеют значительно более строгие гаран- тии сложности, чем Ассоциативные Контейнеры. Оценки, приводимые для Ассо- циативных Контейнеров, верны для среднестатистических значений. Допускается, что в наихудших случаях оценки будут хуже. Сортированные Ассоциативные Контейне- ры, однако, обеспечивают верхнюю границу сложности для наихудших случаев. • Конструктор диапазона и конструктор диапазона с функцией сравнения обыч- но выполняются за O(N logJV), где N — размер диапазона, используемого для создания контейнера. Однако они линейны по N, если диапазон уже отсортир0' ван по возрастанию с помощью value_comp(). • key_comp() и value_comp() выполняются за константное время. • Вставка с подсказкой обычно выполняется за логарифмическое время, но есл11 подсказка корректна, — то за константное амортизированное время (то есть ког да t вставляется непосредственно перед р). • Вставка диапазона обычно выполняется за O(XlogTV), где N — размер диапазон3' Однако она линейно зависит от N, если вставляемый диапазон уже отсортир0 ван в порядке возрастания с помощью value_comp().
9.3. Ассоциативные контейнеры 171 9 Стирание элемента выполняется за константное амортизированное время. • Стирание ключа выполняется за O(log(size()) + count(k)). • Стирание диапазона выполняется за O(log(size()) + А), где А — размер удаля- емого диапазона. • Поиск выполняется за логарифмическое время. • Count выполняется за O(log(size()) + count (к)). • Определения нижней границы, верхней границы и диапазона равенства выпол- няются за логарифмическое время. Инварианты В дополнение к инвариантам, определенным в требованиях к Ассоциативному Кон- тейнеру, должны удовлетворяться следующие инварианты. • Определение сравнения значений Если t1 и t2 — объекты типа X: :value_type, а к! и k2 — соответствующие им ключи, то a. value_comp() возвращает функциональный объект, такой что a. value_comp( )(t1, t2) эквивалентно a. key_comp(k1, k2). • Упорядочение по возрастанию Элементы в Сортированном Ассоциативном Контейнере всегда расположены в по- рядке возрастания ключа. Если а — Ассоциативный Контейнер, то is_sorted(a begin(), a end(), a value_comp()) всегда равно t rue. Это и является причиной названия “сортированный ассоциа- тивный контейнер”. Модели Следующие классы контейнеров STL являются моделями Сортированного Ассоциа- тивного Контейнера: • set • map • multiset • multimap Хешированный Ассоциативный Контейнер Хешированный Ассоциативный Контейнер (Hashed Associative Container) — это Ассо- циативный Контейнер, реализованный в виде хеш-таблицы. Не гарантируется, что Элементы Хешированного Ассоциативного Контейнера расположены в каком-либо ос- иленном порядке. В частности, они не отсортированы. В наихудших случаях боль- Нство операций с Хешированными Ассоциативными Контейнерами выполняются Линейное относительно размера контейнера время, а в среднем — за константное. 0 означает, что в приложениях, где значения просто сохраняются и извлекаются, Упорядочивание пе имеет значения, Хешированные Ассоциативные Контейнеры дей- УЮт обычно намного быстрее Сортированных Ассоциативных Контейнеров. *-сть много книг, в которых обсуждаются хеш-таблицы. Например, см. раздел 6.4 ^ги Кнута [Knu98b].
172 Глава 9. Контейнеры Является развитием Ассоциативного Контейнера (с. 156). Ассоциированные типы В дополнение к типам, определенным в требованиях к Ассоциативному Контейнеру вводится два новых типа. • Функция хеширования X hasher Тип функционального объекта: модель Функции Хеширования (с. 135). Аргумент X:: hashe г имеет тип X:: key_type. • Равенство ключей X key_equal Бинарный Предикат с аргументом типа X:: key_type. Объект типа key_equal воз- вращает t rue, если его аргументы являются одним и тем же ключом, и false в про- тивном случае. X:: key_equal должен создавать отношение равенства. Кроме того, функции хеширования и равенства ключей должны быть согласованы. Если два ключа равны друг другу в соответствии с функцией равенства ключей, то они должны хешироваться в одно и то же значение. Система обозначений X Тип, являющийся моделью Хешированного Ассоциативного Кон- тейнера. а Объект типа X. Т Тип значения X. t ОбъекттипаХ: :value_type. к Объект типа X:: key_type. р, q ОбъекттипаХ: iterator. а Объект типа X:: size_type. h Объект типа X:: hashe г. с ОбъекттипаХ: :key_equal. Определения • Функция хеширования Хешированного Ассоциативного Контейнера X — это Унар- ная Функция, тип аргумента которой — X:: key_type, а тип возвращаемого значе- ния — size_t. Функция хеширования должна быть детерминированной (она все- гда должна возвращать одно и то же значение для одного и того же аргумента)- но возвращаемые ею значения должны быть распределены как можно более раб' номерно. В идеале никакие два различных ключа не должны хешироваться в оД но и то же значение. Плохая функция хеширования, которая отображает много различных клЮ чей в одно и то же значение, отрицательно скажется на производительности. В на ихудшем случае все ключи будут отображаться в одно и то же значение. Тогда большинство операций Хешированного Ассоциативного Контейнера будет выпоД няться за О(№), а не 0(1).
9.3. Ассоциативные контейнеры 173 • Элементы Хешированного Ассоциативного Контейнера сгруппированы в“ведра” (buckets). Хешированный Ассоциативный Контейнер использует значение функ- ции хеширования, чтобы определить, в какое “ведро” поместить элемент. • Количество элементов в Хешированном Ассоциативном Контейнере, деленное на количество “ведер”, называется коэффициентом загрузки (load factor). Как пра- вило, Хешированный Ассоциативный Контейнер с небольшим коэффициентом за- грузки работает быстрее, чем с большим. Допустимые выражения Кроме тех, что определены в Ассоциативном Контейнере, должны быть допустимы следующие выражения. • Конструктор по умолчанию х() X а, Семантика: Создает пустой контейнер с hasher() в качестве функции хеширования и key_equal() в качестве функции сравнения. Постусловие: Размер контейнера равен 0. Количество “ведер” равно неко- торому значению по умолчанию. Функция хеширования — hasher(), а функция сравнения ключей на равенство — key_equal(). • Конструктор по умолчанию с заданием размера хеш-таблицы Х(п) X а(п). Семантика: Создает пустой контейнер по крайней мере с л “ведрами”, используя hasher() в качестве функции хеширования и key_equal () в качестве функции сравнения. Постусловие: Размер контейнера равен 0. Количество “ведер” больше или равно п. Функция хеширования — hasher(), а функция срав- нения ключей на равенство — key_equal(). • * Конструктор по умолчанию с заданием функции хеширования Х(п. h) X a(n h), Семантика: Создает пустой контейнер по крайней мере с п “ведрами”, используя в качестве функции хеширования h, а в качестве функции сравнения ключей — key_equal(). Постусловие: Размер контейнера равен 0. Количество “ведер” больше или равно п. Функцией хеширования является h, а функцией сравнения ключей на равенство — key_equal (). Конструктор по умолчанию с заданием сравнения ключей на равенство x(n. h. к) х a(n ь к)
174 Глава 9. Контейнеры Семантика: Создает пустой контейнер по крайней мере с п “ ведрам^ используя h в качестве функции хеширования, а к — в к ’ честве функции сравнения ключей. Постусловие: Размер контейнера равен 0. Количество “ведер” больше равно п. Функция хеширования — h, а функция сравнения ключей на равенство — к. Конструктор диапазона Х(1. ]) X a(i, з). Требования к типу: 1 и j — Итераторы Ввода, типы значений которых преобра- зуемы к типу Т. Предусловие: [i, 3) — допустимый диапазон. Семантика: Эквивалентно: Ха, a.insert(i, j), (Результат insert — то есть будут вставлены все элементы из [1, j) или только уникальные элементы — зависит от того, является X моделью Множественного Ассоциативного Кон- тейнера или Уникального Ассоциативного Контейнера). Конструктор диапазона с заданием размера хеш-таблицы X(i, j, п) X a(i, j. п); Требования к типу: 1 и з — Итераторы Ввода, типы значений которых преобра- зуемы к типу Т. Предусловие: [1,3) — допустимый диапазон. Семантика: Эквивалентно: X а(п), a insert(i, з), Конструктор диапазона с заданием функции хеширования X(i. j. п, h) X a(i, j, n, h), Требования к типу: 1 и з — Итераторы Ввода, типы значений которых преобра- зуемы к типу Т. Предусловие: [1,3) — допустимый диапазон. Семантика: Эквивалентно: X a(n, h); a insert(i, з), Конструктор диапазона с заданием сравнения ключей на равенство X(i. j, п. h, к) X а(1. j, п, h, к), Требования к типу: 1 и з — Итераторы Ввода, типы значений которых преобра зуемы к типу Т. Предусловие: [1,3) — допустимый диапазон.
9.3. Ассоциативные контейнеры 175 Семантика: Эквивалентно: Xa(n, h, к), a.insert(i, j), • функция хеширования a.hash_funct() Возвращаемый тип: X.: hasher Семантика: Возвращает функцию хеширования контейнера а. Сравнение ключей a.key_eq() Возвращаемый тип: X:key_equal. Семантика: Возвращает функцию сравнения ключей. Размер хеш-таблицы a.bucket_count() Возвращаемый тип: X::size_type Семантика: Возвращает количество “ведер” в хеш-таблице. • Изменение размера a. resize(n) Семантика: Постусловие: Увеличивает количество “ведер”. Количество “ведер” а будет не меньше п. Все итераторы, ука- зывающие на элементы а, останутся допустимыми. Обратите внимание, что, хотя изменение размера не ска- зывается на допустимости итераторов, оно, как правило, приводит к изменению порядка отношений между ите- раторами. Если 1 и j — итераторы, указывающие в Хеши- рованный Ассоциативный Контейнер, а 1 является предше- ственником j, то после изменения размера нет гарантии, что это отношение сохранится. Единственная гарантия при изменении размера — инвариантность непрерывного хра- нения: элементы с одинаковыми ключами всегда соседству- ют друг с другом. Как правило, Хешированные Ассоциативные Контейнеры пытаются сохранять свой коэффициент загрузки в достаточно узких пределах, что необходимо для соответствия ОгРаничениям на вычислительную сложность. Если бы допускался безграничный рост Коэффициента загрузки, то производительность Хешированного Ассоциативного Кон- Тейнера резко падала бы при увеличении количества его элементов. Добавление элементов в Хешированный Ассоциативный Контейнер может вызвать ароматическое изменение его размера, что оказывает влияние на итераторы и диапа- Hbi так же, как и функция-член resize. При этом сохраняется допустимость итера- Ров, но совсем не обязательно сохраняется отношение порядка между ними. Имен- Поэтому существуют resize и конструкторы, позволяющие задать количество “ве- Р • Если вы знаете заранее, насколько вырастет Хешированный Ассоциативный Рейнер, то наиболее эффективным решением будет задание количества “ведер”, °гДа контейнер еще пуст.
176 Глава 9. Контейнеры Гарантии сложности Хешированный Ассоциативный Контейнер ужесточает средние оценки сложности, Огь ределенные в Ассоциативном Контейнере. Но тем не менее показатели для наиху^ ших случаев у Хешированного Ассоциативного Контейнера довольно слабы. Низкая производительность в наихудших случаях обычно связана с неадекватным выборов функции хеширования. • Конструктор по умолчанию, конструктор с заданием размера хеш-таблицы конструктор с заданием функции хеширования и конструктор диапазона с одц’ наковым ключом выполняются за константное амортизированное время (в наи- худшем случае). • Конструктор диапазона, конструктор диапазона с заданием размера хеш-табли- цы, конструктор диапазона с заданием функции хеширования и конструктор диапазона с заданием функции сравнения ключей выполняются за время O(N), где N— размер диапазона, используемого для создания контейнера (в среднем случае). • Получение функций хеширования и равенства ключей выполняется за констант- ное время. • Для стирания одного элемента с помощью е rase( р) требуется константное амор- тизированное время (в наихудшем случае). • Средняя сложность erase(k) (стирание элементов с ключом к) равна О(п), где п равно count (к) (в наихудшем случае — O(N), гдеМ— размер контейнера). • Средняя сложность erase(p, q) (стирание диапазона) равна 0(1) (в наихудшем случае — O(N), где N- количество элементов в контейнере). • Средняя сложность f ind(к) (поиск по ключу) равна 0(1) (в наихудшем случае - 0(7V), где N— количество элементов в контейнере). • Средняя сложность equal_range( к) (нахождение всех элементов, ключи которых равны к), равна O(count(k)) (в наихудшем случае — 0(7V), где W — количество элементов в контейнере). • Средняя сложность count(к) (подсчет количества элементов с ключом к) равна O(count (к)) (в наихудшем случае — 0(N), где N — количество элементов в кон- тейнере). • Подсчет размера хеш-таблицы — 0( 1) (в наихудшем случае). • Оценка изменения размера — 0(N), где N— количество элементов в контейнере (в наихудшем случае). Модели Следующие классы контейнеров STL являются моделями Хешированного АссоциЗ' тивного Контейнера: • hash_set • hash_map 9.4. Аллокатор Аллокатор (Allocator) — это класс, инкапсулирующий некоторые детали реализаШ1’1 выделения и освобождения памяти. Все предопределенные классы контейнеров
9.4. Аллокатор 177 оЛЬзуются Аллокаторами для управления памятью. Например, при создании эк- зе^пляра класса vector вы можете указать, какой класс Аллокатора он будет ис- ^ользовать. у аллокаторов есть три аспекта, которые намного важнее технических деталей. Первое и самое важное: вероятнее всего, вам ничего и не надо знать об Аллокато- а* чтобы пользоваться стандартными классами контейнеров STL, потому что эти контейнеры задают значения параметров шаблона по умолчанию, чтобы скрыть ис- пользование Аллокаторов. Например, по умолчанию vector<int> означает то же, что и vector<int, allocate r<int> >. Вам ничего не надо знать об Аллокаторах даже при на- писании нового класса контейнера. Классу контейнера совершенно ни к чему пользо- ваться изощренными техниками управления памятью. Иногда, как мы видели на при- мере класса block (раздел 5.1), контейнерному классу вообще не нужно выделение памяти. Во-вторых, аллокаторы — наименее переносимая часть STL. В стандарте C++ опи- сывается концепция Аллокатора, но в текущем (1998) году до сих пор используются несколько нестандартных версий аллокаторов. Отчасти это происходит потому, что стандартные аллокаторы ориентированы на некоторые еще не везде реализованные свойства языка. В-третьих, хотя аллокаторы и являются параметрами контейнеров, это не значит, что вы можете контролировать каждый мыслимый аспект управления памятью кон- тейнера. Концепция Аллокатора не имеет ничего общего с тем, сколько элементов со- держится в узле deque, хранятся ли элементы контейнера непрерывно и как увеличи- вается размер vector при его реструктуризации. С фундаментальной точки зрения Аллокатор инкапсулирует методы получения и освобождения памяти. Когда вы запрашиваете память у Аллокатора, он может ис- пользовать mall ос, new или какой-нибудь другой метод, специфичный для вашей опе- рационной системы. Аллокатор может поддерживать и “список свободных блоков”, потому что для выделения одного большого фрагмента памяти часто требуется мень- ше времени, чем для выделения большого количества маленьких фрагментов. Одна- ко такие сложности — лишь внутренняя деталь реализации. Аллокатор обязан быть эффективным даже при выделении маленьких объектов, а контейнеры не должны реализовывать свои собственные, специфичные для данного класса списки свобод- блоков памяти (в этом месте стандарт C++ проще, чем оригинальная версия HP В общем случае два объекта одного класса Аллокатора не эквивалентны. Пусть а1 а а2 — объекты типа My_Allocator — могут выделять память различными способами, апример, если ваша операционная система в состоянии поддерживать выделение Памяти из нескольких различных областей, то вы можете написать класс Аллокатора, Различные объекты которого работают с различными областями памяти. Если вы со- сали контейнер типа vecto r<i nt, Му_А11 ocato r<int> >, можно указать, какую область яти этот vector будет использовать. Как и все стандартные контейнеры, vector зволяет вам задать объект-аллокатор, который он будет применять. Ди ^Се стандаРтные контейнеры используют параметры по умолчанию. Нет необхо- Мости явно указывать объект-аллокатор, если не требуется что-либо отличное от ачения по умолчанию. А в случае стандартного класса allocator это даже не полу- У^я, потому что все объекты класса allocator одинаковы.) Обк К И классы контейнеров, у каждого класса Аллокатора есть тип значения — тип ^Кта, для которого он выделяет память. Если тип значения Аллокатора — Т, то
178 Глава 9. Контейнеры вызов функции-члена allocate(n) — это запрос на выделение памяти, достаточно^ для хранения л объектов типа Т. И так же, как классы контейнеров, классы Аплокато. ра содержат вложенные типы pointer, reference и т. д. В принципе типы могут опре, делить альтернативную модель памяти, когда pointer в действительности не Т*, а кой-нибудь другой тип указателя. Однако на практике свобода выбора модели пам^ ти ограничена, потому что язык C++ не предоставляет удовлетворительного способа определения ссылочного типа, отличного от обыкновенного Т&. Так каку классов-аллокаторов есть типы значений, должны существовать и спосо- бы преобразования из одного типа значения в другой. Рассмотрим, например, класс связного списка. У list<int> есть Аллокатор, тип значения которого — int, но внутри себя list выделяет не объекты типа int, а память под узлы списка. Таким образом у класса list должна быть возможность преобразовать класс-аллокатор с одним ти- пом значения в класс-аллокатор с другим типом значения, а также возможность пре- образовать объект-аллокатор одного класса в объект другого класса. Для решения первой задачи можно воспользоваться механизмом повторного связывания (функ- ция rebind), а для решения второй — “обобщенным конструктором копирования”. И последнее. Вероятней всего, вам не придется заботиться об аллокаторах и, еще более вероятно, не придется обращаться к их нетривиальным возможностям, вроде альтернативных моделей памяти и различных экземпляров аллокатора. Если вы все же планируете ими воспользоваться, необходимо тщательно прочитать документа- цию по вашей реализации языка, потому что в разделе 20.1.5 стандарта C++ [ISO98] содержится важное предостережение: Реализациям контейнеров, описанных в Международном стандарте, допускается предполагать, что их параметр шаблона Allocator отвеча- ет следующим двум требованиям, в дополнение к перечисленным в таблице 32: • Все экземпляры данного типа аллокатора должны быть взаимоза- меняемы, а при сравнении всегда возвращать значение true (эквивалентность экземпляров). • Объявленные с помощью typedef типы pointer, const_pointer, size_type и difference_type должны быть типами Т*,Т const*, size_t и ptrdif f_t соответственно. При реализации языка поощряется создание библиотек, которые могут пользоваться аллокаторами, инкапсулирующими более общие модели памяти и поддерживающими неэквивалентные экземпляры аллокатора. В таких реализациях любые требования, наложенные контейнерами на аллокаторы сверх требований, изложенных в таб- лице 32, а также семантика контейнеров и алгоритмов в присутствии неэквивалентных экземпляров аллокатора, зависят от данной реа- лизации. Это означает, что в то время, как стандарт C++ требует от Аллокаторов синтаксиче* ской возможности использования альтернативных моделей памяти и различных эК' земпляров аллокаторов, он не гарантирует, что вы сможете воспользоваться этим11 возможностями в контейнерах, предоставляемых вашей реализацией.
9.4. Аллокатор 179 0Вдяется развитием • Сравнимого (с. Конструируемого по Умолчанию (с. 101). Аллокатор не является "азвитием Присваиваемого, потому что, хотя он и определяет конструктор копирова- ния, У него нет опеРатоРа присваивания. дссоциированные типы • Тип значения X- -value_type Тип, с которым работает аллокатор. • Тип указателя X: pointer Указатель (не обязательно стандартный указатель C++) на X:: value_type. Должна существовать возможность преобразования pointer в типы const pointer, void* и value_type*. • Константный тип указателя X' :const_pointer Константный указатель (не обязательно стандартный указатель C++) на X: :value_type. • Ссылочный тип X-:reference Ссылка на X: :value_type. Ввиду того что C++ не допускает альтернативных ссылочных типов, X: : reference должен быть X:: value_type&. • Константный ссылочный тип X..const-reference Константная ссылка на X:: value_type. Почти как в случае ссылочного типа, тип константной ссылки должен быть const X:: value_type&. • Разностный тип X. 'difference-type Целочисленный тип со знаком, предназначенный для представления разности между двумя значения типа X:: pointer. Размерный тип X::size_type Целочисленный тип без знака, который может служить для представления любо- го неотрицательного значения типа X:: dif ference_type. Изменение типа аллокатора X ’ rebind<U> other стп Синтаксис до такой степени неуклюж отчасти потому, что C++ не поддерживает “шаблоны кон- УКЦий typedef” Таким образом, other — эго typedef внутри вложенного шаблонною класса rebind
180 Глава 9. Контейнеры Для любого типа U, Х‘ : rebind<U>. other является аллокатором, тип значенИя которого U. Тип X : rebincKX: • value_type>. : other идентичен самому X. 4 Система обозначений т, и X Y а, b У t Р л Любые типы. Аллокатор, тип значения которого Т. Соответствующий Аллокатор, тип значения которого U. Объект типа X. Объект типа Y. Объект типа Т. ОбъекттипаХ: .pointer. ЗначениетипаХ. :size_type. Допустимые выражения • Конструктор по умолчанию Х() X а; Семантика: Создает экземпляр X по умолчанию. • Конструктор копирования X о(а) Х(а). Возвращаемый тип: X Семантика: Создает копию а. Постусловие: Копия, равная а. • Обобщенный конструктор копирования х а(у) Х(у), Возвращаемый тип: X Семантика: Создает копию у Аллокатора, тип значения которого U. Постусловие: Y(X( у)) ==у. • Сравнение а " b Возвращаемый тип: Преобразуемый к bool. Семантика: Возвращает t rue тогда и только тогда, когда память, выДе' ленная с помощью а, может быть освобождена с помошьЮ Ь, и наоборот. • Выделение памяти a allocate(n) Возвращаемый тип: X pointer Предусловие: 0<n<a max_size(). Семантика: Выделяет n * sizeof (Т) байтов памяти.
9.4. Аллокатор 181 Постусловие: Возвращенное значение — указатель па неинициализиро- ванный блок памяти, достаточно большой для хранения п объектов типа Т. Выделение с подсказкой а.allocated, q) Требования к типу: q — это значение типа Y • const_pointer для некоторого Y. Возвращаемый тип: Предусловие: X pointer 0 < n < a. max_size(). Указатель q является либо нулевым, либо указателем, полученным в результате одного из пре- дыдущих вызовов allocate. Семантика: Выделяет n * sizeof(Т) байтов памяти. Аргумент q являет- ся подсказкой. Аллокатор может воспользоваться этим ука- зателем для определения места выделения памяти либо полностью проигнорировать его. Постусловие: Возвращаемое значение — указатель на неинициализиро- ванный блок памяти, достаточно большой для хранения п объектов типа Т. Освобождение памяти a deallocate^, п) Возвращаемый тип: void Предусловие: р — это указатель на область памяти, полученный при пре- дыдущем вызове allocate, ап — аргумент, который был пе- редан allocate, р — ненулевой указатель, п не равен нулю. Семантика: Освобождает память, на которую указывает р. Максимальный размер a.max_size() Возвращаемый тип: X::size_type Семантика: Возвращает максимальное значение, которое можно пере- дать в качестве аргумента функции allocate. Создание a.construct^, t) Возвращаемый тип: Предусловие: void р указывает на неинициализированную область памяти, Семантика: размер которой не меньше sizeof (Т). Эквивалентно new((void*) р)T(t). Уничтожение а destroy(p) Возвращаемый тип: void Предусловие: Семантика: р указывает на объект типа Т. Эквивалентно ((Т*) р)->~Т().
182 Глава 9. Контейнеры • Получение адреса a address(r) Требования к типу: г имеет тип либо Т&, либо const Т&. Возвращаемый тип: pointer, если г имеет тип Т&, и const_pointer в случае const Т&. Предусловие: г — это ссылка на объект, память под который была выде. лена с помощью а. Семантика: Возвращает указатель на г. Модели • allocator (с. 196).
Часть III Справочное руководство: алгоритмы и классы
10 Основные компоненты Типы и функции, описанные в этой главе, являются строительными блоками, кото- рые используются в различных частях библиотеки. В основном они предельно про- сты. Однако некоторые из них крайне низкоуровневы и специализированы и потому полезны только при написании нового контейнера. 10.1. Класс pair pair <Т1, Т2> Класс pai г<Т 1. Т2> представляет собой гетерогенную пару. Класс содержит один объект типа Т1 и один объект типа Т2. Во многом он похож на Контейнер, поскольку “владеет” своими элементами (они уничтожаются, когда уничтожается pair), но фактически не яв чяется моделью Контейнера, так как не поддерживает стандартные методы доступа к его элементам, например итераторы. Функции, которым нужно вернуть в качестве результата два значения, обычно воз- вращают пару. Пример pair<bool. double> result = do_a_calculation(); if (result first) do_something_more(result second), else report_error(), Где определено В реализации HP класс pair определен в заголовочном файле <pair h>. В соответ' ствии со стандартом C++ он объявлен в заголовочном файле <unlity>. Параметры шаблона Т1 Тип первого элемента, хранящегося в pai г. Т2 Тип второго элемента, хранящегося в pai г.
10.1. Класс pair 185 ^ляется моделью иСВаиваемого (с. 100), только если Т1 и Т2 оба являются Присваиваемыми; Констру- * veMoro по Умолчанию (с. 101), только если Т1 и Т2 оба являются Конструируемыми умолчанию; =Сравнимого (с. 102), только если Т1 и Т2 оба являются ^Сравнимыми; сравнимого (с. 103), только если Т1 и Т2 оба являются Сравнимыми. Требования к типам Отсутствуют. Открытые базовые классы Отсутствуют. Члены Некоторые из членов класса pai г не определены ни в концепции Присваиваемого, ни в концепции Конструируемого по Умолчанию, ни в концепциях Сравнимого и Срав- нимого. Эти члены специфичны для pair и помечены символом ♦. Ф pair:: first_type Тип первой компоненты пары Т1. ♦ pair:: second_type Тип второй компоненты парыТ2. ♦ pair:: pair(const Т1& х, const Т2& у) Конструктор. Создает пару таким образом, что первый элемент, f i rst, создает- ся из х, а второй, second, — из у. pair::pair(const pair&) Конструктор копирования. (Описан в концепции Присваиваемого.) ♦ template <class U1, class U2> pair::pair(const pair<U1, U2>& p) Обобщенный конструктор копирования. Если у Т1 есть конструктор, принима- ющий единственный аргумент типа U1, а у Т2 есть конструктор, принимающий единственный аргумент типа U2, то данный конструктор создает такую пару, что первый элемент, f i rst, инициализируется по р. f i rst, а второй элемент, second, — no р. second. pair& pair::operator=(const pair&) Оператор присваивания. (Описан в концепции Присваиваемого.) ♦ Pair::first Открытый член типа f i rst_type — первый элемент пары. * Pair::second Открытый член типа second_type — второй элемент пары.
186 Глава 10. Основные компоненты bool operator==(const pair&, const pair&) Оператор сравнения на равенство. (Описан в концепции =Сравнимого.) bool operator<(const pair& х, const pair& у) Оператор сравнения. Выражение х < у является истинным, если х. f i rst меньще чему first, или если х second меньше, чем у. second, а х. first и у first одина. ковы. (Описан в концепции <Сравнимого.) ♦ template <class Т1, class Т2> pair<T1, Т2> make_pair(const Т1& х, const Т2& у) Вспомогательная функция для создания пары. Эквивалентна paiг<Т1, Т2>(х, у), но часто более удобна. 10.2. Примитивы итераторов 10.2.1. Класс iterator_traits iterator_traits<Iterator> Как обсуждалось в разделе 3.1, у всех итераторов есть ассоциированные типы. Один из таких типов — тип значения, то есть тип объектов, на которые указывает итератор. У итератора также есть ассоциированный разностный тип — целочисленный тип со знаком, используемый для представления разности между двумя итераторами. Для указателя типа int*, например, тип значений — int, а тип разности — pt rdif f_t. STL определяет механизм доступа к ассоциированным типам как часть требований к итераторам. Например, если II является моделью Итератора Ввода, то его тип зна- чения — iterator_traits<II>: :value_type. Стандартные итераторы STL удовлетво- ряют данным требованиям, определяя три версии шаблонного класса iterater_t raits: обобщенную, специализированную для указателей и еще более специализированную для константных указателей. Это пример частичной специализации (partial specialization), так как две специали- зированные версии сами по себе являются шаблонами: template <class Iterator> struct iterator_traits { typedef typename Iterator iterator_category iterator_category, typedef typename Iterator value_type value_type, typedef typename Iterator difference_type difference_type, typeaef typename Iterator pointer pointer, typedef typename Iterator reference reference, template <class T> struct iterator_traits<T*> {
10.2. Примитивы итераторов 187 typedef typedef typedef typedef typedef r andom_access_iterator_iag T ptrdiff.t T* iterator_caregory, value_type, difference_type, pointer, reference; T& template <class T> struct iterator_traits<const T*> typedef random_access_iterator_tag iterator_category, typedef T value_type, typedef ptrdiff_r difference_type, typedef const T* pointer, typedef }; const T& reference, Когда вы создаете новый тип итератора I, необходимо убедиться, что iterator_traits<I> определен правильно. Существует два способа сделать это. Во- первых, можно определить ваш итератор так, чтобы у него были вложенные типы Г. :value_type, I: :difference_type и т. д. Во-вторых, можно явно специализировать iterator _t rails для вашего типа. Первый способ, однако, более удобен, особенно с уче- том того, что есть возможность воспользоваться наследованием от базового класса iterator (с. 193). Класс iterator_traits — относительно новое дополнение к библиотеке STL. Он не входил в оригинальную реализацию HP. (Дело в том, что частичной специализа- ции в то время не было.) В реализации HP STL предлагался другой способ запросить ассоциированные типы итераторов — три функции: value_type, distance_type и iterate ^category (см. раздел З.1.6.), которые больше не входят в стандарт C++. Пример Обобщенная функция возвращает последний элемент в непустом диапазоне. Не по- лучится определить функцию с таким интерфейсом в терминах старой функции value_type, так как тип возвращаемого значения функции должен быть объявлен, а так- Же совпадать с типом значения итератора. template <class Inputlter> typename iterator_traits<lnputlter> value_type last_value(lnputlter first, Inputlter last) { typename iterator_traits<lnputlter>. value_type result = «first. for (++first, first != last, ++first) result = «first, } (Это cnA ЛИШь пРост°й пример использования iterator_t raits, а не рекомендуемый спо- Написания данного обобщенного алгоритма. Существуют более удачные способы Ио СКа последнего элемента в диапазоне Двунаправленных Итераторов или даже Од- Направленных Итераторов.)
188 Глава 10. Основные компоненты Где определено Класс iterater_t raits не входил в реализацию HP. В соответствии со стандартом (X он объявлен в заголовочном файле <iterator>. Параметры шаблона Iterator Тип итератора, для которого запрашиваются ассоциировав ные типы. Является моделью Конструируемого по Умолчанию (с. 101), Присваиваемого (с. 100). Требования к типам • Iterator — модель Итератора Ввода (с. 109) или Итератора Вывода (с. 112). Открытые базовые классы Отсутствуют. Члены Все члены iterater_traits — это вложенные определения typedef. iterator_traits::iterator_category Тег (tag): input_iterator_tag, output_iterator_tag, forward_iterator_tag, bidirectional_iterator_tag или random_access_iterator_tag. Категория итератора (iterator’s category) — это самая специфическая концепция итератора, моделью которой он является. iterator_traits::value_type Тип значения итератора, как задано в требованиях к Итератору Ввода. Заметим, что этот член можно использовать, только если класс Ite rato г является моделью концепции Итератора Ввода. Итератор Вывода не обязан иметь тип значения. iterator_traits::difference_type Разностный тип итератора, как определено в требованиях к Итератору Ввода. Заметим, что Итератор Вывода не обязан иметь разностный тип. iterator_traits::pointer Тип указателя итератора, как определено в требованиях к Итератору Ввода. За- метим, что Итератор Вывода не обязан иметь тип указателя. iterator_traits::reference Ссылочный тип итератора, как определено в требованиях к Итератору Ввода. метим, что Итератор Вывода не обязан иметь ссылочный тип. 10.2.2. Классы тегов итераторов struct output_iterator_tag {}; struct input_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {};
10.2. Примитивы итераторов 189 Str“ct str«Ct bidi rectional.ite rate r_tag random_access_iterator_tag public forward_iterator_tag {}; public bidirectional_iterator_tag {}; Пять классов тегов (tag classes) итераторов полностью пусты. У них нет ни функ- ИЙ'член0В’ НИ пеРеменных’членов’ ни вложенных типов. Как подсказывают их на- звания, они используются только кг.к метки. представляющие собой пять концепций итераторов в системе типов языка C++. Классы тегов итераторов тесно связаны с iterate r_t raits. Для любого итератора I terator_traits<I>: . iterator_category является одним из классов тегов. Вообще, категория итератора — это наиболее специфическая концепция итератора, моделью которой он является. Например, int* является моделью Итератора Произ- вольного Доступа, а также моделью Двунаправленного Итератора, Однонаправленного Итератора и т. д. Однако наиболее специфическая из этих концепций — именно кон- цепция Итератора Произвольного Доступа (она представляет собой развитие всех дру- гихконцепций итератора). Таким образом, iterator_t raits<lnt*>:: iterator_category является типом random_access_iterator_tag. Пример Обобщенный алгоритм reverse (с. 268) можно реализовать для Двунаправленного Итератора, но существует более эффективная версия для Итератора Произвольного Доступа. Следовательно, reverse использует iterater_traits и классы тегов, чтобы выбрать алгоритм, соответствующий типу итератора. Выбор происходит на этапе ком- пиляции и не ведет к увеличению времени выполнения. template <class BI> void __reverse(BI first, BI last. bidirectional_iterator_tag) { while (true) if (first ” last || first == --last) return. else iter_swap(first++. last), } template ^class RAI> v°!d __reverse(RAI first, RAI last, random_access_iterator_tag) { while (first < last) iter_swap(first++, --last). template <class BI> inline void reverse(BI first. BI last) { — reverse (first, last. typename iterator_traits<BI> iterator_category()), определено ^.Реализации HP классы тегов итераторов были объявлены в заголовочном файле Ле rator- h>- В соответствии со стандартом C++ они объявлены в заголовочном фай- <iterator>.
190 Глава 10. Основные компоненты Параметры шаблона Отсутствуют. Являются моделью Конструируемого по Умолчанию (с. 101), Присваиваемого (с. 100). Открытые базовые классы Структуры тегов (tag structs) итераторов образуют иерархию наследования forward_iterator_tag унаследован от input_iterator_tag, bidi rectional_iterator~tag унаследован от forward_iterator_tag, аналогично, random_access_iterator_tag уна- следован от bidirectional_iterator_tag. Может возникнуть вопрос, почему f orward_iterator_tag имеет в качестве базового класса input„iterator_tag, а не output_iterator_tag. Но по большому счету не так важно, что эти классы тегов вообще используют наследование. Иерархия наследования клас- сов тегов итераторов не имеет глубокого смысла. Это удобно, поскольку иногда упро- щает написание обобщенных алгоритмов, выбирающих реализацию в зависимости от категории итератора. Наследование от output_iterator_tag опущено потому, что нс требовалось для данной цели. 10.2.3. distance 1) template <class Inputlterator> typename iterator_traits<lnputlterator>::difference_type distance(lnputlterator first, Inputiterator last); 2) template <class Inputiterator, class Distance> void distance(lnputlterator first, Inputiterator last, Distance& n); Алгоритм distance — это итераторный примитив, который используется в других ал- горитмах STL. Он находит расстояние между f i rst и last, то есть определяет, сколько раз нужно инкрементировать first, пока он не сравняется с last *}. Значения в диа- пазоне [fi rst,last) не важны, так как distance работает только с самими итератора- ми. Ни один из итераторов не разыменовывается. Две версии distance делают одно и то же, используя два различных интерфейса. Версия 2 была определена в первоначальной реализации HP STL [SL95], но в про* цессе стандартизации интерфейс был изменен. В стандарт языка C++ [IS098] вклЮ' чена только версия 1. В настоящее время (1998 год) некоторые реализации STL вклЮ' чают в себя только версию 1, другие — только версию 2, а третьи — обе версии Д-пЯ обеспечения перехода от старого интерфейса к новому. Версия 1 просто возврата' ет расстояние между f i rst и last; версия 2 прибавляет это расстояние к параметру Интерфейс версии 2 довольно громоздок. Чтобы найти расстояние между firSu и last, вы должны создать локальную переменную, инициализировать ее в 0 и после этого обратиться к distance. Это неудобно и ведет к ошибкам (обычно забывают инн ) В этом причина, чю distance не определено для Итераторов Вывода. Не всегда можно сравШ,,,а|1 Итераторы Вывода па равенство
10.2. Примитивы итераторов 191 0ализировать локальную переменную в 0), поэтому интерфейс был изменен на ис- Дуемый s версии! Причина, по которой в оригинальной библиотеке ML применялся этот неудоб- g интерфейс, заключается в том, что объявление типа, возвращаемого функцией я stance, довольно нетривиально. Если мы подсчитываем количество элементов в ди- ^азоне [first, last), равных value, то тип возвращаемого значения должен быть це- очисленным типом, достаточным, чтобы вместить это количество. Каждый тип ите- аТОров имеет ассоциированный разностный тип, который может представлять рас- стояние между любыми двумя итераторами этого типа. Например, разностный тип для встроенных указателей — это ptrdiff_t. Тип значения, которое вычисляет distance, — это разностный тип Итератора Ввода. Механизм для объявления такого типа возвращаемого значения — класс iterator^raits (раздел 3.1). Если I является моделью Итератора Ввода, то вложен- ный тип iterator_traits<I> difference_type представляет собой разностный тип I. Оригинальная версия библиотеки HP STL использовала другой метод, поскольку вто время не было iterator_traits. Версия 2 запоминает значение в переменной, пре- доставляемой пользователем. Тип Distance в версии 2 должен быть не меньше, чем разностный тип Итератора Ввода. Где определено В реализации HP distance был объявлен в заголовочном файле <algo. h>. В соответ- ствии со стандартом C++ он объявлен в заголовочном файле <iterator>. Требования к типам Версия 1: • Inputlterator является моделью Итератора Ввода. Версия 2: * Inputlteratoг является моделью Итератора Ввода. • Distance является целочисленным типом, достаточным для представления неотрицательных значений разностного типа Итератора Ввода. предусловия ®еРсия 1: [first, last) является допустимым диапазоном. &еРсия 2: [first, last) является допустимым диапазоном. Количество элементов в диапазоне [first, last) не превышает максимального значения типа Distance. ®^Нка сложности ^^ОлНяется за константное время, если Inputiterator является моделью Итератора ^звольного Доступа. В противном случае выполняется за линейное время.
192 Глава 10. Основные компоненты Пример Этот надуманный пример показывает, как использовать версию 1 алгоритма distanc& int main() { list<int> L, L push_back(O), L push_back(1), assert(distance(L begin(), L end()) == L sizeQ), } Безусловно, distance существует не для таких тривиальных примеров, а как прими- тив, используемый другими обобщенными алгоритмами. Например, реализация lower_bound (с. 307) использует distance следующим образом. template <class Forwarditerator, class T> Forwarditerator lower_bound(Forwarditerator first, Forwarditerator last, const T& value) { iterator_traits<ForwardIterator> difference_type len - distance(first, last), iterator_traits<ForwardIterator> difference_type half, Forwarditerator middle, while (len > 0) { half = len / 2, middle = first, advance(middle, half), if (*middle < value) { first - middle, ++first. len = len - half - 1, } else len = half. } return first, } 10.2.4. advance template <class Inputiterator, class Distance> void advance(lnputlterator& i, Distance n); Выражение advance( i, n) инкрементирует n раз итератор i. Если n > 0, то это эквива лентно операции + + 1, выполненной п раз (что будет намного быстрее, есл11 Input Iterate г является моделью Итератора Произвольного Доступа), а если п < 0,т° это эквивалентно операции --1, выполненной abs(n) раз. Если же п == 0, то ничего меняется. Отрицательное значение разрешено, только если Inputiterator является модель10 Двунаправленного Итератора.
10.2. Примитивы итераторов 193 Где определено «злизации HP advance был объявлен в заголовочном файле <algo h>. В соответ- ^•вийсо стандартом C++ он объявлен в заголовочном файле <iterator>. Требования к типам • Inputiterator является моделью Итератора Ввода. • Distance является целочисленным типом, преобразуемым к разностному типу итератора Inputiterator. Предусловия • i — несингулярный итератор. • Каждый итератор между i и i+n (включительно) является несингулярным. • Если Inputiterator является моделью Итератора Ввода, но не является моделью Двунаправленного Итератора, то п должно быть неотрицательным. Если Input Ite rate г является моделью Двунаправленного Итератора (включая случай, когда он является моделью Итератора Произвольного Доступа), условие не на- кладывается. Оценка сложности Константная, если тип Inputiterator является моделью Итератора Произвольного Доступа. В противном случае линейная. Пример Этот надуманный пример показывает, как использовать advance, чтобы инкременти- ровать Итератор Произвольного Доступа п раз: int main() { slist<int> L, L. push_front(0), L.push_front(1), slist<int> iterator i = L begin(), advance(i, 2), assert(i == L end()). k в и в случае с distance (с. 190), реально advance предназначен для использования Реализации других обобщенных алгоритмов. Например, lower_bound написан как на Иове distance, таки advance. Реализация lower_bouna представлена нас. 192. Базовый класс итераторов 6rator<Category, T, Distance, Pointer, Reference> ь сс iterator является пустым базовым классом. Он не содержит ни функций-чле- »«и переменных-членов, а только вложенные определения typedef.
194 Глава 10. Основные компоненты Нет необходимости использовать класс iterator, если вы не намерены опреде лить свой собственный класс итераторов. Фактически вам не обязательно обращу ся к нему, даже когда вы определяете класс-итератор. Это всего лишь один из вари антов. Когда вы пишете новый класс итераторов, следует убедиться, что он надлежащ^ образом работает с iterator_t raits. Простейший способ проверки — включить в ващ класс пять вложенных типов: iterator_category, value.type, difference_type, pointer и reference. Можно определить типы явно либо унаследовать новый класс итерат0. ров от iterator, который сделает свои аргументы шаблона доступными в виде пяти определений typedef. Заметим, что в реализацию HP STL не входил класс iterator. Вместо этого в нее включено пять различных базовых классов итераторов: input-iterator, output_iterator forward-iterator, bidirectional-iterator и random_access_iterator, которые больше не являются частью стандарта языка C++. В HP STL вы можете определить новый Итератор Вывода, унаследовав его от output-iterator и новый Однонаправленный Итератор, унаследовав его от forward_iterator<T> В соответствии со стандартом языка C++ можно вместо этого определить новый Ите- ратор Вывода, унаследовав его от iterator<oucput_iterator_tag, void, void, void, void> и новый Однонаправленный Итератор, унаследовав его от iterator<forward_iterator_tag, Т> Пример class my_forward_iterator . public std .iterator<forward_iterator_tag, double> { }. Здесь my_forward_iterator объявляется как Однонаправленный Итератор, тип значе- ния которого — double, разностный тип — ptrdiff_t, а типы указателя и ссылки " double* и double&. Можно сделать то же, не используя ite rato г, а определяя вложенные типы в самом классе my_forwa rd_iterator: class my_forward_iterator { public typedef forward_iterator_tag iterator_category, typedef double value_type; typedef ptrdiff_t difference_type,
10.2. Примитивы итераторов 195 •typedef double* typedef double& pointer: reference, р^ределение вложенных типов в явном виде несколько многословнее, однако про- чнее. Кроме того, иногда можно получить лучшую производительность, объявляя их явно. Существуют компиляторы, где пустые базовые классы реально занимают некоторое место. (Стандарт языка C++ не требует и не запрещает это.) Если вы ис- пользуете такой компилятор и наследуете my_forward_iterator от iterator, то будете зря терять несколько байтов каждый раз, когда создаете объект my_forward_iterator. Где определено Класс iterator не входил в реализацию HP. В соответствии со стандартом C++ он объявлен в заголовочном файле <iterator>. Параметры шаблона Category Структура тегов итератора (с. 188), представляющая кате- горию итератора: input_iterator_tag, output_iterator_tag, forward_iterator_tag, bidirectional_iterator_tag или random_access_iterator_tag. Категория итератора — это наи- более специфичная концепция, моделью которой является итератор. Вложенный тип iterator_category также будет яв- ляться этим типом. Т Тип значения итератора — тип объекта, на который указы- вает итератор, также определен как вложенный тип value_type. Distance Разностный тип итератора — целочисленный тип со знаком, который может представлять расстояние между двумя ите- раторами. Вложенный тип dif ference_type также будет яв- ляться таким типом. По умолчанию: ptrdiff_t. Pointer Указатель на тип значения итератора. Тип указателя почти всегда либо Т*, либо const Т*. Первый — для изменяемого итератора, второй — для константного итератора. Вложен- ный тип pointer также будет являться таким типом. Deference По умолчанию: Т*. Ссылка на тип значения итератора. Ссылочный тип почти всегда либо Т&, либо const Т&. Первый — для изменяемого итератора, второй — для константного итератора. Вложен- ный тип reference также будет являться таким типом. ^Мяется моделью По умолчанию: Т&. Жируемого по Умолчанию (с. 101), Присваиваемого (с. 100).
196 Глава 10. Основные компоненты Открытые базовые классы Отсутствуют. 10.3. Класс allocator allocateг<Т> Пример Очень редко приходится непосредственно обращаться к классу allocator. Если вы не создаете новый класс контейнеров, то встретить Аллокатор (с. 176) мо^. но лишь как параметр шаблона контейнера, а необходимость упоминать этот пара- метр шаблона возникает при желании использовать что-либо другое вместо класса allocator. Все стандартные контейнеры STL имеют параметр шаблона, являющийся моде- лью Аллокатора, и по умолчанию значение параметра шаблона — allocator. Напри- мер, vecror<int> — это то же, что vector<int, allocator<int> >. Когда вы пишете новый контейнерный класс и хотите параметризовать способ его работы с динамической памятью, можно следовать стандартному образцу, давая классу параметр шаблона, по умолчанию использующий класс allocator. Где определено В реализации HP почти целиком отличающаяся версия класса allocator определена в заголовочном файле <allocator. h>. В соответствии со стандартом C++ allocator определен в заголовочном файле <memory>. Параметры шаблона Т Тип значения аллокатора, тип объекта, для которого выде- ляется и освобождается память. Является моделью Аллокатора (с. 176) Требования к типам Отсутствуют. Открытые базовые классы Отсутствуют. Члены Следующие члены определены для общего случая класса allocator<T>. Существуй также специализированный класс allocator<void>, который содержит только члены value_type, pointer, const_pointer и rebind. (Только члены, имеющие смысл Д;1>1 allocator<void>.) У класса allocator нет никаких специальных членов. Все его члены определсНь1 в требованиях к Аллокатору.
10.3. Класс allocator 197 allocator::value_type Тип значения аллокатора, Т (Описан в концепции Аллокатор.) allocator::pointer Тип указателя аллокатора, Т* (Описан в концепции Аллокатор.) allocator::const_pointer Тип константного указателя аллокатора, const Т* (Описан в концепции Аллокатор.) allocator::reference Тип ссылки аллокатора, Т& (Описан в концепции Аллокатор.) allocator::const_reference Тип константной ссылки аллокатора, const Т& (Описан в концепции Аллокатор.) allocator::size_type Беззнаковый целочисленный тип size_t (Описан в концепции Аллокатор.) allocator::difference_type Целочисленный тип со знаком pt rd if f_t (Описан в концепции Аллокатор.) allocator::rebind Вложенный шаблон класса. У класса rebind<U> есть единственный член other, который является typedef-определением для allocater<U>. (Описан в концепции Аллокатор.) allocator: :allocator() Конструктор по умолчанию. (Описан в концепции Аллокатор.) allocator::allocator(const allocator^ Конструктор копирования. Поскольку все экземпляры класса allocate г эквива- лентны, разница между конструктором по умолчанию и конструктором копиро- вания чисто синтаксическая. (Описан в концепции Аллокатор.) template <class U> allocator::allocator(const allocator<U>&) Обобщенный конструктор по умолчанию. (Описан в концепции Аллокатор.) allocator::-allocator() Деструктор. (Описан в концепции Аллокатор.)
198 Глава 10. Основные компоненты pointer allocator::address(reference x) const Возвращает адрес объекта. Выражение a. address(x) эквивалентно &x. (Описан в концепции Аллокатор.) const-pointer allocator::address(const_reference x) const Возвращает адрес константного объекта. Выражение a. address(x) эквивалент но &х. (Описан в концепции Аллокатор.) pointer allocator::allocate(size_type n, const void* = 0) Выделяет блок памяти достаточного объема для хранения п объектов типа Т. Вто- рой аргумент — лишь подсказка. Реализация может использовать его для увели- чения локальности обращений к памяти либо игнорировать его полностью. (Описан в концепции Аллокатор.) void allocator::deallocate(pointer р, size_type n) Освобождает блок памяти, ранее выделенный с помощью allocate. (Описан в концепции Аллокатор.) size_type allocator::max_size() const Возвращает максимально возможное значение аргумента allocate. (Описан в концепции Аллокатор.) void allocator::construct(pointer р, const Т& х) Эквивалентно new((const void*)p)Т(х) (Описан в концепции Аллокатор.) void allocator::dostroy(pointer p) Эквивалентно p->~T(). (Описан в концепции Аллокатор.) 10.4. Примитивы управления памятью “Алгоритмы” из данного раздела не похожи на другие в библиотеке STL. Они работают с неинициализированной памятью, а не с реальными объектами языка C++. Такие опе- рации низкого уровня полезны в основном для реализации контейнерных классов. Три наиболее важных алгоритма в этом разделе — uninitialized_copy- uninitialized-fill и uninitialized_fill_n. Они близки алгоритмам сору (с. 239), fi1: (с. 254) и fill_n (с. 255). Единственная разница состоит в том, что copy, fill и f 11V присваивают новые значения уже существующим объектам, тогда как алгоритмы uninitialized_copy, uninitialized-fill и uninitialized_fill_n создают новые объекты- 10.4.1. construct template <class Т1, class T2> void construct(T1* p, const T2& value); В языке C++ оператор new выделяет память для объекта, а затем создает там объс^1 обращаясь к конструктору. Иногда полезно разделить эти операции. (Например, к°г да вы реализуете контейнерный класс.)
10.4. Примитивы управления памятью 199 роли Р является указателем на выделенную, но не инициализированную память, conStruct(p, value) создает объект типа Т1 на указанном р месте. Аргумент value Т°педается в качестве аргумента конструктора класса Т1. р^ражение construct(p, value) по существу эквивалентно new(p)T1(value). Где определено Яоеализации HP const ruct был объявлен в заголовочном файле <algo. h>, в реализации ^^заголовочном файле <memory>. В стандарт C++ алгоритм construct не включен. Требования к типам , у Т1 есть конструктор, который принимает единственный аргумент типа Т2. Предусловия • р — допустимый указатель на область памяти, размер которой как минимум sizeof(T1). • Память, на которую указывает р, не инициализирована, то есть в этой памяти не созданы никакие объекты. Пример string* sptr = (string*) malloc(sizeof(string)); construct(sptr, "test' ), assert(strcmp(sptr->c_str(), "test”) == 0), 10.4.2. destroy 1) template <class T> void destroy(T* pointer); 2) template <class Forwardlterator> void destroy(Forwarditerator first, Forwarditerator last); В языке C++ оператор delete уничтожает объект, вызывая его деструктор (destruc- tor)» азатем освобождает память, где хранился объект. Иногда полезно разделить эти операции, например когда вы реализуете контейнерный класс. Контейнер обычно Управляет памятью, часть которой содержит объекты языка, а часть — не инициали- зирована. С помощью dest гоу можно вызвать деструктор объекта, не освобождая па- Мять. где хранился объект. версия 1 destroy уничтожает объект, на который указывает pointer, обращаясь к де- структору Т:. -Т(). Память, где хранился объект, не освобождается и может быть ис- льзована заново для какого-либо другого объекта. Версия 2 dest гоу уничтожает все объекты в диапазоне [fi rst, last). Это эквивален- иьтзову destroy(&*i) для каждого итератора i в диапазоне [first, last). определено ^^ализации HP dest гоу был объявлен в заголовочном файле <algo. h>, в реализации в заголовочном файле <memory>. В стандарт C++ алгоритм dest гоу не включен. Мования к типам ВеРсия 1: Доступен деструктор класса Т, ~Т().
200 Глава 10. Основные компоненты Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Тип значения Forwarditerator имеет доступный деструктор. Предусловия Версия 1: • pointer указывает на корректный объект типа Т. Версия 2: • [fi rst, last) — допустимый диапазон. • Каждый итератор в диапазоне [first, last) указывает на корректный объект. Оценка сложности Сложность выполнения версии 2 линейна: она вызывает деструктор ровно last - first раз. Пример class Int { public Int(int x) val(x) {} int get() { return val, } private int val. }. int main() Int A[] = { Int(1). Int(2), Int(3) }. destroy(A, A + 3). constructs + 0, Int(10)), constructs + 1. Int(11)): constructs + 2, Int(12)). 10.4.3. uninitialized-copy template <class Inputiterator, class Forwardlterator> Forwarditerator uninitialized_copy(lnputlterator first, Inputiterator last, Forwarditerator result); Алгоритм uninitialized_copy — одна из низкоуровневых компонент STL, которь1 позволяют развязать процессы выделения памяти и создания объектов. Если ка> дый итератор в выходном диапазоне [ result, result + (last - f i rst)) указывает на Ht инициализированную память, то uninitialized_copy использует конструктор ко1\ рования для создания копий объектов [f i rst, last) в выходном диапазоне. Таким °°
10.4. Примитивы управления памятью 201 оМ, Для каждого итератора 1 во входном диапазоне uninitialized_copy создает ко- 10 *1 на соответствующем месте в выходном диапазоне, обращаясь п const ruct(&*( result+ (i - first)), *i). * функции типа uninitialized_copy полезны при реализации контейнерного класса. |1аПРимеР’ конструктор диапазона контейнера обычно состоит из двух шагов: 1 Выделение блока неинициализированной памяти достаточного размера, чтобы вместить все элементы диапазона. 2 . Использование uninitialized_copy для создания элементов контейнера в выде- ленном блоке памяти. Так как uninitialized_copy в основном предназначен для использования в контейнерах, у него необычные “обязанности” — использовать uninitialized_copy для написания кода, безопасного в случае исключительных ситуаций. В частности, должна существовать возможность написать класс контейнера, который не провоцирует утечек памяти, если один из конструкторов, вызванных из uninitialized_copy, создает исключительную ситуацию. Стандарт языка C++ требует, чтобы uninitialized_copy имел семантику с фиксацией результата или с откатом назад (commit or rollback). Он либо создает все запрошенные элементы, либо не создает вообще ничего, если хоть один из кон- структоров копирования сгенерировал исключительную ситуацию. Здесь приводит- ся одна из возможных реализаций. template <class Inputiterator, class Forwardlterator> Forwarditerator uninitialized_copy(lnputlterator first, Inputiterator last, Forwarditerator result) { Forwarditerator cur = result, try { for ( ; first 1= last, ++first, ++cur) construct(&*cur, *first), return cur, } catch( ) { destroy(result, cur), throw. } определено ^Реализации HP uninitialized_copy был объявлен в заголовочном файле <algo h>. соответствии со стандартом C++ он объявлен в заголовочном файле <memory>. требования к типам Inputiterator — модель Итератора Ввода. ForwardIteratoг — модель Однонаправленного Итератора. Forwarditerator — изменяемый итератор.
202 Глава 10. Основные компоненты Тип значения Forwarditerator имеет конструктор, принимающий единствен аргумент, по типу совпадающий с типом значения Input Iterate г. Предусловия • [first, last) — допустимый диапазон. • [result, result + (last - first)) — допустимый диапазон. • Каждый итератор в диапазоне [result, result + (last - first)) указывает на05 ласть неинициализированной памяти достаточного размера, чтобы вместщь значение типа значения итератора Forward Iterator. Оценка сложности Линейная. Выполняется ровно last - first вызовов конструктора. Пример class Int { public Int(int x) val(x) {} // Int не имеет конструктора по умолчанию int get() { return val, } private- int val, } int mainf) { mt A1[] = {1, 2, 3, 4, 5. 6, 7}, const int N = sizeof(A1) / sizeof(int), Int* A2 = (Int*) malloc(N * sizeof(Int)), uninitialized_copy(A1, A1 + N, A2). } 10.4.4. uninitialized_fill template <class Forwarditerator, class T> void uninitialized_fill(ForwardIterator first, Forwarditerator last, const T& x); Как и uninitialized_copy (c. 200), uninitialized_fill — один из низкоуровневых ал горитмов STL, которые позволяют развязать процессы выделения памяти и создания объектов. Если каждый итератор в выходном диапазоне [first, last) указывав на неинициализированную память, то uninitialized_fill создает копии значения ' в этом диапазоне. Таким образом, для каждого итератора i в диапазоне [first, laSL uninitialized-fill создает копию х в этом месте, вызывая const ruct(&*i, х). Как uninitialized_copy полезен при реализации конструктора, создающего контсП нер из заданного диапазона элементов, так и алгоритм uninitialized_fill полсз^1
10.4. Примитивы управления памятью 203 ^реализации констРУктоРа> создающего контейнер, каждый элемент которого ини- ^ализируется значением х. U фк и uninitialized_copy, uninitialized_fill должен иметь семантику с фиксацией „улътатпа или с откатом назад. Он должен либо создавать все элементы, либо не Сдавать ничего. Если хоть один из конструкторов копирования сгенерировал ис- ^дочительную ситуацию, то uninitialized_f ill должен выполнить очистку, уничто- все созданные им элементы. Где определено В реализации HP uninitialized-fill был объявлен в заголовочном файле <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <memory>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Тип значения Fo rwa rdl te rato г имеет конструктор, принимающий единственный аргумент типа Т. Предусловия • [first, last) — допустимый диапазон. • Каждый итератор в диапазоне [f i rst, last) указывает на область неинициализи- рованной памяти достаточного размера, чтобы вместить значение типа значе- ния Fo rwa rd Ite rat о г. Оценка сложности Линейная. Выполняется ровно last - f i rst вызовов конструктора. Пример class Int { Public: Intfint x) . val(x) {} // Int не имеет конструктора no умолчанию int get() { return val, } Private: mt val. >; int main() const int N = 137. int val(46), Int* A = (Int*) malloc(N * sizeof(Int)), uninitialized_fill(A. A + N. val),
204 Глава 10. Основные компоненты 10.4.5. uninitialized_fill_n template <class Forwarditerator, class Size, class T> Forwarditerator uninitialized_fill_n(ForwardIterator first, Size n, const T& x); Алгоритм uninitialized_fill_n похож на uninitialized_fill. Это один из низкоур0в невых алгоритмов STL, которые позволяют развязать процессы выделения памя1И и создания объектов. Он инициализирует все элементы диапазона копией одного и того же значения Разница между двумя алгоритмами состоит в том, что uninitialized_f ill работает на диапазоне, заданном парой итераторов, тогда как uninitialized_fill_n работает на диапазоне, заданном одним итератором и некоторым количеством элементов. Это позволяет uninitialized_f 111_п работать с Итераторами Вывода. Если каждый итератор в выходном диапазоне [first, first + п) указывает на не- инициализированную память, то uninitialized.f ill_n создает копии значения х в этом диапазоне с помощью конструктора копирования. Таким образом, для каждого ите- ратора 1 в диапазоне [f i rst, f i rst + n) uninitialized_fill_n создает копию x в соответ- ствующем месте, вызывая const ruct(&*i, х). Так же, как uninitialized_copy и uninitialized_fill, uninitialized_fill_n имеет семантику с фиксацией результата или с откатом назад. Он создает все элементы или не создает ничего. Если хоть один из конструкторов копирования сгенерировал исключительную ситуацию, то uninitialized_fill_n должен выполнить очистку, унич- тожая все созданные им элементы. Где определено В реализации HP uninitialized_f ill_n был объявлен в заголовочном файле <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <memory>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • ForwardIteratoг — изменяемый итератор. • Size — целочисленный тип, который может быть преобразован к типу разности Forwarditerator. • У типа значения ForwardIteratoг есть конструктор, принимающий единствен- ный аргумент типа Т. Предусловия • п — неотрицательно. • [first, first + n) — допустимый диапазон. • Каждый итератор в диапазоне [first, first + п) указывает на область неинп циализированной памяти достаточного объема, чтобы вместить значение тппа значения Forwarditerator. Оценка сложности Линейная. Выполняется ровно п вызовов конструктора.
10.5. Временные буферы 205 0рИМсР class Int { public lnt(int x) val(x) {} U Int не имеет конструктора по умолчанию int get() { return val, } private int val; }; int main() { const int N = 137, Int val(46), Int* A - (Int*) malloc(N * sizeof(Int)), uninitialized_fill_n(A. N, val); 10.5. Временные буферы Некоторые алгоритмы, например stable_sort или inplace .merge, являются адаптив- ными (adaptive). Они пытаются выделить дополнительную память для промежуточ- ных результатов. Если выделение памяти прошло успешно, то адаптивные алгорит- мы переключаются на альтернативные методы, у которых более хорошие показатели времени выполнения. Если вы сами пишете адаптивный алгоритм, то можно применить один из меха- низмов, который состоит в использовании пары функций get_temporary_buffer и return_tempoга ry_buf fег. Как видно из самих названий, get_tempoга ry_buf fег выде- ляет неинициализированный блок памяти, a retu rn_tempora ry_buf fer освобождает его. Не указано, какой метод использует get_temporary_buf fer: функцию malloc, оператор new или иной. Эти функции должны применяться только для временной памяти. Если функция выделяет память с помощью get_temporary_buf fer, то впоследствии она должна осво- бодить эту память с помощью return_temporary_buffer. К сожалению, get_temporary_buffer и return_temporary_buffer не так полезны, как МоЖет показаться. Использование этих функций — сложный процесс. Память, кото- рую вы получаете от get_temporary_buffer, не инициализирована, то есть ее необхо- димо инициализировать при помощи uninitialized-fill (или какой-либо подобной Функции). Аналогично, требуется сначала уничтожить все объекты в буфере, прежде использовать return_temporary_buffer для его освобождения. В силу такой многоступенчатой процедуры довольно сложно писать безопас- ки в смысле исключительных ситуаций код с помощью get_temporary_buf fer return_temporary_buf fer. Если вы пишете адаптивный алгоритм и при этом важна ^опасность кода, то вместо этих функций следует использовать класс временного
206 Глава 10. Основные компоненты буфера. Конструктор должен выделить буфер при помощи get_temporary и инициализировать в нем элементы, а деструктор должен уничтожить элемецТь и освободить буфер. 10.5.1. get_temporary_buffer template <class Т> pair<T*, ptrdiff_t> get_temporary_buffer(ptrdiff_t len); Функция get_temporary_buffer выделяет временную память. Аргумент len — ЭТо запрашиваемая величина буфера. Например, get_temporary_buffer<T>(len) запращи. вает буфер, выровненный в памяти для объектов типа Т и имеющий достаточный размер для хранения len объектов типа Т. Такое обращение к функции необычно: параметр шаблона Т должен быть представ- лен в явном виде, потому что его нельзя угадать по аргументу get_temporary_buffer. Это называется явной спецификацией (explicit specification) аргументов шаблона функ- ции и представляет собой относительно новую возможность языка. Сейчас, в 1998 году, не все компиляторы поддерживают ее. Если ваш компилятор до сих пор не поддерживает явную спецификацию, вы можете использовать версию get„tempoгаry_buf fer из реализации HP STL, которая больше не является частью стандарта, но обычно предоставляется для обратной совместимости. Эта версия имеет два аргумента, один из них — фиктивный аргумент типа Т*, исполь- зуемый для выяснения типа параметра шаблона. Чтобы запросить буфер, который мо- жет содержать len объектов типа Т, можно написать get_tempo га ry_buf f е г(len, (Т*) 0). В любом случае аргумент len — это просьба, а не требование. Выделяемый буфер может оказаться меньше. Задача состоит в том, чтобы get_tempora ry_buf fer возвращал настолько большой буфер, насколько возможно без ущерба для производительности. Возвращаемое значение — это пара Р, первая компонента которой содержит указа- тель на выделенную память, а вторая — размер буфера. Буфер, на который указывает Р f i rst, может содержать Р. second объектов типа Т. Одно из постусловий состоит в том, что 0 < Р. second < len. Если Р. second равно нулю, то буфер вообще не выделен. В этом случае Р. f i rst — нулевой указатель. Заметим, что Р. first — указатель на неинициализированную память, а не на ре- альные объекты типа Т. Вы можете создать объекты в буфере, используя алгоритмы uninitialized_copy, uninitialized-fill или какой-нибудь аналогичный способ. Где определено В реализации HP get_temporary„buffer был объявлен в заголовочном файле ctempbuf h>- который включен в <algo. h>. В соответствии со стандартом C++ он объявлен в загс* ловочном файле <memory>. Предусловия • len больше нуля. Пример int main() { pair<int*. ptrdiff_t> Р = get_temporary„buffer<int>(10000). int* but - P first,
10.5. Временные буферы 207 ptrdirf_t N = Р second, uninitialized_fill_n(buf, N. 42), int* result = find_if(buf. but + N, bind2nd(not_equal_to<int>(), 42)), assert(result == buf + N); return_temporary_buffer(buf); } 10.5.2. return_temporary_buffer te9plate <class T> void return_temporary_buffer(T* p); функция retu rn_ tempo ra ry_buffe г используется для освобождения памяти, выделен- ной с помощью get_tempo га ry_buf f е г. Где определено В реализации HP return_temporary_buffer был объявлен в заголовочном файле <tempbuf. h>, который включен в <algo. h>. В соответствии со стандартом C++ он объяв- лен в заголовочном файле <memory>. Предусловия • Аргумент р — это указатель на блок памяти, который выделен с помощью get_tempoгаry_buffег(pt rdiff_t, T*). • Все объекты, которые были созданы в этом блоке памяти, уничтожены перед вызовом return-tempo га ry_buf fer.
Неизменяющие алгоритмы В этой главе описаны основные алгоритмы STL, работающие на диапазонах итерато- ров, не изменяя элементы, на которые эти итераторы указывают. Алгоритмы возвра- щают некоторую информацию об элементах в заданном диапазоне. Другими слова- ми, они служат для получения информации, а не модификации данных. 11.1. Линейный поиск 11.1.1. find (поиск) template<class Inputiterator, class EqualityComparable> Inputiterator find(lnputlterator first, Inputiterator last, const EqualityComparable& value); Алгоритм find выполняет линейный поиск (linear search) определенного значе- ния в диапазоне итераторов, то есть он возвращает первый итератор i в диапазоне [first, last), для которого *1 == value. Если такого значения не существует, возвраща- ется last. Линейный поиск (или, если использовать терминологию Кнута [Knu98b], после- довательный поиск) представляет собой непосредственный способ найти определен- ное значение в диапазоне. Процедура должна проверить первый элемент на равен ство заданному значению, при равенстве — остановиться, в противном случае — пе рейти к следующему элементу и повторять до тех пор, пока не будут исчерпаны все элементы. Иногда линейный поиск пишется несколько иначе. Если поместить в конец огра ничивающий элемент, равный value (такой элемент называется “флажковым , 110 английски — sentinel), то гарантировано, что поиск будет закончен всегда и моЖ опустить проверку на конец диапазона. Однако этот вариант не годится для полн0^ стью обобщенного алгоритма, потому что основан на предположении, что процеД) поиска может изменять входной диапазон. В STL такое предполагать нельзя. М0>к оказаться, что вне диапазона [fi rst, last) нет элементов или диапазон задан конста1 ными итераторами. Версия find в STL не использует технику флажкового элеМей '
11.1. Линейный поиск 209 Где определено р реализации HP find был объявлен в заголовочном файле <algo. h>. В соответствии с0стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • EqualityCompa rable является моделью =Сравнимого. • Input Iterate г является моделью Итератора Ввода. • Между объектами типа EqualityCompacable и объектами типа значения Input Iterate г определено отношение равенства. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Выполняется не более last - f i rst сравнений на равенство. Пример int main() { list<int> L, L.push_back(3), L.push_back(i), L.push_back(7), list<int> iterator result = find(L begin(), L end(). 7), assert(result == L end() || *result == 7), И.1.2. find_if (поиск по условию) teipiate<class Inputiterator, class Predicate> ^putlterator find_if(Inputiterator first, Inputiterator last, Predicate pred); Алгоритм find_if, как и find (c. 208), выполняет линейный поиск в диапазоне, задан- ии итераторами. Различие в том, что алгоритм find_if более общий. Вместо поиска конкретного значения он ищет элемент, удовлетворяющий некоторому условию. Воз- вРащаемое значение — первый итератор i в диапазоне [f i rst, last), для которого вы- ражение р red (*1) является истинным. Если такой не найден, возвращается значение Отметим, что f ind_if можно использовать для поиска первого элемента, равного Нкретному значению. Проверять, равен ли его аргумент данному значению, может Ункциональный объект pred. find выполняет ту же операцию проще, но в данном интересно увеличение общности. on ^ествУет очень распространенная операция поиска элемента, который имеет Ределенный ключ (например, поиск человека по фамилии или поиск процесса опе- Ионной системы по его идентификатору). Многие книги об алгоритмах, включая ИГУ Кнута [Knu98b], определяют поиск как нахождение неизвестного элемента
210 Глава 11. Неизменяющие алгоритмы с известным ключом. Алгоритм find для этого использовать невозможно, поскодь^ он умеет выполнять только проверку на равенство объекта, но легко использоват^ f ind_if. Функциональный объект может извлекать ключ аргумента и проверять еГо на равенство заданному значению. Алгоритмы find и find.if настолько похожи, что возникает вопрос: нужны ли 0Ии оба? Почему бы не сделать две перегруженные версии find? Причина чисто техничен кая. У обоих алгоритмов одинаковое число аргументов, и в обоих случаях тип третье» го аргумента — параметр шаблона. При наличии одного имени к ним невозможно быд0 бы обратиться. Компилятор не смог бы определить, который алгоритм имеется в виду Где определено В реализации HP find_if был объявлен в заголовочном файле <algo. h>. В соответ- ствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Predicate является моделью Предиката. • Inputiterator является моделью Итератора Ввода. • Тип значения Inputiterator можно преобразовать к типу аргумента Predicate. Предусловия • [fi rst, last) — допустимый диапазон. • Для каждого итератора 1 в диапазоне [f i rst, last) значение * i находится в обла- сти определения Predicate. Оценка сложности Линейная. Выполняется не более last - first применений pred. Пример Найти первый нуль в массиве целых чисел. Здесь find_if используется только как менее удобная версия find. int main() { mt A[] = {4, 1, 0, 3, 2, 0, 6}; const int N = sizeof(A) / sizeof(int); int* p = find_if(A, A+N, bind2nd(equal_to<int>(), 0)), cout « "Index of first zero = “ « p - A « endl; } Найти первый элемент, ключ которого равен 2. В этом примере элементами являются пары, а первая компонента пары интерпретируется как ключ. В более общем случае ключом может быть поле структуры данных. При этом требуется только функШ10' нальный объект для извлечения поля. int main() { typedef paircint, char*> Pair,
11.1. Линейный поиск 211 vector<Pair> V, V.push_back(Pair(3, y.push_back(Pair(7, V. push_back(Pair(2, V.push_back(Pair(O, V.push_back(Pair(6. "A”)). ’C")). ”E")), vector<Pair> iterator p = find_if(V begin(), V end(), composel(bind2nd(equal_to<int>(), 2), select1st<Pair>())), cout « "Found « ”<” « (*p) first « « (*p) second « ">" « endl, } Найти первый положительный элемент в списке целых значений. Этот пример пока- зывает f ind_if в его наиболее общем виде. Здесь ищется не конкретное значение и даже не элемент с конкретным ключом. int main() { list<int> L, L. push_back(-3); L.push_back(O), L.push_back(3), L.push_back(-2), L.push_back(7); list<int> -iterator result = find.if(L.begin(), L.endO, bind2nd(greater<int>(), 0)); assert(result == L end() || *result > 0); } 4.1.3. adjacent_find (поиск соседних) D template <class Forwardlterator> Forwarditerator adjacent_find(Forwarditerator first, Forwarditerator last); 2) template <class Forwarditerator, class BinaryPredicate> ForwardIterator adjacent_find(Forwarditerator first, Forwarditerator last, BinaryPredicate binary.pred); Алгоритм adjacent_find, как и find (c. 208), выполняет линейный поиск в диапазоне, 3аДаннОхМ итераторами. Различие между этими алгоритмами состоит в том, что find иШет один элемент, a adjacent_find ищет два соседних элемента. Есть две версии Adjacent_find. Версия 1 ищет два соседних элемента, которые равны между собой. °еРсия 2 — более общая. Она ищет два элемента, которые удовлетворяют некоторо- му Условию, заданному Бинарным Предикатом.
212 Глава 11. Неизменяющие алгоритмы Версия 1 adjaccnt_find возвращает первый итератор 1, для которого i mi+r пустимые итераторы в [f i rst, last), a *i == *( 1+1). Если таких итераторов нет, то вОз вращается last. Версия 2 возвращает первый итератор 1, для которого 1 и 1+1 — допустимые итера торы в [first, last), abinary_pred(*i, *(i+1)) равно true. Если таких итераторов нет- то также возвращается last. Где определено В реализации HP алгоритм adj acent_f ind был объявлен в заголовочном файле <algo В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator является моделью Однонаправленного Итератора. • Тип значения Forwarditerator является =Сравнимым. Версия 2: • Forwarditerator является моделью Однонаправленного Итератора. • Тип значения Forwarditerator может быть преобразован к типу первого и второ- го аргументов Bi ла ryPredicate. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности Линейная по отношению к количеству элементов. Конкретно, если диапазон [first, last) непустой, то adjacent_find выполняет не более last - first - 1 сравне- ний. Если же диапазон [f i rst, last) пуст, то сравнения не выполняются. Пример Найти первый повторяющийся элемент. Используется версия 1: ir-t main() { mt А[] = {1, 2, 3 3, 4. 5}. const int N - sizeof(A) / sizeof(int), const int* p - adjacent_find(A, A + N), if (p ’= A + N) coot << "Duplicate element " « *p « endl; } Найти первый элемент, который больше, чем следующий за ним, то есть первое ме' сто в диапазоне, где нарушается упорядоченность по возрастанию. Использует^ версия 2.
11.1. Линейный поиск 213 int main О { int А[] = {1, 2, 3, 4, 6, 5, 7, 8}. const int N - sizeof(A) / sizeof(int), const int* p = ad]acent_find(A, A + N. greater<inr>()), if (p == A + N) cout « "The range is sorted in ascending order « endl, else cout « "Element « p - A « " is out of order « *p « ” > « *(p + 1) « " " « endl, 11.1.4. find_first_of (поиск первого из) 1 ) template <class Inputiterator, class Forwardlterator> Inputiterator find_first_of(Inputiterator firstl, Inputiterator lastl, Forwarditerator first2, Forwarditerator last2); 2 ) template <class Inputiterator, class Forwarditerator, class BinaryPredicate> Inputiterator find_first_of(Inputiterator firstl, Inputiterator lastl, ForwardIterator first2, Forwarditerator last2, Binarypredicate comp); Алгоритм find_fi rst_of похож на find (c. 208). Он выполняет линейный поиск в диа- пазоне Итераторов Ввода. Отличие состоит в том, что если find ищет конкретное зна- чение, то f ind_f i rst„of ищет одно из нескольких значений. Конкретно, f ind_f i rst_of ищет первое появление любого элемента из [f i rst2, last2) в диапазоне [firstl, lastl). Если find похож на st rch г из стандартной библиотеки Си, то find_fi rst_of похож Hastrpbrk. Две версии find_first_of различаются способом сравнения элементов на равен- ство. Версия 1 использует operator”, а версия 2 использует функциональный объект comp, задаваемый пользователем. Версия 1 возвращает первый итератор i в диапазоне I irstl, lastl), такой что для некоторого итератора j из диапазона [first2, last2) вер- но условие *i == *j. Версия 2 возвращает первый итератор i в [firstl, lastl), такой Чт° Для некоторого итератора j из диапазона [first2, last2) выражение comp(*i, *j) истинно. Как обычно, обе версии возвращают lastl, если указанных итераторов i не суще- с^Ует. Следовательно, если либо [f i rst1, lastl), либо [f i rst2, last2) является пустым апазоном, то find_first_of обязательно возвратит lastl. определено еализацпя HP не содержит find.first_of. В соответствии со стандартом С++ ncLfirst_of объявлен в заголовочном файле <algorithm>.
214 Глава 11. Неизменяющие алгоритмы Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода.’) • Forwarditerator — модель Однонаправленного Итератора. • Тип значения Inputiterator является =Сравнимым и может быть сравним равенство с типом значений Forwarditerator. Версия 2: • Inputiterator — модель Итератора Ввода. • Forwarditerator — модель Однонаправленного Итератора. • BinaryPredicate — модель Бинарного Предиката. • Тип значения Inputiterator можно преобразовать к типу первого аргумента BinaryPredicate. • тип значения Forwarditerator можно преобразовать к типу второго аргумента BinaryPredicate. Предусловия • [first*!, last*!) — допустимый диапазон. • [f i rst2, last2) — допустимый диапазон. Оценка сложности Выполняется не более (last*! - f i rst 1) x (last2 - first2) сравнений. Пример Как и в случае st rpbrk, одно из применений f ind_f i rst_of — поиск пробельных сим- волов в строке. Пробел, знак табуляции и переход на новую строку суть пробельные символы. int main() { const char* WS = " \t\n"; const int n_WS = strlen(WS); char* s1 = ’’This sentence contains five words."; char* s2 = "OneWord"; char* end1 = find_first_of(s1, s1 + strlen(sl), WS, WS + n_WS); char* end2 = find_first_of(s2, s2 + strlen(s2), WS, WS + n_WS); printf("First word of s1: %.*s\n", end1 - s1, s1); printf("First word of s2: %.*s\n", end2 - s2, s2); } ) В соответствии co стандартом C++ оба типа итераторов должны быть моделями Однонаправленног° Итератора, но это ограничение не является обязательным. Можно реализовать find_first_of так, что п”1 второго итератора будет моделью Однонаправленного Итератора, а тин первого — моделью любого Итера тора Ввода.
11.2. Сравнение подпоследовательностей 215 0.2. Сравнение подпоследовательностей ll 2.1- search (поиск вхождения диапазона) 1) template <class Forwardlteratorl, class Forwardlterator2> Forwardlteratorl search(Forwardlteratorl firstl, Forwardlteratorl lastl, Forwardlterator2 first2, Forwardlterator2 last2); 2) template <class Forwardlteratorl, class Forwardlterator2, class BlnaryPredicate> Forwardlteratorl search(Forwardlteratorl firstl, Forwardlteratorl lastl, Forwardlterator2 first2, Forwardlterator2 last2, BinaryPredicate binary_pred); Алгоритм search похож на find (c. 208) и findJLf (c. 209) тем, что выполняет поиск в заданном диапазоне элементов. Однако если find и find_if пытаются найти один элемент, то search ищет целую последовательность элементов: диапазон [first2, last2) внутри [f i rst 1, lastl). To есть sea rch ищет подпоследовательность диапазона [f i rst 1, lastl), которая идентична диапазону [first2, last2) при поэлементном сравнении. Онвозвращает итератор, указывающий на начало подпоследовательности, или lastl, если такой подпоследовательности не существует. Версия 1 возвращает первый итератор 1 в диапазоне [firstl, lastl - (last2- first2)), такой что для каждого итератора j в диапазоне [f i rst2, last2) справедливо * (i + (j - first2)) == * j. Это условие только выглядит сложным; оно означает, что каждый эле- мент в поддиапазоне, начинающемся с 1, должен быть равен соответствующему эле- менту в [fi rst2, last2). Диапазон задается как [firstl, lastl - (last2 - first2)), а не просто |first 1, lastl), потому что мы ищем поддиапазон, равный целому диапазону [first2, last2). Под- диапазон не может быть длиннее, чем целый диапазон, и следовательно, итератор i не может быть началом такой подпоследовательности, если не соблюдается условие lastl - i > last2 - first2. Можно вызвать search с такими аргументами, что lastl - firstl < last2 - first2, но этот поиск будет всегда давать отрицательный результат. Версия 2 очень похожа на версию 1, причем разница заключается в способе, ис- пользуемом для определения идентичности элементов. Версия 1 применяет operate г==, авеРсия 2 — функциональный объект binary_pred, заданный пользователем. Версия 2 в°звращает первый итератор 1 в диапазоне [firstl, lastl - (last2 - first 2)), такой что каждого итератора j в диапазоне [first2, last2) выражение binary_pred( *(i + (j - lrst2)), ) истинно. определено Реализации HP search был объявлен в заголовочном файле <algo h>. В соответ- со стандартом C++ он объявлен в заголовочном файле <algorithm>. ^бования к типам ВеРсия 1: Forwardlteratorl — модель Однонаправленного Итератора.
216 Глава 11. Неизменяющие алгоритмы • ForwaгаIterator? — модель Однонаправленного Итератора. • Тип значения Forwardlteratorl является моделью =Сравнимого. • Тип значения Fo rwa rd Iterator? является моделью Сравнимого. • Объекты типа значения Forwardlteratorl сравнимы на равенство с объекта^ типа значения Forwardlterator2. Версия 2: • Forwardlteratorl — модель Однонаправленного Итератора. • Forwarditerator? — модель Однонаправленного Итератора. • BinaryPredicate — модель Бинарного Предиката. • Тип значений Forwardlteratorl можно преобразовать к типу первого аргумента BinaryPredicate. • Тип значений Forwarditerator? можно преобразовать к типу второго аргумента BinaryPredicate. Предусловия • [first!, last 1) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. Оценка сложности В худшем случае сложность линейно зависит как от длины текста, так и от длины образца, который надо отыскать (search выполняет не более (last! - firstl) х (last2 - first?) сравнений), но такой вариант маловероятен. В среднем сложность линейно зависит от last! - firstl.^ Пример Найти подстроку в строке символов. Это одно из основных применений search. int main() { const char S1[] = "Hello, world'". const char S2[] - "world"; const int N1 = strlen(S1), const int N2 = strlen(S2), const char* p = search(S1, S1 + N1, 82. S2 + N2), if (p ' = 81 + N1) printf("Found substring \”%s\" at character %d of string \'%s\" \n". 82. p - SI. S1). else printf(’Couldn’t find substring \n"), } Имеются алгоритмы с трудоемкостью поиска, линейной по суммарной длине диапазонов — ПРи меч ред
11.2. Сравнение подпоследовательностей 217 подпоследовательность из трех чисел, последние цифры которых 1,2 и 3 соот- ветственно. template <class Integeo struct congruent { congruent(Integer mod) N(mod) {} bool operator()(Integer a, Integer b) const { return (a - b) % N == 0, } Integer N, }; int main() { int A[10] = (23. 46. 81. 2, 43. 19. 14. 98. 72. 51}. int digits[3] = {1, 2. 3}; int* seq = search(A, A + 10. digits, digits + 3. congruent<int>(10)). if (seq ’= A + 10) { cout « "Subsequence: ’ copy(seq, seq + 3, ostream_iterator<int>(cout. ” ")), cout « endl. I else cout « "Subsequence not found" « endl. } Результат: Subsequence 81 2 43 П.2.2. find. .end (поиск последнего вхождения диапазона) О template <class Forwardlteratorl, class Forwardlterator2> Forwardlteratorl find_end(Forwardlteratorl firstl, Forwardlteratorl lastl, ForwardIterator2 first2, Forwardlterator2 last2); 2) template <class Forwardlteratorl, class Forwardlterator2, class BinaryPredicate> Forwardlteratorl Hnd_end(Forwardlteratorl firstl, Forwardlteratorl lastl, Forwardlterator2 first2, Forwardlterator2 last2, BinaryPredicate comp); ^Лгоритм find_end назван несколько неудачно. Он гораздо больше похож на search *с* 215), чем на find, и точнее было бы назвать его search_end. Как и search, find_end ищет подпоследовательность внутри диапазона [firstl, lastl), °торая идентична диапазону [first2, last2). Разница в том, что если search ищет
218 Глава 11. Неизменяющие алгоритмы первую такую подпоследовательность, то f ind_end ищет последнюю. Он возвращу, итератор, указывающий на начало подпоследовательности, или lastl, если такой ц0;ь последовательности нет. Версия 1 возвращает последний итератор i в диапазоне [firstl, lastl - (lasta^ f i rst2)), такой что для каждого итератора j в диапазоне [f i rst2, last2) верно * (i + (к f i rst2)) == * j. Это условие только выглядит сложным; оно означает, что каждый эле- мент поддиапазона, начинающегося с 1, должен быть идентичен соответствующему элементу в [first2, last2). Как и в случае с search, причина того, что рассматриваемым диапазоном является [f 1 rstl, last 1 - (last2 - f i rst2)), а не [f i rst 1, lastl), состоит в том, что ищется подди. апазон, равный целому диапазону [f i rst2, last2). Поддиапазон не может быть длин- нее целого диапазона, следовательно, итератор 1 не может быть началом такой под- последовательности, если не соблюдается условие lastl - i >last2 - first2. Можно вызвать find_end с такими аргументами, что lastl - f i rstl < last2 - f i rst2, но возвра- щаемое значение в этом случае всегда будет lastl. Версия 2 очень похожа на версию 1, за исключением способа определения иден- тичности элементов. Версия 1 использует operator==, а версия 2 — функциональный объект binary_pred, задаваемый пользователем. Версия 2 возвращает последний ите- ратор 1 в диапазоне [firstl, lastl - (last2 - f i rst2)), такой что для любого итератора j в [first2, last2) выражение binary_pred(*(i + (j - first2)), *j) истинно. Где определено Алгоритм find_end не входит в реализацию HP. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwardlteratorl — модель Однонаправленного Итератора. • Forwardlterator2 — модель Однонаправленного Итератора. • Тип значения Forwardlteratorl является =Сравнимым. • Тип значения Forwardlterator2 является =Сравнимым. • Объекты типа значения Forwardlteratorl сравнимы на равенство с объектами типа значения Fo rwa rd Ite rat о r2. Версия 2: • Forwardlteratorl — модель Однонаправленного Итератора. • Forwardlterator2 — модель Однонаправленного Итератора. • BinaryPredicate — модель Бинарного Предиката. • Тип значения Forwardlteratorl можно преобразовать к типу первого аргумент3 BinaryPredicate. • Тип значения Forwardlterator2 можно преобразовать к типу второго аргумент3 BinaryPredicate. Предусловия • [fi rstl, lastl) — допустимый диапазон. • [first2, last2) — допустимый диапазон.
11.2. Сравнение подпоследовательностей 219 Оценка сложности дичество сравнений пропорционально (last 1 - firstl) х (last2 - first2). Если ForwardIterator1,H F° rwardlterat° р2 являются моделями Двунаправленного Итера- т0 сРедняя сложность линейна; в худшем случае выполняется (last 1 - f i rst 1) x (}ast2 - fi rst2) сравнений. Пример int main() { char* s = "executable exe”. char* suffix = "exe”, const int N = strlen(s). const int N_suf = strlen(suffix), char* location = find_end(s, s + N, suffix, suffix + N_suf), if (location '= s + N) { cout « "Found a match for " « suffix « " within ” « s « endl, cout « s « endl; int i; for (1=0, i < (location - s), ++i) cout « ; for (i = 0, i < N_suf, ++i) cout « cout « endl, } else cout « "No match for " « suffix « " within " « s « endl, 4*2.3. search_n (поиск n последовательных элементов) 0 template <class Forwarditerator, class Integer, class T> Forwarditerator search_n(ForwardIterator first, Forwarditerator last, Integer count, const T& value); 2) ) template <class Forwarditerator, class Integer, class T, class BinaryPredicate> Forwarditerator search_n(Forwarditerator first, Forwarditerator last, Integer count, const T& value, BinaryPredicate binary_pred); ^Оритм search_n ищет в диапазоне [first, last) подпоследовательность из count ЛеДовательно расположенных элементов, каждый из которых равен value. Он
220 Глава 11. Неизменяющис алгоритмы возвращает итератор, указывающий на начало первой такой подпоследовательности или last, если такой подпоследовательности не существует. Заметим, что count может быть нулем. Подпоследовательность из нуля элементу вполне определена. Неважно, чему равно value: любой диапазон содержит поддцаПа зон из нуля элементов, которые равны value. Когда search_n вызывается с count, рав, ным нулю, поиск всегда успешен и возвращаемое значение всегда f i rst. Две версии sea rch_n различаются способом определения идентичности элементов Версия 1 использует operator^, а версия 2 — функциональный объект binary заданный пользователем. Версия 1 возвращает первый итератор i в диапазоне [first, last - count), такой что для каждого итератора j в диапазоне [i, i + count) выполняется условие *j == vaiue Версия 2 возвращает первый итератор i в диапазоне [f i rst, last - count), такой что для каждого итератора j в диапазоне [i, i + count) выражение binary_pred(*j, value) ис- тинно. Как и в случае с search (с. 215), речь идет о диапазоне [first, last - count), а не о [first, last), потому что мы ищем поддиапазон длиной count. Поддиапазон не мо- жет быть длиннее, чем целый диапазон, следовательно, итератор 1 не может быть на- чалом такой подпоследовательности, если не соблюдается условие last - i > count Можно вызвать search_n с такими аргументами, что last - f i rst < count, но такой по- иск всегда будет давать отрицательный результат. Где определено Алгоритм search_n не входит в реализацию HP. В соответствии £о стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • Integer — целочисленный тип. • Т — модель =Сравнимого. • Тип значения Forwarditerator является моделью Сравнимого. • Объекты типа значения Forwarditerator сравнимы на равенство с объектами типаТ. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • Integer — целочисленный тип. • Т — модель =Сравнимого. • BinaryPredicate — модель Бинарного Предиката. • Тип значений ForwardIteratoг можно преобразовать к типу первого аргумента BinaryPredicate. • Т можно преобразовать к типу второго аргумента Binarypredicate. Предусловия • [fi rst, last) — допустимый диапазон. • count >0.
11.2. Сравнение подпоследовательностей 221 Оценка сложности ди11ейная: sea rch_n выполняет не более last - f i rst сравнений. пример bool eq_nosign(int x, int y) { return abs(x) == abs(y), } void lookup(int* first, int* last. size_t count, int val) { cout « "Searching for a sequence of « count « ' " « val « ........ « (count '= 1 9 s...............), int* result = search_n(first, last, count, val), if (result == last) cout « "Not found" « endl, else cout « "Index = ” « result - first « endl, } void lookup_nosign(int* first, int* last, size_t count, int val) { cout « "Searching for a (sign-insensitive) sequence of « count « ............. « val « ....... « (count ’ = 1 9 "s‘.............): int* result = search_n(first, last, count, val, eq_nosign), if (result == last) cout « "Not found” « endl, else cout « "Index = ” « result - first « endl, ) int main() { const mt N = 10, int A[N] = {1, 2, 1, 1, 3, -3, 1, 1, 1, 1}; lookup(A. A + N, 1, 4), lookup(A. A + N, 0, 4); 100kup(A. A + N, 1, 1), lookup(A. A + N, 2, 1), lookup(A. A + N, 3, 1), lookup(A. A + N, 4, 1), lookup(A, A + N. 1. 3), iookup(A. A + N. 2, 3), нИе уТандаРт C++ дас1 верхнюю границу в count х (last - first) сравнений, но это ненужное послабле- hst\ ВзиРая назначение count,для search_n iiei причин проверять каждый элсмет в диапазоне |first. ) более одного раза
222 Глава 11. Неизменяющие алгоритмы ]ookup_nosign(A, A+N, 1 3), ]ookup_nosign(A, A+N, 2, 3); Результат: Searching for a sequence of 1 '4' Searching for a sequence of 0 ’4‘s Searching for a sequence of 1 ’1’ Searching for a sequence of 2 ’1’s Searching for a sequence of 3 ‘1’s Searching for a sequence of 4 ’1’s Searching for a sequence of 1 3’ Searching for a sequence of 2 ’3's Searching for a (sign-insensitive) Searching for a (sign-insensitive) Not found Index = 0 Index = 0 Index = 2 Index = 6 Index = 6 Index = 4 Not found sequence of 1 ’3’ Index = 4 sequence of 2 "3's Index = 4 11.3. Подсчет элементов 11.3.1. count (подсчет) 1) template <class Inputiterator, class EqualityComparable> typename iterator_traits<lnputlterator>::difference_type count(lnputlterator first, Inputiterator last, const EqualityComparable& value); 2) template <class Inputiterator, class EqualityComparable, class Size> void count(Inputiterator first, Inputiterator last, const EqualityComparable& value, Size& n); Алгоритм count подсчитывает количество элементов диапазона [first, last), кото- рые равны value. Обе версии count подсчитывают количество элементов, равных value, используя для этого разные интерфейсы. Версия 2 была определена в первоначальной реализа- ции HP STL [SL95], но в процессе стандартизации интерфейс был изменен. Стандарт C++ [ISO98] содержит только версию 1. В настоящее время (1998 год) одни реализа- ции STL содержат только версию 1, другие — только версию 2, а третьи — обе версии. Версия 1 возвращает количество элементов в диапазоне [f i rst, last), равных value- У версии 2 более сложный интерфейс. Она добавляет к п количество итераторов i в диапазоне [f i rst, last), таких что *i == value. Интерфейс версии 2 очень неудобен. Если необходимо подсчитать количество эле- ментов, которые равны некоторому значению, вы должны создать локальную пере менную, инициализировать ее нулем, а затем вызывать count, что неудобно и подвер' жено ошибкам (можно забыть проинициализировать локальную переменную началь- ным значением 0). Поэтому интерфейс был изменен на используемый в версии 1- Как и в случае с distance (с. 190), в оригинальной версии STL использовался такой неудобный интерфейс, потому что версия HP STL не поддерживал^ ite rato r__t raits (раздел 3.1).
11.3. Подсчет элементов 223 рде опРеделено -реализации HP count был объявлен в заголовке <algo. h>. В соответствии со стан- C++ он объявлен в заголовочном файле <algonthm>. к типам Требования Персия 1- • Inputiterator — модель Итератора Ввода. • EqualityComparable — модель =Сравнимого. • Тип значения Inputiterator является моделью ^Сравнимого. • Объекты типа значения Inputiterator должны быть сравнимы на равенство с объектами типа значения EqualityComparable. Версия 2: • Inputiterator — модель Итератора Ввода. • EqualityComparable — модель ^Сравнимого. • Size — целочисленный тип, достаточно большой для представления неотрица- тельных значений типа расстояния Inputiterator. • Тип значения Inputiterator является моделью ^Сравнимого. • Объекты типа значения Inputiterator должны быть сравнимы на равенство собъектами типа значения EqualityComparable. Предусловия Версия 1: • [f i rst, last) — допустимый диапазон. Версия 2: • [first, last) — допустимый диапазон. • п плюс количество элементов, равных value, не должно превосходить максималь- ного значения типа Size. Оценка сложности Линейная. Выполняется ровно last - first сравнений. Пример Подсчитать количество нулей в диапазоне, используя старый интерфейс (версию 2). lnt main() { int А[] = {2,0. 4, 6, 0. 3, 1, -7 }, const int N = sizeof(A) / sizeof(int), lr)t result = 0, Count(A, A + N, 0, result), cout « "Number of zeros " « result « endl,
224 Глава 11. Неизменяющие алгоритмы___________________ Сделать то же самое, используя новый интерфейс (версия 1). int main() { mt А[] - { 2, 0, 4, 6, 0. 3. 1, -7 }, const int N = sizeof(A) / sizeof(int), cout « "Number of zeros « count(A, A+N, 0) « endl. } 1 1.3.2. count_if (подсчет по условию) 1) template <class Inputiterator, class Predicate> typename iterator_traits<lnputlterator>::difference_type count_if(lnputlterator first, Inputiterator last, Predicate pred); 2) template <class Inputiterator, class Predicate, class Size> void count_if(lnputlterator first, Inputiterator last, Predicate pred, Size& n); Алгоритм count_if очень похож на count (с. 222), но более общ. Вместо подсчета ко- личества элементов, равных определенному значению, count_] f подсчитывает коли- чество элементов в диапазоне [first, last), которые удовлетворяют некоторому ус- ловию. Условие определяется функциональным объектом pred, задаваемым пользо- вателем, a count.if подсчитывает количество итераторов 1 в заданном диапазоне [f i rst, last), таких 4TOpred(*i) истинно. Есть две версии интерфейса count_if. Причина их существования та же, что у count. Версия 2 была определена в первоначальной реализации HP STL [SL95], а версия 1 - в стандарте C++ [ISO98]. Одни реализации STL включают в себя только старый ин- терфейс, другие — только новый, а третьи — оба. Версия 1 возвращает количество элементов, которые удовлетворяют условию pred, ю есть возвращается количество итераторов i в диапазоне [first, last), таких что pred( *1) является истинным. Версия 2 добавляет к п количество итераторов i в диа- пазоне [first, last), таких что pred(*i) является истинным. Где определено В реализации HP count_if был объявлен в заголовке <algo. h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • Predicate — модель Предиката. • Тип значения Inputiterator можно преобразовать к типу аргумента Predicate- Версия 2: • Inputlteratoг — модель Итератора Ввода. • Predicate — модель Предиката.
11.3. Подсчет элементов 225 • Size — целочисленный тип, который может содержать значения типа дистанции Inputiterator. , Тип значения 1приШега1огможнопреобразоватьктипуаргумента Predicate. Нредусловия Версия 1: . [first, last) — допустимым диапазон. Версия 2: • [fl rst, last) — допустимый диапазон. • п плюс количество элементов, удовлетворяющих pred, не должно превышать максимального значения типа Size. Оценка сложности Линейная. Ровно last - first применений pred. Пример Подсчитать количество нулей в диапазоне, используя старый интерфейс (версию 2). На самом деле, нет оснований привлекать для этого count.if, так как count делает то же самое, но более удобно. Данный пример лишь иллюстрирует, что count — специ- альный случай count_if. int main() { int А[] = {2.0, 4, 6, 0, 3, 1, -7 }, const int N = sizeof(A) / sizeof(int), int result = 0, count_if(A. A + N, bind2nd(equal_to<int>(), 0), result), cout «‘"Number of zeros " « result « endl, Сделать то же самое, используя новый интерфейс (версию 1). lnt main() { int А[] = { 2, 0, 4, 6, 0. 3 1, -7 }, const int N = sizeof(A) / sizeof(int), cout « "Number of zeros << « count_if(A, A + N, bind2nd(equa]_to<int>(), 0)) « endl, И B слУчае c f(c. 209), подсчет количества элементов, равных некоторому имеет интересное обобщение: подсчет количества элементов, ключ кото- Равен заданному значению. В таком примере элементы — пары. Первый компо- тВары интерпретируется как ключ.
226 Глава И. Неизменяющие алгоритмы int main() { typedef pairdnt, char*> Pair. vector<Pair> V, V push_back(Pair(3, "A")), V push_back(Pair(7, ”6")), V push_back(Pair(2, "C”)), V push_back(Pair(3, "D")), V push_back(Pair(O, ”E”)). V push_back(Pair(6, "E”)), cout « "Number of elements with key equal to 3 « count_if(V begin(), V end(), composel(bind2nd(equal_to<int>(), 3), selectlst<Pair>())) « endl, } Подсчитать количество четных элементов в диапазоне. int main() i { int А[] = { 2, О, 4, 6, 0, 3, 1. -7 }; const int N = sizeof(A) / sizeof(int), cout « "Number of even elements1 « count_if(A, A + N, composel(bind2nd(equal_to<int>(), 0), bind2nd(modulus<int>(), 2))) « endl, } 11.4. for_each (выполнение для каждого) template <class Inputiterator, class UnaryFunction> UnaryFunction for_each(lnputlterator first, Inputiterator last, UnaryFunction f); Алгоритм f о r_each применяет функциональный объект f к каждому элементу диапа зона [fi rst, last), игнорируя значение, возвращаемое f (если оно есть). Применен^ функции выполняется в прямом порядке, то есть от first к last. Возвращаемое зна чение — функциональный объект после того, как он был применен к каждому менту. Возвращаемое значение часто игнорируется, так как f or_each обычно используй ся только для выполнения последовательности действий. Однако иногда возврат3 емое значение полезно. Функциональный объект — не обязательно простой указа тель на функцию: это может быть определенный пользователем класс. В частное^ функциональный объект может обладать внутренним состоянием. Это означает.4
11.4. for^each (выполнение для каждого) 227 r each не только выполняет некоторую последовательность действий, но и накап- f вает информацию. Функциональный объект мог бы, например, подсчитать, сколь- «яз состоялся его вызов, или мог бы иметь флаг состояния (status flag), чтобы по- бывать успешность вызовов. Где определено В реализации HP f or_each был объявлен в заголовке <algo. h>. В соответствии со стан- дарт01^ он объявлен в заголовочном файле <algorithm>. Требования к типам • inputiterator — модель Итератора Ввода. • UnaryFunction — модель Унарной Функции. • Тип значений Inputiterator можно преобразовать к типу аргумента UnaryFunction. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - first применений UnaryFunction. Пример Напечатать последовательность элементов, используя унарный функциональный объект, который распечатывает свой аргумент. template<class Т> struct print • public unary_function<T, void> print(ostream& out) os(out), count(O) {} void operator() (T x) { os « x « ’ ++count, } ostream& os, int count, >; int main() { int A[] = {1, 4. 2. 8, 5, 7}, const int N - sizeof(A) / sizeof(int), Pnnt<int> P ~ for_each(A. A + N, print<int>(cout)), cout « endl « P count « ” objects printed " « endl; Эт 07 пРимер не очень интересен, потому что того же результата можно достичь с по- *Цью ostream_iterator. бц5 СКОЛЬКО более сложный пример использует функцию system из стандартной Лиотеки Си, чтобы последовательно выполнить список команд операционной си- Ука111’ этом случае функциональный объект, передаваемый for_each, является ^Лтелем на функцию.
228 Глава i 1. Неизменяющие алгоритмы int main() char* commands[] = {"uptime", "pwd", 'Is”}, const int N = sizeof(commands) / sizeof(char*), for_each(commands commands + N, system), } Этот пример может быть расширен почти неограниченно. В частности, вместо непо- средственного применения функции system мы могли бы использовать функциональ- ный объект, проверяющий значение, возвращаемое функцией system, чтобы опреде- лить, было ли выполнение успешным. Говоря более претенциозно, мы могли бы ис- пользовать функциональный объект, который инкапсулирует отличия между различными операционными системами. 11.5. Сравнение двух диапазонов 1 1.5.1. equal (сравнение на равенство) 1) template <class Inputlteratorl, class Inputlterator2> bool equal(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2); 2) template <class Inputlteratorl, class Inputlterator2, class BinaryPredicate> bool equal(lnputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, BinaryPredicate binary.pred); Алгоритм equal возвращает true, если диапазоны [firstl, lastl) и [first2, first2 + (lastl - firstl)) идентичны при поэлементном сравнении. В противном случае воз- вращается false. Две версии equal различаются тем, что первая сравнивает элементы с помошыо operator—, а вторая использует функциональный объект binary_pred, задаваемый пользователем. Версия 1 возвращает истинность того, что для каждого итератора 1 в диапазоне [f 1 rstl, lastl) выполняется условие *i == *(first2 + (i - firstl)). Версия 2 возвраша' ст истинность того, что для каждого итератора i в диапазоне [firstl, lastl) условие Dinary_pred(*i *(first2 + (i - firstl)) является истинным. Где определено В реализации HP equal был объявлен в заголовке <algo h> и определен в <algobase В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • InputIterator2 — модель Итератора Ввода.
11.5. Сравнение двух диапазонов 229 9 Тип значений Inputlteratorl является моделью =Сравнимого. • Тип значений Inputiterator? является моделью ^Сравнимого. • Объекты типа значения Inputlteratorl сравнимы на равенство с объектами типа значения Inputiterator?. рерсия 2: • inputlteratorl — модель Итератора Ввода. • Inputiterator? — модель Итератора Ввода. • BinaryPredicate — модель Бинарного Предиката. • Тип значений Inputlteratorl можно преобразовать к типу первого аргумента BinaryPredicate. • Тип значений Inputiterator? можно преобразовать к типу второго аргумента BinaryPredicate. Предусловия • [firstl, lastl) — допустимый диапазон. • [first?, first? + (last? - lastl)) — допустимый диапазон. Оценка сложности Линейная. Выполняется не более lastl - firstl сравнений. Пример Сравнить два диапазона на равенство. int main() { int А1[] = { 3, 1, 4, 1, 5. 9, 3 }, int А2[] = { 3. 1, 4, 2, 8, 5, 7 ), const int N = sizeof(A1) / sizeof(int), if (equal(A1, Al + N, A2)) cout « "Equal” « endl, else cout « "Not equal" « endl, Сравнить две строки на равенство, игнорируя регистр букв. 1гП1пе bool eq_nocase(char cl, char с2) { return toupper(cl) == toupper(c2). *nt main() const char* s1 = "This is a Test' , const cnar* s2 = "this is a test", const int N - strlen(sl),
230 Глава 11. Неизменяющие алгоритмы if (equal(s1, s1 -» N, s2. eq.nocase)) coiit « "Equal” « endl; else cout « "Not equal" « endl: 11.5.2. mismatch (поиск различия) 1) template <class Inputlteratorl, class Inputlterator2> pair<lnputlterator1, Inputlterator2> mismatch(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2); 2) template <class Inputlteratorl, class Inputlterator2, class BinaryPredicate> pair<lnputlterator1, Inputlterator2> mismatch(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, BinaryPredicate binary_pred); Алгоритм mismatch возвращает первую позицию, где диапазоны [firstl, lastl) и ffirst2, first2 + (lastl - firstl)) различаются. Эту позицию можно представить в виде итератора в диапазоне [firstl, lastl) и соответствующего итератора в диапа- зоне [first2, first2 + (lastl - firstl)), a mismatch возвращает эту пару итераторов’*. Две версии mismatch различаются способом проверки равенства элементов. Вер- сия 1 проверяет на равенство, применяя operateг==, а версия 2 использует функцио- нальный объект ргео, заданный пользователем. Версия 1 ищет первый итератор i в [firstl, lastl), такой что *i ! = *(first2 + (i - fi rst1)). Возвращаемое значение — пара, где первый элемент — 1, а второй — f i rs 12+ (i - f i rst1). Если такой итератор i отсутствует, то возвращается пара с первым эле- ментом lastl и вторым — first2+ (lastl - firstl). Версия 2 ищет первый итератор i в диапазоне [firstl, lastl), такой что binary_pred(*i, *(first2 + (i - firstl)) ложно. Возвращаемое значение — пара, где первый элемент — i, а второй — f i rst2+ (i - f i rst 1). Если такой итератор i отсутству- ет, то возвращается пара с первым элементом lastl и вторым — first2+ (lastl -firstD- Где определено В реализации HP mismatch был объявлен в заголовочном файле <algo. h> и определен в Olgobase h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. * Заметим, что mismatch похоже на equal (с. 228). Единственное отличие заключается в том, что, два диапазона различаются, equal возвращает лишь результат логического типа, a mismatch сообщает знцию Выражение equal(f1, 11, f2) эквивалентно выражению mismatch(f 1, Ц, f2).first == 1+ ”J разумный способ реализации equal.
11.5. Сравнение двух диапазонов 231 9 jnputlterator2 — модель Итератора Ввода. , Тип значения Inputlteratorl является моделью =Сравнимого. 9 Тип значения Inputlterator2 является моделью ^Сравнимого. • Объект типа значения Inputlteratorl сравним на равенство с объектом типа зна- чения Inputlterator2. рерсия 2: • inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • BinaryPredicate — модель Бинарного Предиката. • Тип значения Inputlteratorl можно преобразовать к типу первого аргумента BinaryPredicate. • Тип значения Inputlterator2 можно преобразовать к типу второго аргумента BinaryPredicate. Предусловия • [firstl, lastl) — допустимый диапазон. • [first2, f i rst2 + (last2 - lastl)) — допустимый диапазон. Оценка сложности Линейная. Выполняется не более lastl - firstl сравнений. Пример Найти первую позицию, где две последовательности различаются. int main() { int A1[] = { 3, 1, 4, 1. 5, 9. 3 }; int A2[] = { 3. 1, 4, 2. 8, 5. 7 }; const int N = sizeof(A1) / sizeof(int), pair<int*, int*> result = mismatch(A1. A1 + N, A2), if (result first == A1 + N) cout « "The two ranges do not differ." « endl, else { cout « "First mismatch is in position " « result first - A1 « endl. cout « "Values " « *(result first) << ", « *(result.second) « endl, } I ^айт <ЩТи первую позицию, где две строки различаются, игнорируя регистр букв. lnline bool eq_nocase(char cl, char c2) { return toupper(d) == roupper(c2);
232 Глава 11. Неизменяющие алгоритмы int main() { const char* const char* const int N s1 - 'This is a Test". s2 = ’this is a test". = strlen(sl), pair<const char*, const char*> result = rnismatch(s1, s1 + N. s2, eq_nocase) if result first == s1 + N) cout « ‘The two strings do not differ" « endl, eJse { cout « "The strings differ starting at character « result first - s1 « endl, cout « "Trailing part of s1 " « result first « endl, cout « "Trailing part of s2 ” « result second « endl 11.5.3. lexicographical_compare (лексикографическое сравнение) 1) template <class Inputlteratorl, class Inputlterator2> bool lexicographical_compare(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2); 2) template <class Inputlteratorl, class Inputlterator2, class BinaryPredicate> bool lexicographical_compare(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, BinaryPredicate comp); Алгоритм lexicographical_compare возвращает истинность того, что диапазон [firstl lastl) лексикографически меньше, чем диапазон [first2, last2). Лексикографическое сравнение (lexicographical comparison) — это поэлементно? упорядочение, как в словаре”. То есть [fi rst 1, lastl) меньше, чем [fi rst2, last2), если *first1 меньше, чем *first2, и наоборот, больше, если *first1 больше, чем *first2- Если два первых элемента эквивалентны, то lexicographical_compare сравнивает два вюрых элемента, и т. д. Как и в обычном словаре, первый диапазон считается меньпк второго и в том случае, если каждый элемент в первом диапазоне равен соответств) ющему элементу во втором диапазоне, но второй содержит большее количество эЛ? ментов. Два диапазона [firstl, lastl) и [first2, last2) не обязательно имеют одинаков количество элементов. Это нетривиально, поскольку большинство алгоритмов Ь работающих с двумя диапазонами, например equal (с. 228) или сору (с. 239), требу чтобы оба диапазона были равной длины. Обычно алгоритмы задают два диапазон с помощью трех итераторов, а не четырех, как lexicographical_compare.
11.5. Сравнение двух диапазонов 233 Две версии lexicographical_compare различаются способом определения, является одИн элемент меньше другого. Версия 1 сравнивает объекты с помощью operator^ версия 2 — с помощью функционального объекта comp, заданного пользователем. Где определено йоеализации HP lexicographical_compare был объявлен в заголовке <algo. h> и опре- лей в <algobase. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Тип значений Inputlteratorl является моделью <Сравнимого. • Тип значений Inputlterator2 является моделью <Сравнимого. • Ecnnvl является значением типа значения Inputlteratorl, a v2 — значением типа значения Inputlteratoг2, то определено как vl < v2, так и v2 < v1. Версия 2: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • BinaryPredicate — модель Бинарного Предиката. • Тип значений Inputlteratorl можно преобразовать к типам первого и второго аргументов BinaryPredicate. • Тип значений Inputlterator2 можно преобразовать к типам первого и второго аргументов BinaryPredicate. Предусловия • [firstl, lastl) — допустимый диапазон. • [first2, last2) — допустимый диапазон. Оценка сложности Линейная. Выполняется не более 2min(last1 - firstl, last2 - first2) сравнений. Пример Сравнить два диапазона чисел в словарном порядке. *nt main() { int A1[] = {3 1, 4, 1, 5, 9, 3}, int A2[] = {3, 1. 4, 2, 8, 5, 7}, int A3[] = <1. 2. 3 4}, int A4[] = <1. 2, 3, 4. 5}, const int N1 = sizeof(A1) / sizeof(int), const int N2 = sizeof(A2) / sizeof(int), const int N3 = sizeof(A3) / sizeof(int),
234 Глава 11. Неизменяющие алгоритмы const int N4 = sizeof(A4) / sizeof(int), bool C12 = lexicographical_compare(Ai, A1 + Nl. A2. A2 + N2). bool C34 = lexicographical_compare(A3. A3 + N3, A4, A4 + N4), cout « ”A1[] < A2[] " « (C12 9 "true" cout « ”A3[] < A4[] " « (C34 9 ‘’true” "false”) « endl, "false") « endl, Сравнить две строки в словарном порядке, игнорируя регистр букв. inline bool lt_nocase(char cl, char c2) { return toupper(d) < toupper(c2), } int main() const char* s1 const char* s2 const char* s3 const int N1 = = "abc”: = "ABC". = "abcDEF". 3. N2 = 3. N3 = 6; printf("%s < %s . %c\n”, s1, s2, lexicographical_compare(s1, s1 + N1, s2, s2 + N2, lt_nocase) 9 ’t* ’f'), pnntf("%s < %s %c\n". s2, s3. Iexicographical_compare(s2. s2 + N2, s3. s3 + N3, lt_nocase) 9 *f } ’f). Результат: abc < ABC f ABC < abcDEF t 11.6. Минимум и максимум 11.6.1. min (определение меньшего из двух) 1) template <class LessThanComparable> const LessThanComparable& min(const LessThanComparable& a. const LessThanComparable& b); 2) template <class T, class BinaryPredicate> const T& min(const T& a. const T& b, BinaryPredicate comp); Большинство алгоритмов STL работают с диапазонами элементов. Алгоритм пир один из немногих, которые работают с единичными объектами, передаваемыми
11.6. Минимум и максимум 235 ^енты, то есть min возвращает меньший из двух аргументов. Если ни один не мень- другой, то возвращается первый аргумент. ^едве версии min различаются способом определения меньшего из элементов. Вер- 1 сравнивает объекты с помощью operateг<, а версия 2 — с помощью функцио- нального объекта comp. Где определено gреализации HP алгоритм min был объявлен в заголовке <algo.n> и определен <algobase. h>. В соответствии со стандартом C++ он объявлен в заголовочном фай- Je<algonthm>. Требования к типам Версия 1: • LessThanCompa cable — модель <Сравнимого. Версия 2: • BinaryPredicate — модель Бинарного Предиката. • Т можно преобразовать к типам первого и второго аргументов BinaryPredicate Пример int main() { const int х = min(3, 9), assert(x == 3), int a = 3, int b = 3; const int& result = min(a, b). assert(&resuIt == &a), И.6.2. max (определение большего из двух) О template <class LessThanComparable> const LessThanComparable& max(const LessThanComparable& a, const LessThanComparable& b); 21 ' template <class T, class BinaryPredicate> const T& max(const T& a, const T& b, BinaryPredicate comp); t* льшинство алгоритмов STL работают с диапазонами элементов. Алгоритм max — а н из немногих, которые работают с единичными объектами, передаваемыми как ДОУМенты, то есть max возвращает больший из двух аргументов. Если ни один не боль- шем другой, то возвращается первый аргумент. Две версии max различаются способом определения меньшего элемента. Версия 1 Р^Внивает объекты с помощью operatorc, а версия 2 — с помощью функционального ^Ъекта comp.
236 Глава 11 Неизменяющие алгоритмы Где определено В реализации HP алгоритм max был объявлен в заголовке <algo.h> и опреДс, в <algobase h>. В соответствии со стандартом C++ он определен в заголовочном ле<а1дог11Ит>. Требования к типам Версия 1: • LessThanComparable — модель <Сравнимого. Версия 2: • BinaryPredicate — модель Бинарного Предиката. • Т можно преобразовать к типам первого и второго аргументов BinaryPredicate Пример int main() { const int x = max(3, 9), assert(x -- 9), int a ~ 3. int b - 3, const int& result - max(a, b); assert(&result == &a), 11.6.3. min_element (поиск наименьшего в диапазоне) 1) template <class Forwardlterator> Forwarditerator min_element(Forwarditerator first, Forwarditerator last); 2) template <class Forwarditerator, class BinaryPredicate> Forwarditerator min_element(Forwarditerator first, Forwarditerator last, BinaryPredicate comp); Алгоритм min_element ищет наименьший элемент в диапазоне [first, last). Он воз- вращает первый итератор i в [f i rst, last), такой что нет других итераторов в диапазоне, указывающих на значение, меньшее, чем *1.’) Значение last возвращается, только если [first, last) — пустой диапазон. Две версии min element различаются способом определения меньшего элемента Версия 1 сравнивает объекты с помощью ope rato г<, а версия 2 — с помощью функШ10 пильного объекта comp. } Мы не можем говорить “итератор, указывающий на наименьший элемент в (first, last)”. hoioM}' q|( едиисIвенного наименьшего элемента можем не быть. Наименьшее значение может встречаться в диапаЗ1 нс более одного раза Например, если все элементы в (first, last) равны, то min_element будем всегда вращал» f 1 rst Эго обобщение min, коюрое возвращает первый аргумент, если они оба равны
11.6. Минимум и максимум 237 рерсия 1 возвращает первый итератор 1 в [f i rst, last), такой что для каждого итера- j в [first, last) выражение *j < *1 ложно. Версия 2 возвращает первый итератор i иапазоне [first, last), такой что для каждого итератора j в [first, last) выражение Jt’P1)™- Где определено Рреализации HP min_element был объявлен в заголовке <algo. h>. В соответствии со стаЯдартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • ForwardIteratoг — модель Однонаправленного Итератора. • Тип значения Forwarditerator является < Сравнимым Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • BinaryPredicate — модель Бинарного Предиката. • Тип значения Forwarditerator можно преобразовать к типам первого и второго аргументов BinaryPredicate. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Для непустого диапазона выполняется ровно (last - fi rst) - 1 сравнений. Пример int main() { llSt<int> L, generate_n(front_inserter(L), 1000, rand), list<int> const-iterator it = min_element(L begin(), L end()), cout « "The smallest element is " « *it « endl, } Ч-6.4. max_element (поиск наибольшего в диапазоне) О template <class Forwardlterator> Forwarditerator roax_element(Forwarditerator first, ForwardIterator last); ' template <class Forwarditerator, class BinaryPredicate> Forwarditerator max_element(Forwarditerator first, Forwarditerator last, BinaryPredicate comp); ^Лгоритм max_element ищет наибольший элемент в диапазоне [first, last). Он воз- Ращает первый итератор i в [first, last), такой что нет других итераторов в этом
238 Глава И. Неизменяющие алгоритмы диапазоне, указывающих назначения, большие, чем *i*}. Значение last возвра111а ся, только если [ f i rst, last) — пустой диапазон. ет' Две версии max_element различаются способом определения меньшего элемеат. Версия 1 сравнивает объекты с помощью operator^ а версия 2 — с помощью функци* нального объекта comp. Версия 1 возвращает первый итератор 1 в [f i rst, last), такой что для каждого Ите ратора j в [first, last) выражение *i < *j ложно. Версия 2 возвращает первый итера тор 1 в [first, last), такой что для каждого итератора j в [first, last) выражен^ comp(*i, *j) ложно. Где определено В реализации HP max_element был объявлен в заголовке <algo.h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • Тип значения Forwarditerator является <Сравнимым. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • BinaryPredicate — модель Бинарного Предиката. • Тип значения Forwarditerator можно преобразовать к типам первого и второго аргументов BinaryPredicate. Предусловия • [f i rst, last) — допустимый диапазон. Оценка сложности Линейная. Для непустого диапазона выполняется ровно (last - first) - 1 сравнений. Пример int main() { list<int> L, generate_n(front_inserter(L), 1000, rand), list<int> const-iterator it = max_element(L begin(), L end()), cout « "The largest element is " « *it « endl, } ) Мы не можем говорить “итератор, указывающий на наибольший элемент в [first, last) ”. uoTt,M' чю единственного наибольшего элемента можег нс быть Наибольшее значение может встречал»1 в диапазоне более одного раза. Например, если все элементы в (fi rst, last) равны, то max_eleme"t 6v,H всегда возвращать first. Это обобщение max, которое возвращает первый аргумент, если они оба рав,,ь1’
12 Основные изменяющие алгоритмы Эта глава описывает основные алгоритмы, работающие на одном или нескольких диапазонах итераторов, в отличие от “просматривающих” (inspection) алгоритмов главы 11. При этом они изменяют часть элементов, на которые указывают итераторы. Основные модифицирующие алгоритмы включают в себя копирование элементов из одного диапазона в другой, присваивание значений каждому итератору в диапазоне и замену одних значений на другие. Алгоритмы в этой главе объединены важным свойством. Они модифицируют зна- чения, на которые указывает диапазон итераторов, а не сами итераторы (иными сло- вами, эти алгоритмы модифицируют диапазон элементов, а не диапазон итераторов). Когда диапазон [f i rst, last) передается одному из алгоритмов, описанных в этой главе, алгоритм не может изменять собственно итератор first, хотя изменяет *first. Он не может повлиять на порядок следования итераторов, а также не может изменять число элементов в диапазоне [f i rst, last). Будучи заявлено подобным образом, это свойство кажется очевидным, но в кон- тексте конкретных алгоритмов оно иногда имеет неожиданные проявления. Некоторые алгоритмы в этой главе, такие как partition, работают с одним диапа- зоном. Другие, вроде сору, работают с двумя отдельными диапазонами: с входным Галазоном, где значения не модифицируются, и с выходным диапазоном, которому присваиваются результаты работы алгоритма. Иногда в STL предусмотрены две формы одного и того же алгоритма. Например. reverse переставляет диапазон в обратном порядке на месте, тогда как reverse_copy к°пирует входной диапазон в обратном порядке в выходной диапазон. Такое правило именования всегда соблюдается: алгоритм по имени Х_сору всегда является копиру- И^й формой алгоритма X. 12.1. Копирование диапазонов |п | сору (копирование) Opiate <class Inputiterator, class Outputlterator> PutIterator copy(Inputiterator first, Inputiterator last, Outputiterator result);
240 Глава 12. Основные изменяющие алгоритмы Алгоритм сору копирует элементы из входного диапазона [first, last) в выхода , диапазон [result, result + (last - first)), то есть выполняет присваивания * resu^/* *fi rst, * (result +1) = * (f i rst +1) и т. д. Возвращается значение result + (last - f x rst' Требования алгоритма copy к своим параметрам шаблона крайне слабые. Вход^ диапазон должен состоять лишь из Итераторов Ввода, а выходной диапазон — из раторов Вывода. Это означает, что можно использовать сору для копирования значе ний практически из любого вида источника в почти любой вид приемника. Для любого целого п от 0 до, но не включая, last - f i rst алгоритм copy выполняет присваивание *(result + п) = *(first + п). Присваивание выполняется в прямом по- рядке, то есть в порядке возрастания п. Ш) Как и все алгоритмы в этой главе, сору изменяет только значения, на которые ука- зывает диапазон итераторов [result, result + (last - first)), а не сами итераторы Он присваивает новые значения элементам выходного диапазона, но не создает новые элементы. Он не может изменить количество итераторов в выходном диапазоне. Та- ким образом, сору нельзя использовать непосредственно для того, чтобы вставить элементы в пустой Контейнер. Если необходимо вставить элементы в Последовательность, то можно использо- вать либо ее соответствующую функцию-член, либо сору вместе с адаптером insert_iterator (с. 347). Где определено В реализации HP сору был объявлен в заголовке <algo. h> и определен в <algobase h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Inputiterator — модель Итератора Ввода. • Output Iterate г — модель Итератора Вывода. • Тип значения Input Iterate г можно преобразовать к одному из набора типов зна- чений Outputlrerator. Предусловия • [fi rst, last) — допустимый диапазон. • result не содержится внутри диапазона [fi rst, last). • Достаточно места для хранения всех копируемых элементов. Более формально требование можно сформулировать так: [result, result + (last - first)) явлЯ' ется допустимым диапазоном. Оценка сложности Линейная. Выполняется ровно last - first операций присваивания. J Порядок присваивания важен, когда входной и выходной диапазоны перекрываются Это ()31,аЧ^ь1. ню нельзя использовать сору, если result находится в диапазоне (first, last), то есть если начал0 ходиого диапазона перекрывается с входным диапазоном, но можно использован» сору, если копен > холного диапазона перекрывается с входным диапазоном Алгоритм copy_backward имеет обратное о» пичепие (Если оба диапазона совсем нс перекрываются, то можно использовать любой Также порядок присваивания важен, если result — это ost ream_iteraror (с 354) или другой и тер*11 семантика которого зависит от порядка присваивания
12.1. Копирование диапазонов 241 Пример СкоПИРовать элементы из vector в slist. В этом примере мы в явном виде создаем slist, в который наверняка поместятся все элементы из vector. int main() { char A[ ] - ’abcdefgh’, vector<char> V(A, A + strlen(A)), slist<char> L(V size()), copy(V begin(), V end(), L begin()), assert(equal(V begin(), V end(), L begin())), } Заполнить пустой list элементами из vector. Это нельзя сделать прямым копирова- нием в list, потому что в нем нет элементов, которым можно было бы присваивать значения, поэтому мы используем сору вместе с адаптером Итератора Вывода, добав- ляющим элементы в list. int main() { char A[] - "abcdefgh”, vector<char> V(A, A + strlen(A)), list<char> L, I copy(V begin(), V end(), back_inserter(L)), assert(equal(V begin(), V end(), L begin())), } Скопировать элементы list в стандартный поток вывода с помощью ost ream_ite rato г (с. 354). int main() { list<int> L L.push_back(l). L push_back(3), L push_back(5), L push_back(7), copy(|_ begin(). L end(), ostream_iterator<int>(cout, "\n")), } 12*1.2. copy_backward (копирование в обратном порядке) template <class Bidirectionallteratorl, class Bidirectionallterator2> irectionallterator2 copy_backward(Bidirectionallteratorl first, Bidirectionallteratorl last, Bidirectionallterator2 result); ^Лгоритм copy_backwa rd, как и copy (c. 239), копирует элементы из входного диапазо- аВ выходной диапазон. Входным диапазоном является [f i rst, last), а выходным —
242 Глава 12. Основные изменяющие алгоритмы [result - (last - first), result). Выполняются присваивания *( result - 1) = *(last - * (result - 2) = * (last - 2) и т. д. To есть для каждого целого п от 0 до last - f i rst, н0 включая последнего, *( result - п - 1) = *(last - п - 1). Присваивание выполняется От конца входной последовательности к началу в порядке возрастания п. } Возвращаемое значение равно result - (last - f i rst). Между алгоритмами copy и copy_backwa rd есть три различия. Во-первых, сору ну-^ дается только в том, чтобы его входной диапазон состоял из Итераторов Ввода, а вы- ходной — из Итераторов Вывода. copy_backward предъявляет гораздо более строгие требования. Его входной и выходной диапазоны должны состоять из Двунаправлен- ных Итераторов. Во-вторых, эти два алгоритма выполняют присваивание в противо- положных порядках: сору копирует диапазон от начала к концу, a copy_backward, как подсказывает название, копирует диапазон от конца к началу. В-третьих, сору, как большинство алгоритмов STL, обозначает выходной диапазон посредством итерато- ра, который указывает на начало диапазона. В случае с copy_backward, напротив, ите- ратор result указывает на конец выходного диапазона, что совершенно нетипично. Это единственный алгоритм STL, где диапазон описывается одним итератором, ука- зывающим на конец диапазона. Где определено В реализации HP copy_backward был объявлен в заголовке <algo.h> и определен в <algobase. h>. В соответствии со стандартом C++ он объявлен в заголовочном фай- ле <algorithm>. Требования к типам • Bidirectionallteratorl и Bidirectionallterator2 — модели Двунаправленного Итератора. • Значения типа значения Bidi rectionallteratorl можно преобразовать к значе- ниям типазначения Bidirectionallterator2. Предусловия • [f i rst, last) — допустимый диапазон. • result не содержится внутри диапазона [f i rst, last). • Достаточно места для хранения всех копируемых элементов. Более формально требование можно сформулировать так: [result - (last - first), result) являет- ся допустимым диапазоном. Оценка сложности Линейная. Выполняется ровно last - first операций присваивания. Пример Единственная разница между сору и copy_backward состоит в порядке присваивания- Порядок важен, если входной и выходной диапазоны перекрываются. В данном при } Порядок присваивания важен, когда входной и выходной диапазоны перекрываются. Это означав- что copy-backward нельзя применять, если result находится в диапазоне [first, last), то есть если ко’,с выходного диапазона перекрывается с входным диапазоном; но его можно использовать, если нача^ выходного диапазона перекрывается с входным диапазоном. Алгоритм сору имеет обратное огран»’4* пне Если оба диапазона совсем нс перекрываются, то, конечно, может использоваться любой ал гор1” •
12.2. Перестановка элементов 243 мы копируем элементы из одной части массива в другую (перекрывающуюся) меРе часть- int main() { int А[15] = {1. 2, 3, 4, 5. 6. 7. 8, 9, 10, 11. 12. 13. 14. 15}, copy_backward(А. А + 10, А + 15), сору(А- А + 15, ostream_iterator<int>(cout. " ’’)), cout « endl, II Результат- 12345123456789 10 12.2. Перестановка элементов 12.2.1- swap (обмен) teaplate <class Assignable> void swap(Assignable& a, Assignable& b); Алгоритм swap — один из немногих алгоритмов STL, которые работают с отдельны- ми объектами, передаваемыми в качестве аргументов, а не с диапазонами элементов. Он меняет местами значения а и Ь, присваивая аргументу b содержимое а, а аргумен- ту а — содержимое Ь. Операция swap — это примитив, используемый многими други- ми алгоритмами. Единственный по-настоящему общий способ реализации swap связан с использо- ванием временной переменной. Алгоритм совершает одно обращение к копирующе- му конструктору и два обращения к оператору присваивания, ожидаемое время вы- полнения будет приблизительно как у трех операций присваивания. В большинстве случаев можно написать специальные версии swap, которые будут гораздо эффектив- нее. Например, рассмотрим обмен двух объектов vecto r<double>, состоящих из Nэле- ментов. Наиболее обобщенная версия требует трех операций присваивания vector, то есть 3N операций присваивания double. Специальная версия, написанная только Для vector, могла бы ограничиться обменом значения нескольких указателей. Это очень важно, поскольку swap используется как примитивная операция во мно- гих алгоритмах STL, а типы вроде контейнера контейнеров (например, vecto r<st ring>) Встречаются довольно часто. STL содержит специализированные версии swap для всех контейнерных классов. Определяемые пользователем типы также должны обеспечи- вать специализированные версии swap, если их можно сделать более эффективными, еМ общую версию. определено Реализации HP swap был объявлен в заголовке <algo. h> и определен в <algobase. h>. с°°тветствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. требования к типам Assignable — модель Присваиваемого. ^Редусловия Чет.
244 Глава 12. Основные изменяющие алгоритмы Опенка сложности Константное амортизированное время (amortized constant time)/1 Пример int main() { int x = 1, int у = 2, assert(x == 1 && у -- 2), swap(x y), assert(x == 2 && у == 1), 12.2.2. iter_swap (обмен по итераторам) template <class Forwardlteratorl, class Forwardlterator2> inline void iter_swap(Forwardlteratorl a, Forwardlterator2 b); Если а и b являются итераторами, то iter_swap(a, b) эквивалентно swap(*a, *b). Лучше использовать swap вместо iter_swap, который был включен в исходную вер- сию HP STL [SL95] по чисто техническим причинам (а именно, для поддержки ите- раторов с нестандартными ссылочными типами). Он сохранился в последующих ре- ализациях и был включен в стандарт C++ для совместимости с ранее написанными программами. Где определено ВрсализацииНР11ег_8иарбылобъявленвзаголовке<а1до.Ь>иопределенв <algobase h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algonthm>. Требования к типам • Forwardlteratorl и Forwardlterator2 — модели Однонаправленного Итератора. • Forwardlteratorl и Forwardlterator2 — изменяемые итераторы. • Forwardlteratorl и Forwardlterator2 имеют одинаковые типы значения. Предусловия • Fo rwa rd Ite rat о г 1 и Fo rwa rd I te rato r2 можно разыменовать. Оценка сложности Константное амортизированное время. Пример int main() { int х = 1, } Время выполнения swap для двух объектов тина Т, очевидно, будет зависеть от конкретно!о 1Я|1‘ “Константное время” не означает, чго производительность будет одинаковой для 8-битных и 128-‘я1 пых complex<dodble>
12.2. Перестановка элементов 245 ini у - 2 assert(x == 1 && у == 2), iter_swap(&x, &у), assert(x == 2 && у == 1) 12.2.3. swap_ranges (обмен диапазонов) teflplate <class Forwardlteratorl, class Forwardlterator2> forwardlterator2 $wap..ranges( Forwardlteratorl firstl, Forwardlteratorl lastl, Forwardlterator2 first2); Алгоритм swap,ranges обменивает местами содержимое двух диапазонов одинаково- го размера. Каждое значение в диапазоне [f i rst 1, last 1) обменивается с соответству- ющим ему в диапазоне [fi rst 2, fi rst 2 + (lastl - firstl)). To есть для каждого целого я в интервале 0 <п < (lastl - firstl) переставляется *( firstl + ri) и *(first2 + ??). Возвра- щается значение first2 + (lastl - firstl). Где определено В реализации HP swap_ ranges был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwardlteratorl и Forwardlterator2 — модели Однонаправленного Итератора. • Типы значений Forwardlteratorl и Forwardlterator2 можно преобразовать друг к другу. Предусловия • [firstl, lastl) — допустимый диапазон. • [first2, first2+ (lastl - fi rst 1)) —допустимый диапазон. • Диапазоны [firstl, lastl) и [first2, first2 + (lastl - firstl)) не перекрываются. Оценка сложности Линейная. Выполняется ровно lastl - firstl обменов. Пример lnt main() { vector<int> V1. V 1 push_back(1). V 1 push_back(2), vector<int> V2 V2 push_back(3) V2 push_back(4),
246 Глава 12. Основные изменяющие алгоритмы assert(V1[0] == 1 && V1[1] == 2 && V2[0] == 3 && V2[1] == 4); swap_ranges(V1 begin(), V1 end(), V2 begin()), assert(V1[0] == 3 && V1[1] == 4 && V2[0] == 1 && V2[1] == 2); 12.3. transform (преобразование) 1) template <class Inputiterator, class Outputiterator, class UnaryFunction> Outputiterator transform(lnputlterator first, Inputiterator last, Outputiterator result, UnaryFunction op); 2) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class BinaryFunction> Outputiterator transform(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Outputiterator result, BinaryFunction binary_op); Алгоритм transformoneHb похож на for_each (c. 226), который выполняет некоторую операцию (передаваемую в виде функционального объекта) над диапазоном объек- тов. Отличие состоит в следующем: for_each отбрасывает значения, возвращаемые функциональным объектом, a transform копирует возвращаемые значения в другой диапазон. Отсюда название алгоритма transform: он преобразует один диапазон в другой, выполняя некоторую операцию над элементами диапазона. Есть две версии transform. Первая применяет Унарную Функцию к единственному входному диапазону, а вторая применяет Бинарную Функцию к двум входным диапа- зонам одинакового размера. Версия 1 выполняет операцию ор( *i) для каждого итератора i во входном диа- пазоне [first, last) и присваивает результат этого выражения в *о, где о — выход- ной итератор, соответствующий итератору i. То есть для каждого п в интервале 0 <п < last - fi rst выполняется присваивание *( result + п) =ор(*(first +и)). Воз вращается значение result + (last - first). Версия 2 использует Бинарную Функцию, а не Унарную Функцию. Она выполняет операцию ор(*11, *12) для каждого итератора 11 в диапазоне [firstl, lastl) и присва ивает результат в *о, где 12 — соответствующий итератор из второго входного диапа зона, а о — соответствующий выходной итератор. Таким образом, для каждого п в ин тервале 0 < п < lastl - firstl выполняется присваивание *( result +n) =ор( *(firstl п), *(first2 + n)). Возвращается значение result + (lastl -firstl). ) Если вы программируете на lisp, то ситуация вам знакома: for_each и transform соотносятся так71(1 как таре и mapcar
12.3. transform (преобразование) 247 Заметим, что transform может использоваться для модификации последователь- сТц “на месте”. Допустимо, чтобы итераторы f i rst и result совпадали. Несмотря на то что входной и выходной диапазоны могут совпадать, они не могут перекрываться. 0Терат°Р Вывода result не должен совпадать ни с одним Итератором Ввода в диапа- 30fle[first> last), за исключением собственно first. Где определено В реализации HP t ransf о rm был объявлен в заголовке <algo. h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • UnaryFunction — модель Унарной Функции. • Тип значения Inputiterator можно преобразовать к типу аргумента UnaryFunction. • Тип результата UnaryFunction можно преобразовать к одному из набора типов значений Outputiterator. Версия 2: • Inputlteratorl и Input Iterate г2 — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • BinaryFunction — модель Бинарной Функции. • Типы значений Inputlteratorl и Inputlteratoг2 можно преобразовать, соответ- ственно, к типам первого и второго аргументов Бинарной Функции. • Тип результата Una ryFunction должен быть преобразуем к одному из набора ти- пов значений Outputlte rato г. Предусловия Версия 1: • [fi rst, last) — допустимый диапазон. result не содержится в диапазоне [first + 1, last). Достаточно места для хранения всех копируемых элементов. Более формально требование можно сформулировать так: [result, result + (last - first)) являет- ся допустимым диапазоном. ВеРсия 2: [fi rst 1, lastl) — допустимый диапазон. [first2, first2 + (lastl - firstl)) —допустимый диапазон. result не является итератором в диапазоне [firstl + 1, lastl) или [first2 + 1, first2+ (lastl -firstl)). Достаточно места для хранения всех копируемых элементов. Более формально требование можно сформулировать так: [result, result + (lastl - firstl))явля- ется допустимым диапазоном.
248 Глава 12. Основные изменяющие алгоритмы Оценка сложности Линейная. Операция применяется ровно last - fi rst раз в случае версии 1 или last1 fi гs11 в случае версии 2. Пример Версия 1 transform является обобщением сору (с. 239). Она берет значения изоднОГо диапазона, выполняет некоторую операцию над ними и записывает их в другой дИа. пазон. Мы можем получить точно такое поведение, как у сору, если эта операция яв- ляется тождественной функцией. int main() { const int N = 7. double A[N] = {4. 5, 6, 7, 1. 2, 3}, list<double> L(N), transform(A, A + N, L begin(), identity<double>()), copy(L begin(), L end(), ostream_iterator<double>(cout, ”\n”)). } Более традиционный пример — замена каждого числа в массиве на число с противо- положным знаком. int main() { const int N = 10. double A[N] = {1, 2, 3. 4. 5. 6. 7, 8, 9, 10}, transform(A, A + N, A, negate<double>()), copy(A, A + N. ostream_iterator<double>(cout, "\n")), Версия 2 работает с двумя различными входными диапазонами. В этом случае мы ис- пользуем ее для вычисления суммы vector и массива. Выходной диапазон не обязан быть объектом типа list или vector. Он может быть любым Итератором Вывода, вклю- чая Итератор Вывода, который вообще не ассоциирован ни с каким контейнером. int main() const int N = 10. vector<int> V(N). fill(V begin(), V end(). 75). int A[N] = (10 9 8. 7. 6. 5, 4. 3. 2. 1}, transform(V beginQ, V end(), &A[OJ. ostream_iterator<int>(cout, ’\n”). plus<int>()),
12.4. Замена элементов 249 |2.4* Замена элементов 12.4.1- replace (замена) opiate <class ForwardIteratoг, class T> void replace(ForwardIterator first, Forwarditerator last, const T& old_value, const T& new_value) Алгоритм replace проверяет все элементы в диапазоне, заменяя каждое встреченное значение old._value на new_value. Элементы, которые не равны old_value, остаются без изменения. Иначе говоря, для каждого итератора i в диапазоне [f i rst, last) алгоритм сравни- вает *i со значением old_value. Если *1 == old_value, он выполняет присваивание *1 = new_value. Как и в случае с find (с. 208), существует очевидное обобщение replace. Чтобы не проверять элементы на равенство значению old_value, можно позволить пользовате- лю управлять проверкой, предоставляя обобщенный Предикат. По причинам техни- ческого характера эту более общую версию нельзя также назвать replace. У этих двух версий одинаковое количество аргументов, и обе являются шаблонными функция- ми. Общая версия должна иметь другое имя: replace_if. Где определено Вреализации HP replace был объявлен в заголовке <algo. h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Т можно преобразовать к типу значения Forwarditerator. • Тявляется Присваиваемым. • Т является =Сравнимым и может быть сравним на равенство с объектами типа значения Forwarditerator. Предусловия [first, last) — допустимый диапазон. Оценка сложности ^Инейная. Выполняется ровно last - f i rst сравнений на равенство и не более last - Xrst операций присваивания. пример Загнить яблоко (apple) на апельсин (orange). int main() { vector<string> fruits, fruits push_back(’cherry”). fruits push_back(”apple”).
250 Глава 12. Основные изменяющие алгоритмы fruits push_back("peach”), fruits push_back("plum"), fruits push_back("pear"): replace(fruits begin(), fruits end(), string("apple”). string("orange")). copy(fruits begin(), fruits end(), osrream_iterator<string>(cout, ”\n")). } 12.4.2. replace.if (замена по условию) template <class Forwarditerator, class Predicate, class T> void replace_if(Forwarditerator first, Forwarditerator last, Predicate pred, const T& new.value); Алгоритм replace_if — это обобщение replace. Он проверяет каждый элемент в диа- пазоне, заменяя элементы, для которых Предикат р red истинен. Таким элементам при- сваивается значение new_value. Элементы, для которых pred ложен, остаются без изме- нения. Более формально, для каждого итератора i в диапазоне [first, last) алгоритм replace_if вызывает pred(*i). Если pred(*i) истинен, то replace_if выполняет при- сваивание *i = new value. Многие алгоритмы STL имеют две версии: выполняющую операцию по умолча- нию и принимающую общую, предоставленную пользователем операцию. Версии обычно имеют одно и то же имя. Алгоритмы replace и replace_if имеют разные име- на по техническим причинам. Оба являются шаблонными функциями, и число аргу- ментов у них одинаково. Таким образом, могли бы возникать неоднозначные ситуа- ции, если бы имя replace использовалось для двух версий. Где определено В реализации HP replace_if был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Predicate — модель Предиката. • Тип значения Forward Iterate г можно преобразовать к типу аргумента Predicate. • Т можно преобразовать к типу значения Forwarditerator. ' • Т является Присваиваемым. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - f i rst применений pred и не более last - f i rst операций пр11 сваивания.
12.4. Замена элементов 251 Пример Обычно replace_if применяется с целью убедиться, что все значения в диапазоне удов- ргворяют некоторому условию. Значение, которое ему не удовлетворяет, заменяет- на приемлемое значение. В данном примере все отрицательные числа заменяются яаО. int main() < vector<double> V, V push_back(1), V.push_back(-3), V. push_back(2), V. push_back(-1), replace_if(V begin(), V end(), bind2nd(less<double>(), 0), 0 ), assert(V[1] == 0 && V[3] == 0); } Таким же образом мы можем заменить все строки, длина которых больше шести сим- волов, на индикатор переполнения: ******. struct string_length_exceeds { string_length_exceeds(int n) limit(n) {} bool operator()(const string& s) const { return s size() > limit, } int limit, }; int main() { string A[7] = {"oxygen", "carbon”, "nitrogen", "iron", "sodium", "hydrogen", "silicon"}, replace_if(A, A + 7, string_length_exceeds(6), "******”), c°py(A, A + 7, ostream_iterator<string>(cout, "\n")); 12.4.3. replace_copy (замена с копированием) <class Inputiterator, class Outputiterator, class T> Putlterator replace_copy(lnputlterator first, Inputiterator last, Outputiterator result, const T& old_value, const T& new.value); ^оритм replace_copy копирует элементы из диапазона [f i rst, last) в диапазон [ result, ь. t+ (last - first)), заменяя при этом значения old_value на new_value. Диапазон lrst, last) не изменяется.
252 Глава 12 Основные изменяющие алгоритмы Точнее, для каждого целого п при условии, что 0 < п < last - first, выполняет^ присваивание *( result + п) = new_value, если *(first + п) ==old_value, и *( result + *( fi rst + п) в противном случае. Как обычно для алгоритмов, названных *_сору, алгоритм replace_copy очень п0 хож на сору с последующим replace. Отличие в том, что replace_copy более эффек тивен и может использоваться с выходным диапазоном, заданным Итератора^ Вывода. Где определено В реализации HP replace_copy был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Inputiterator — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Т является =Сравнимым и может быть сравним на равенство с объектами типа значения Inputiterator. • Т является Присваиваемым. • Тип Т можно преобразовать к одному из набора типов значений Output Iterator. Предусловия • [fi rst, last) — допустимый диапазон. • В выходном диапазоне достаточно места для хранения копируемых элементов, то есть [result, result + (last - first)) — допустимый диапазон. • result не содержится в диапазоне [fi rst, last). Оценка сложности Линейная. Ровно last - first сравнений на равенство и ровно last - first операций присваивания. Пример Заменить все вхождения числа 1 на 99. int main() { vector<int> VI. V 1 push_back(1). V 1 push_back(2). V I push_back(3). V 1 push_back(1). vector<int> V2(V1 size()). replace_copy(V1 begin(), V1 end(), V2 begin(), 1. 99). assert(V2[1] == V1[1] && V2[2] == V1[2]), assert(V2[0] == 99 && V2[3] == 99). }
12.4. Замена элементов 253 12.4.4- replace_copy_if (замена с копированием по условию) eaiplate <class Inputiterator, class Outputiterator, * class Predicate, class T> Outputlterator replace_copy_if(InputIterator first, Inputiterator last, Outputiterator result, Predicate pred, const T& new.value); Алгоритм replace_copy_if является копирующим вариантом replace.if, так же как алгоритм replace_copy является копирующим вариантом replace. Он очень похож на replace.1 f, за исключением того, что работает с входным и выходным диапазонами, а не изменяет единственный диапазон “на месте”. Как и другие копирующие формы алгоритмов, replace_copy_if очень похож на сору споследующим replace_if. Он копирует элементы из диапазона [first, last) в диа- пазон [result, result + (last - first)), заменяя все элементы, для которых pred истин- но, на значение new_value. Таким образом, для любого целого п, такого что 0 < п < last - first, алгоритм replace.copy.if выполняет присваивание *( result + п) = new_value, если выполняется условие pred(*(first + п)), и присваивание *( result + п) = *(first + п) в противном случае. Где определено Вреализации HP replace_copy_if был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Inputiterator — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Predicate — модель Предиката. • Т можно преобразовать к типу аргумента Предиката. • Т является Присваиваемым. • ТипТ можно преобразовать к одному из набора типов значений Out put Iterate г. Предусловия [fi rst, last) — допустимый диапазон. В выходном диапазоне достаточно места для хранения копируемых элементов, то есть [result, result + (last - first)) является допустимым диапазоном, result не содержится в диапазоне [first, last). Оценка сложности ^Инейная. Ровно last - first применений pred и ровно last - first операций присва- Оример Пировать элементы из вектора в стандартный Числа нулем. поток вывода, заменяя отрицатель-
254 Глава 12. Основные изменяющие алгоритмы int main() { vector<int> V, V push_back(1), V push_back(-1) V push_back(-5) V push_back(2), replace_copy_if(V begin(), V end(), ostream_iterator<int>(cout, ”\n”), bind2nd(less<int>(), 0), 0). 12.5. Заполнение диапазонов 12.5.1. fill (заполнение) template <class Forwarditerator, class T> void fill(ForwardIterator first. Forwarditerator last, const T& value); Алгоритм fill присваивает значение value каждому элементу в диапазоне [ f i rst, last). To есть для каждого итератора 1 в [f i rst, last) выполняется присваивание *i = value Где определено В реализации HP fill был объявлен в заголовке <algo. h> и определен в <algobase h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Fo rwa rd Iterator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Т является Присваиваемым. • Т можно преобразовать к типу значения Forwarditerator. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - first операций присваивания. Пример Присвоить значение 137 каждому элементу vector. } Аргумеш должен быть переменным Однонаправленным Итератором, а не Итератором Выво№ поюму Mio ‘ill использует диапазон итераторов [first, last) Нельзя задать диапазон с помощью И раторов Вывода, так как их нельзя сравнивать на равенство Алгоритм fill_n, нанрошв, разрешав1 пользован» Итераторы Вывода
12.5. Заполнение диапазонов 255 int main О vector<double> V(4); filKV begin». V end». 137). assert(V[0] == 137 && V[1] == 137 && V[2] == 137 && V[3] == 137), 12.5.2. fill_n (заполнение первых n) teBplate <class Outputiterator, class Size, class T> Outputiterator fill_n(0utputlterator first, Size n, const T& value); Алгоритм fill_n присваивает значение value каждому элементу в диапазоне [first, first + n). To есть для каждого итератора i в диапазоне [f i rst, f i rst + n) выполняется присваивание *i = value. Возвращается значение f i rst + n. Где определено Вреализации HP fill_n был объявлен в заголовке <algo. h> и определен в <algobase. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Outputiterator — модель Итератора Вывода. • Size является целочисленным типом (со знаком или без знака). • Т является Присваиваемым. ' • Тип Т можно преобразовать к одному из набора типов значений Output Iterate г. Предусловия • п>0. • Достаточно места для хранения п значений. То есть [first, first + п) является допустимым диапазоном. Оценка сложности Линейная. Ровно п операций присваивания. Пример Добавить три копии значения 137 в конец vector. int main() ( vector<double> V(2, 128 ), fill_n(back_inserter(V), 3, 137 ), assert(V size() == 5 && V[2] == 137 && V[3] == 137 && j V[4] == 137).
256 Глава 12. Основные изменяющие алгоритмы 12.5.3. generate (генерация) template <class Forwarditerator, class Generator void generate(Forwarditerator first, Forwarditerator last, Generator gen); Алгоритм generate присваивает результат вызова функционального объекта gen дому элементу в диапазоне [f i rst, last). Сам gen не имеет аргументов. Это значит, ЧТо для каждого итератора 1 в диапазоне [first, last) выполняется операция *i = деп^ Функциональный объект gen вызывается для каждого итератора в диапазоне [fir$T last), а не один раз вне цикла. Это замечание существенно, потому что Генератор не- обязательно возвращает одно и то же значение при каждом вызове. Он может читать из файла, поддерживать и изменять внутреннее состояние и т. д. Если вы хотите вызвать функциональный объект один раз и присвоить результаг каждому элементу в диапазоне, то можете это сделать с помощью fill. Где определено В реализации HP gene rate был объявлен в заголовке <algo h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора.’* • Forwarditerator — изменяемый итератор. • Gene ratoг — модель Генератора. • Тип результата Generator можно преобразовать к типу значений Forwarditerator. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - f i rst обращений к gen. Пример Заполнить vector случайными числами, используя функцию rand стандартной био- лиотеки Си. int main() { vector<int> V(100). generated begin(), V end(), rand), 12.5.4. generate_n (генерация первых n) template <class Outputiterator, class Size, class Generator Outputiterator generate_n(0utputlterator first, Size n, Generator gen); * Аргумен i должен быть изменяемым Однонаправленным Итератором, а не Итератором Вывода, но’°'’ Mio он генерирует диапазон итераторов (first, last) Невозможно задать диапазон с помощью Итерат0 ров Вывода, гак как их нельзя сравнивать на равено во Ишерфенс алюритма generate^ позволяй ,и нользова । ь Итераторы Вывода.
12.6. Удаление элементов 257 горитм generate_n присваивает результат вызова функционального объекта део Л^доМУ элементу в диапазоне [f i rst, f i rst + n ), то есть для каждого итератора i в диа- [^irst’ first+ п) он выполняет присваивание *1 = gen(). П Возвращается значение f i rst + n. функциональный объект gen вызывается п раз (по одному разу для каждого итера- тора в диапазоне [first, first + п), а не один раз вне цикла. Это замечание существен- йо потому что Генератор не обязательно возвращает одно и то же значение при каж- дом вызове. Он может читать из файла, поддерживать и модифицировать внутреннее состояние и т. д. Где определено В реализации HP generate_n был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Outputiterator — модель Итератора Вывода. • Size — целочисленный тип (со знаком или без знака). • Generator — модель Генератора. • Тип результата Generator можно преобразовать к одному из набора типов значе- ний Output Iterator, Предусловия • п>0. • Достаточно места для хранения п значений, то есть [first, first + п) является допустимым диапазоном. Оценка сложности Линейная. Ровно п обращений к gen. Пример Напечатать 100 случайных чисел, используя функцию rand стандартной библиотеки Си. Int main() { 9enerate_n(ostream_iterator<int>(cout, ”\n"), 100, rand), > ’2.6. Удаление элементов 12 R i •°-1, remove (удаление) ^•plate <class ForwardIterator, class T> ^rdlterator remove(Forwarditerator first, Forwarditerator last, const T& value); ^горитм remove удаляет значение value из диапазона f f i rst, last).
258 Глава 12. Основные изменяющие алгоритмы Понятие удаления, однако, довольно тонкое. Важно обратить внимание на то, ч. ни один из модифицирующих алгоритмов, описанных в этой главе, включая rem0Vft° не уничтожает ни одного итератора и не меняет расстояния между first и р, " (см. с. 239). Они просто не могут сделать ничего подобного. Представим, напрцМе что А является массивом языка Си типа int[ 10]. Если написать remove(A, А + ю, з> при этом количество элементов в А остается прежним: массив в языке Си не мо^ менять размер. Выражение А + 10 будет всегда указывать непосредственно за 10-й эде мент А. Удаление должно означать нечто иное, чем реальное изменение размера контейне- ра, так как алгоритмы этой главы работают не с контейнерами, а с итераторами: они лишь присваивают значения элементам, на которые указывают итераторы. Реальный смысл удаления в данном контексте состоит в следующем: remove воз- вращает итератор new_last, такой что диапазон [f i rst, new_last) не содержит элемен- тов, равных value, то есть value удалено из диапазона [first, new_last). Процедура remove стабильна, значит, относительный порядок оставшихся элементов сохраняется Итераторы в диапазоне [new_last, last) все еще можно разыменовывать, но значе- ния, на которые они указывают, не определены, поскольку эти элементы не представ- ляют интереса. Если вы удаляете элементы из Последовательности, то можно просто стереть эле- менты, следующие за new_last. Разумный способ удалить элементы из Последова- тельности S — действительно удалить их, изменив размер Sequence, то есть написать гак: S еrase(remove(S begin(), S end(), x), S end()); Где определено В реализации HP remove был объявлен в заголовке <algo. h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Т является =Сравнимым. • Значения типа Т могут быть сравнимы на равенство со значениями типа значе- ния Forwarditerator. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - f i rst сравнений на равенство. Пример Удалить из vector все элементы, равные 1. Заметим, что размер vector не меня?11* после обращения к remove. Когда vector печатается второй раз, сразу после remove, в нем по-прежнему девять элементов. Однако в диапазоне [V. begin (), nevceqb нет элементов, равных 1.
12.6. Удаление элементов 259 размер vector не меняется до явного обращения к функции-члену erase(). teroplate<class Contained vOjd pnnt_container(const Contained C) { cout << c size() « " elements ". copy(C begin(). C end(). ostream_iterator<typename Container value_type>(cout, " ”)). cout endl, } int main() { const int A[9] - {3. 1, 4, 1. 5. 9, 2, 6, 5}, vector<int> V(A A + 9), print_conrainer(V), vector<int> iterator new_end = remove(V begin(), V end(). 1), print_container(V). V erase(new_end. V end()), prini_container(V), } 12.6.2. remove_if (удаление по условию) template cclass Forwarditerator, class Predicate> Forwarditerator remove_if(Forwarditerator first, Forwarditerator last, Predicate pred); Алгоритм remove_if удаляет из диапазона [first, last) все элементы, для которых Pred истинно. Он соотносится с remove так же, как replace_if соотносится с replace. У алгоритмов remove и remove_i f разные имена по чисто техническим причинам. Они °баявляются шаблонными функциями, и у них одинаковое количество аргументов, поэтому могли бы возникать неоднозначные ситуации, если бы имя remove использо- залось для двух версий. Как и в случае remove, понятие удаления довольно тонкое. Оно не означает измене- НИя количества элементов в диапазоне [f i rst, last). (См. с. 257, где подробно объяс- нено, почему это именно так.) Как и remove, алгоритм remove_if возвращает итератор new_last, такой что диапа- в[first, new_last) не содержит элементов, для которых pred истинно, то есть эле- Нты, удовлетворяющие pred, удалены из диапазона [f i rst, new_last). Операция ста- v Ьйа» значит, относительный порядок элементов в [first, new_last) такой же, как Цементов в [f i rst, last). B() 6 ИтеРатоРы в диапазоне [new_last, last) по-прежнему можно разыменовывать, значения, на которые они указывают, не определены. Определено Реализации HP remove_if был объявлен в заголовке <algo h>. В соответствии со стан- ком C++ он объявлен в заголовочном файле <algorithm>.
260 Глава 12. Основные изменяющие алгоритмы Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Predicate — модель Предиката. • Тип значения Fo rwa rdlte rato г можно преобразовать к типу аргумента Predict Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - f i rst применений pred. Пример Удалить все четные числа из vector. Как и в случае с remove (с. 258), remove if не меняет количество элементов в vector. Размер vector не меняется до явного обраще- ния к функции-члену erase(). int main() { vector<int> V, V push_back(1). V push_back(4); V push_back(2), V push_back(8), V push_back(5), V push_back(7), copy(V begin(), V end(). ostream_iterator<int>(cout, " ")), // Результат 142857 vector<int> iterator new_end = remove_if(V begin(), V end(), composel(bind2nd(equal_to<int>(), 0), bind2nd(modulus<int>(), 2))); V erase(new_end, V end()), copy(V begin(), V end(), ostream_iterator<int>(cout, ")), // Результат 157 12.6.3. remove_copy (удаление с копированием) template <class Inputiterator, class Outputiterator, class T> Outputiterator remove_copy(Inputiterator first, Inputiterator last, Outputiterator result, const T& value); Алгоритм remove_copy копирует элементы из диапазона [first, last) в диапазон* чинающийся с result, причем элементы, равные value, не копируются. Возврата^10
12.6. Удаление элементов 261 чеНие — конец результирующего диапазона. Если и элементов в [first, last) не рав- зН^а1ие’то возвращаемое значение — result + п. ** эта операция стабильна, то есть относительный порядок копируемых элементов такойже, как и в диапазоне [first, last). Где определено В реализации HP remove_copy был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Inputiterator — модель Итератора Ввода. • Output Iterate г — модель Итератора Вывода. • Тип значений Input Iterate г можно преобразовать к одному из набора типов зна- чений Out put Iterate г. • Т является =Сравнимым. • Объекты типа Т могут быть сравнимы на равенство с объектами типа значения Inputiterator. Предусловия • [first, last)— допустимый диапазон. • В выходном диапазоне достаточно места для хранения копируемых значений. Если п элементов в [first, last) не равны value, то [result, result +п) является допустимым диапазоном. • result не содержится в диапазоне [f i rst, last). Оценка сложности Линейная. Ровно last - f i rst сравнений на равенство и не более la»t - f i rst опера- ций присваивания. Пример ^спечатать все непустые строки в vector. int main() ( vector<string> V. V push_back("one”), v push_back( ’ ), V push_back("four’ ), v push_back("' ), V push_back('"), V Push_back("ten ’), remove_copy(V begin(), V end(), ostream_iterator<string>(cout, "\n”), stnng( ’ )),
262 Глава 12. Основные изменяющие алгоритмы Результат: one four ten 12.6.4. remove_copy_if (удаление с копированием по условию) template <class Inputiterator, class Outputiterator, class Predicate> Outputiterator remove_copy_if(Inputiterator first, Inputiterator last, Outputiterator result, Predicate pred); Алгоритм remove_copy_if копирует элементы из диапазона [first, last) в диапазон начинающийся с result, кроме элементов, для которых pred истинно. Возвращаемое значение — конец результирующего диапазона. Если в [f i rst, last) есть n элементов для которых pred ложно, то возвращаемое значение — result + п. Эта операция стабильна, то есть относительный порядок копируемых элементов такой же, как в диапазоне [first, last). Где определено В реализации HP remove_copy_if был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Inputiterator — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Тип значений Inputiterator можно преобразовать к одному из набора типов значений Outputiterator. • Predicate — модель Предиката. • Тип значения Inputlteratoг можно преобразовать к типу аргумента Predicate. Предусловия • [fi rst, last) — допустимый диапазон. • В выходном диапазоне достаточно места для хранения копируемых значении, то есть если п элементов в [f i rst, last) не удовлетворяют рred, то [ result, result является допустимым диапазоном. • result не содержится в диапазоне [first, last). Оценка сложности Линейная. Ровно last - first применений pred и не более last - first операций пр11 сваивания. Пример Заполнить вектор неотрицательными элементами другого вектора. int main() { vector<int> V1
12.6. Удаление элементов 263 y-|.push_back( -2). ^.push.back(O). yi.push_back(-1), yi.push_back(O), Vl.pusb_back(1), V1.push_back(2), vector<int> V2, remove_copy_if(V1 begin(), V1 end(), back_inserter(V2). bind2nd(less<int>(), 0)), } ^однократно высказывалось мнение, что в STL должен быть алгоритм copy_if, про- тйВоположный алгоритму remove_copy_if. Если remove_copy_if копирует все элемен- ты, которые не удовлетворяют некоторому условию, copy.if должен копировать все элементы, которые удовлетворяют указанному условию. Реализация copy_if на основе remove_copy_if тривиальна: template <class Inputiterator, class Outputiterator, class Predicate> Outputiterator copy_if(Inputiterator first, Inputiterator last, Outputiterator result, Predicate pred) { return remove_copy_if(first, last, result, notl(pred)), } 12.6.5. unique (единственные) 1) template <class Forwardlterator> ForwardIterator unique(Forwarditerator first, Forwarditerator last); 2) template <class Forwarditerator, class BinaryPredicate> ForwardIterator unique(Forwarditerator first, Forwarditerator last, BinaryPredicate binary_pred); Алгоритм unique удаляет повторяющиеся (duplicate) элементы. Когда встречается ^Уппа последовательных повторяющихся элементов в диапазоне [f i rst, last), unique ^являет только первый из них. Заметим, что unique удаляет лишь соседние повторяющиеся элементы. Если вы *°тите удалить все повторяющиеся элементы, то должны быть уверены, что они со- ^2gJ04eHbI рядом. По этой причине unique особенно полезен в комбинации с sort Как и все алгоритмы в этой главе, unique не может реально изменить количество Ментов в диапазоне [first, last). На с. 258 указано, что именно означает удаление таких алгоритмов. ne^r°PH™ unique возвращает итератор new_last, такой что в диапазоне [first, Ле не содержится соседних повторяющихся элементов, то есть дубликаты уда- Ы из диапазона [first, new_last). Операция стабильна, поскольку относительный Р^Док элементов, которые не удаляются, не изменяется. Итераторы в диапазоне [new_last, last) по-прежнему можно разыменовывать, ^Дементы, на которые они указывают, не определены.
2G4 Глава 12. Основные изменяющие алгоритмы Есть две разные версии unique, потому что существуют два определения понЯт дубликата для последовательных групп элементов. В версии 1 это определение за но простым равенством. Элементы в диапазоне [f, 1) являются повторяющимися, для каждого итератора i в этом диапазоне, кроме первого, выполняется *i == *(х В версии 2 проверка задана Бинарным Предикатом binary_pred: элементы Диапазон- [f, 1) являются повторяющимися, если для каждого итератора i в этом диапазон* кроме первого, binary_pred(*1, *(i-1)) истинно. Где определено В реализации HP unique был определен в заголовке <algo. h>. В соответствии со стан дартом C++ он определен в заголовочном файле <algonthm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленный Итератор. • Forward Iterator — изменяемый итератор. • Тип значений Forwarditerator является =Сравнимым. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • Binarypredicate — модель Бинарного Предиката.^ • Тип значений Forwarditerator можно преобразовать к типам первого и второго аргумента Binarypredicate. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности Линейная. Для непустых диапазонов ровно (last - first) - 1 применений operator- (версия 1) или binary_pred (версия 2). В случае с пустым диапазоном — ни одного применения operator== или binary_pred. Пример Удалить повторяющиеся значения для последовательных групп равных чисел типа int. Как и в случае с remove (с. 257), обратите внимание, что размер vector не измепЯ ется до непосредственного обращения к функции-члену erase(). int main() { vector<int> V V push_back(1), V push_back(3), V push_back(3), } Нс требуемся, чтобы BinaryPredicate задавал отношение эквивалентности, но следует нрояв о< горожнос1ь при использовании unique в сочетании с таким Бинарным Предикатом: вы лшко полечить не то, что ожидали.
12.6. Удаление элементов 265 y.push_back(3), V . push_back(2), V .push_back(2). V .push_back(1), vector<int> iterator new_end = unique(V begin(), V endO), V.erase(new_end, V end()), copy(V begin(), V end(), ostrearri_iterator<int>(cout, " ")), cout « endl, // Результат 1321 } Строго говоря, версия 1 алгоритма unique является лишней: она абсолютно эквива- лентна вызову версии 2 с объектом класса equal.to в качестве аргумента Бинарного Предиката. Здесь мы используем версию 2 алгоритма unique как менее удобную заме- ну версии 1. int main() { int А[8] = {7, 7, 1, 4. 6, 6, 6, 3}, int* new_end = unique(A, A + 8, equal_to<int>()), copy(A, new_end, ostream_iterator<int>(cout, “\n")), } Более полезный пример использования версии 2 — удаление повторяющихся симво- лов из vector, игнорируя регистр. Сначала мы сортируем vector, а затем удаляем дуб- ликаты из последовательных групп. inline bool eq_nocase(char cl, char c2) { return tolower(d) == tolower(c2), } inline bool lt_nocase(char d, char c2) { return tolower(d) < tolower(c2); } lnt main() const char init[] = ’’The Standard Template Library", vector<char> V(init, init + sizeof(init)), sort(V begin(), V end(), lt_nocase), c°py(V begin(), V end(), ostream_iterator<char>(cout)), cout « endl, vector<char> iterator new_end = unique(V begin(), V end(), eq_nocase), c°Py(V begin(), new_end, ostream_iterator<char>(cout)), cout « endl, // Результат // aaaabddeeehiLlmnprrrStTtTy // abdehiLmnprSty
266 Глава 12. Основные изменяющие алгоритмы 12.6.6. unique_copy (единственные с копированием) 1) template <class Inputiterator, class Outputlterator> Outputiterator unique_copy(InputIterator first, Inputiterator last, Outputiterator result); 2) template <class Inputiterator, class Outputiterator, class BinaryPredicate> Outputiterator unique_copy(lnputlterator first, Inputiterator last, Outputiterator result, BinaryPredicate binary_pred); Алгоритм unique_copy копирует все элементы из диапазона [first, last) в диапазон начинающийся с result, причем в последовательной группе повторяющихся элемен- тов копируется только первый элемент. Возвращаемое значение — конец диапазона в который копируются элементы. Как и в случае с другими алгоритмами, названия которых имеют форму *_сору. алгоритм unique_copy является копирующей версией unique (с. 263). Алгоритм поход на сору с последующим вызовом unique. Так же как и unique, он удаляет повторяющи- еся элементы, если они соседствуют друг с другом. Дубликаты, разделенные другими элементами, не удаляются. Существуют две различные версии unique_copy, поскольку есть два определения понятия дубликата для последовательных групп элементов. В версии 1 проверкой служит простое равенство: элементы диапазона [f, 1) являются дубликатами, если для каждого итератора 1 в этом диапазоне, кроме первого, *1 == *(1-1). В версии 2 проверка задается некоторым Бинарным Предикатом binary_pred: элементы в диапа- зоне [f, 1) являются дубликатами, если для каждого итератора 1 в этом диапазоне, кроме первого, binary_pred(*i, *(1 -1)) истинно. Где определено В реализации HP unique_copy был объявлен в заголовке <algo.h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • Тип значения InputIteratoг является =Сравнимым. • Outputiterator — модель Итератора Вывода. • Тип значения Inputiterator можно преобразовать к одному из типов из набора типов значений Outputiterator. Версия 2: • Inputiterator — модель Итератора Ввода. • BinaryPredicate — модель Бинарного Предиката. **> ) Эю напоминает программу unique из UNIX } Не 1ре6устся, чюбы BinaryPredicate задавал отношение .жвивалентности, по c.ie/ivei бы и»(Н рожными при использовании unique_copy в сочетании с таким Бинарным Предикатом Вы можете i|() чи п» не го, что ожидали.
12.6. Удаление элементов 267 Тип значения Inputiterator можно преобразовать к типам первого и второго аргументов BinaryPredicate. • outputIte rato г — модель Итератора Вывода. t Тип значения Inputlte rato г можно преобразовать к одному из набора типов зна- чений Output Ite rato г. Предусловия . [first, last) — допустимый диапазон. • Для хранения всех копируемых элементов достаточно места. Иначе говоря, если после удаления дубликатов из последовательных групп в диапазоне [f i rst, last) остается n элементов, то [result, result + n) должен быть допустимым диапа- зоном. Оценка сложности Линейная. Ровно last - first применений operator== (версия 1) или Dinary_pred (вер- сия 2) и не более last - f i rst операций присваивания. Пример Распечатать все числа в массиве, при этом печатать только первое в последователь- ной группе идентичных чисел. В выводе все еще есть дубликаты — числа 2 и 8 появля- ются дважды, — но результат не содержит повторяющихся соседних элементов: как раз то, что гарантируют unique или unique_copy. int main() { const int A[] = {2, 7, 7, 7, 1, 1, 8. 8, 8, 2, 8. 8}, unique_copy(A. A + sizeof(A) / sizeof(int), ostream_iterator<int>(cout, ” ”)), cout « endl, // Результат 271828 } Заполнить vector числами из массива, выбирая только одно число из диапазона 0-9, одно — из диапазона 10-19 и т. д. struct eq_div { eQ_div(int divisor) n(divisor) {} bool operator()(int x, int y) const { return x / n == у / n, } int n, main() int A[ ] = {1, 5, 8, 15, 23, 27, 41, 42, 43, 44, 67, 83, 89}. const int N = sizeof(A) / sizeof(int), vector<inT> v, unique_cooy(A. A+N, back_inserter(V), eq_div(10)),
268 Глава 12. Основные изменяющие алгоритмы copy(V begin(). V end(). ostream_iterator<int>(cout. '\n')), // Результат 1 15 23 41 67 83 12.7. Алгоритмы перестановки Перестановка диапазона элементов — это изменение порядка элементов. Она не из- меняет старых и не добавляет новых значений, а лишь выстраивает значения входно- го диапазона в другом порядке. Многие важные алгоритмы для диапазонов являются перестановками. (Так, например, сортировка диапазона — это перестановка. Однако сортировка настолько важна, что алгоритмам сортировки будет целиком посвящена глава 13.) 12.7.1. reverse (переворачивание) template <class Bidirectionallterator> void reverse(Bidirectional!terator first, Bidirectionallterator last); Алгоритм reve rse переворачивает диапазон “на месте”, то есть для каждого п в интерва- ле 0 < п < (last - first)/2 он меняет местами значения *(first + п) и *(last - (п + 1)). Обратите внимание, что reverse, как и все алгоритмы в этой главе, переставляв! значения, на которые указывает диапазон итераторов, а не сами итераторы. Где определено В реализации HP reverse был объявлен в заголовке <algo h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Bidirectionallterator — модель Двунаправленного Итератора. • Bidirectionallterator — изменяемый итератор. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Ровно (last - first)/2 вызовов swap (с. 243) Пример ни main() vector<int> V. V push_back(O) V push_back(1), V push_back(2) copy(V begin(), V end(), osrream_iterator<int>(cout, ' ’)), cout << endl, /7 Резулыаг 0 1 2
12.7. Алгоритмы перестановки 269 reverse(V begin(), V end()), copy(v begin(). V end(), ostream_iterator<int>(cout, " ”)), cout « endl. // Результат 2 1 0 l2.7.2. reverse_copy (переворачивание с копированием) teeplate <class Bidirectionallterator, class Outputlterator> gutputlterator reverse_copy(Bidirectional!terator first, Bidirectionallterator last, Outputiterator result); Алгоритм reverse_copy копирует элементы из диапазона [fi rst, last) в диапазон [ result, result + (last -first)), так что копия представляет собой исходный диапазон в пере- вернутом виде, то есть для каждого п в интервале 0 < п < (last - first) алгоритм reverse_copy выполняет присваивание *(result + (last - first) - n) = *(fi rst + n). Возвращаемое значение — конец получившегося диапазона: result + (last - fi rst). Как видно из названия самого алгоритма, reverse_copy является копирующей вер- сией reverse. Его действие сходно с сору с последующим reverse. Где определено Вреализации HP reverse_copy был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Bidirectionallterator — модель Двунаправленного Итератора. • Outputiterator — модель Итератора Вывода. • Тип значения Bidi rectional Ite rato г можно преобразовать к одному из типов из набора типов значений Output iterator. Предусловия [first, last) — допустимый диапазон. Достаточно места для хранения всех копируемых элементов. Более формально требование выражается так: [result, result + (last - first)) должен быть допус- тимым диапазоном. Диапазоны [first, last) и [result, result + (last - first)) не перекрываются ®Ченка сложности ^Нейная. Ровно last ~ first операций присваивания. пример *nt main() { vector<int> V. V.push_back(O).
270 Глава 12. Основные изменяющие алгоритмы V push_back(1), V push_back(2), copy(V begin(), V end(). ostream_iterator<int>(cout, " ”)), cout « endl, // Результат 012 reverse_copy(V begin(), V end(), ostream_iterator<int>(cout. ’)), cout « endl, // Результат 2 1 0 12.7.3. rotate (циклический сдвиг) template <class Forwardlterator> inline void rotate(Forwarditerator first, Forwarditerator middle, Forwarditerator last); Алгоритм rotate выполняет циклический сдвиг элементов в диапазоне, то есть он пе- ремещает элемент с позиции middle на позицию first, middle + 1 — на first + 1 и так далее. Формально для каждого целого п в интервале 0 < п < last - first элемент * (fi rst + п) присваивается элементу ★ (fi rst + (п + (last - middle)) % (last -first)). Из-за такого громоздкого определения можно подумать, что алгоритм rotate - очень сложный или узкоспециализированный. В действительности существует го- раздо более простое объяснение функции rotate: она меняет местами два диапазона, а именно [f i rst, middle) и [middle, last). Один из методов использования rotate — поменять местами диапазоны, что нельзя сделать с помощью swap_ranges (с. 245). Алгоритм swap, ranges можно использовать для обмена местами двух диапазонов строго одинаковой длины, a rotate — для пере- становки двух соседних диапазонов различной длины. Где определено В реализации HP rotate был объявлен в заголовке <algo. h>. В соответствии со стан- дартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. Предусловия • [first, middle) и [middle, last) — допустимые диапазоны. (Следовательно, Л,1а пазон [fi rst, last) тоже является допустимым.) Оценка сложности Линейная: rotate вызывает swap (с. 243) не более last - first раз. (Точное количестве обращений зависит от того, являются ли аргументы rotate Однонаправленными Ите раторами, Двунаправленными Итераторами или Итераторами Произвольного ДостУг,а' Сложность линейна во всех трех случаях.)
12.7. Алгоритмы перестановки 271 (|рИМеР й примере выполняются циклические сдвиги массива целых чисел. Мы начинаем со ешения элемента first в конец массива, то есть меняем местами диапазон, со- С*пжаший только элемент first, и диапазон, содержащий все остальные элементы. q^eM выполняем обратную операцию — меняем местами диапазон, содержащий толь- ко элемент last, и диапазон, содержащий все остальные элементы. Наконец, перено- сим первые три элемента массива в конец. int main() { int А[] = И. 2. 3. 4. 5, 6, 7}: const int N = sizeof(A) / sizeof(int), copy(A, A + N. ostream_iterator<int>(cout, " ")), cout « endl. // Результат 1234567 rotate(A. A + 1, A + N); copy(A, A + N, ostream_iterator<int>(cout, ” ")), cout « endl. // Результат 2345671 rotate(A A+N-1, А + N), сору(А. А + N. ostream_iterator<int>(cout, ” ")). cout « endl, И Результат 1234567 rotate(A, А + 3, А + N), сору(А, А + N, ostream_iterator<int>(cout, " ”)); cout « endl; /1 Результат 4 5 6 7 1 2 3 12.7.4. rotate_copy (циклический сдвиге копированием) te*Plate <class Forwarditerator, class Outputlterator> Outputlterator r°tate_copy( Forward Iterator first, Forwarditerator middle, ForwardIterator last, Outputiterator result); ^горитм rotate_copy — это копирующая версия rotate. Он похож на сору, за которым ^ДУет rotate. Чтобы не выполнять циклический сдвиг входного диапазона “на мес- »fotate_copy создает копию с уже выполненным циклическим сдвигом в диапазо- »Начинающемся с итератора result. /^алогично случаю с rotate, можно представить себе эту операцию как обмен двух апазонов: rotate_copy копирует элементы из входного диапазона [f i rst, last) в вы- ДНой диапазон [result, result + (last - first)), причем диапазоны [first, middle) iddle, last) берутся в обратном порядке. Таким образом, *middle копируется rBSult, *(middle + 1) копируется в *( result + 1) и т. д. Формально для каждого
272 Глава 12. Основные изменяющие алгоритмы целого п в интервале 0 < п < last -first алгоритм rotate_copy выполняет присвац ние *( result + (и + (last - middle)) % (last - first)) = *(first+ и). Возвращенное значение — конец выходного диапазона result + (last - first) Где определено В реализации HP rotate_copy был объявлен в заголовке <algo. h>. В соответствии Cf) стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Forwarditerator — модель Однонаправленного Итератора. • Outputiterator — модель Итератора Вывода. • Тип значения ForwardIteratoг можно преобразовать к одному из набора типов значений Outputiterator. Предусловия • [first, middle) и [middle, last) являются допустимыми диапазонами. (Следова- тельно, диапазон [f i rst, last) тоже является допустимым.) • Достаточно места для хранения всех копируемых элементов. Иначе говоря, [result, result + (last - first)) является допустимым диапазоном. • Диапазоны [first, last) и [result, result + (last - first)) не перекрываются. Оценка сложности Линейная. Ровно last - f i rst операций присваивания. Пример В этом примере массив А состоит из серий единиц, за которой следует серия двоек. Мы копируем массив в стандартный поток вывода, но меняем порядок двух частей. int main() { int А[ ] = {1, 1, 1, 2, 2, 2, 2}, const int N = sizeof(A) / sizeof(int), rotate_copy(A. A + 3, A + N, ostream_iterator<int>(cout, ” ’ )), cout << endl, // Результат 2222111 } 12.7.5. next_permutation (следующая перестановка) 1) template <class Bidirectionallterator bool next_permutation(BidirectionaIIterator first, Bidirectionallterator last); 2) template <class Bidirectionallterator, class StrictWeakOrdering> bool next_permutation(Bidirectionallterator first, Bidirectionallterator last, StrictWeakOrdering comp);
12.7. Алгоритмы перестановки 273 любые два диапазона, элементы которых являются <Сравнимыми, всегда можно срав- нять в словарном порядке, с помощью lexicographical_compare (с. 232). В частности, ^пжно сравнить две различные перестановки одного диапазона, например (1, 2, 3) 3(1-з’2> Для любого диапазона, состоящего из п элементов, существует конечное число раз- личных перестановок, максимум п\.Можно представить себе упорядочение всех пе- естановок в виде таблицы, отсортированной в лексикографическом порядке. Для Нсдой перестановки в этой таблице существует однозначное определение предыду- щей и последующей перестановок. Алгоритм next_permutation преобразует диапазон элементов [first, last) в следу- ющую перестановку, то есть лексикографически “большую”. Если существует такая перестановка, то next_pemutation преобразует в нее диапа- зон [first, last) и возвращает true. В противном случае он преобразует [first, last) в лексикографически “наименьшую” перестановку (которая по определению распо- ложена в восходящем порядке) и возвращает false. Постусловие таково: новая перестановка элементов лексикографически “больше” старой (в смысле lexicog raphical_compa re) тогда и только тогда, когда возвращенное значение равно t rue. Две версии next_pe mutation различаются способом определения меньшего элемента и, следовательно, лексикографически меньшего диапазона. Версия 1 сравнивает объек- ты с помощью operators а версия 2 — с помощью предоставленного пользователем функционального объекта comp. Где определено В реализации HP next.pe mutation был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Bidirectionallterator — модель Двунаправленного Итератора. • Bidirectionallterator — изменяемый итератор. • Тип значения Bidirectionallterator является Строго Слабо Сравнимым. Версия 2: Bidirectionallterator — модель Двунаправленного Итератора. Bidirectionallterator — изменяемый итератор. StrictWeakOrdenng — модель Строго Слабо Сравнимого. Тип значения Bidirectionallterator можно преобразовать к типу аргумента StrictWeakOrdenng. Предусловия [first, last) — допустимый диапазон. ___________ Неи ^СЛИ всс J'ivnciii ы в данном диапазоне различаются, то существует ровно п' перестановок Ес in То °Т°^Ые цементы совпадаю!, ю количество перестановок сокращается Так, например, cvihcctbvci ЛьКо три (3*/2) перестановки элементов (1, 2, 2)
274 Глава 12. Основные изменяющие алгоритмы Оценка сложности Линейная. Не более (last - first)/2 вызовов swap (с. 243). Пример Сгенерировать все перестановки элементов (1, 2,3,4). Эта программа создает 4! 24 строк вывода. Первая перестановка — (1,2, 3,4), последняя — (4, 3, 2, 1). int main() { const int N - 4, int A[N] - {1. 2. 3, 4}, do { copy(A, A + N, ostream_iterator<int>(cout, ” ")), cout « endl, } while (next_permutation(A, A + N)), } Последовательная генерация перестановок диапазона может служить базой для худ- шего из известных детерминированных алгоритмов сортировки. Большинство хоро- ших алгоритмов сортировки имеют сложность O(iVlogN), и даже самые плохие алго- ритмы — только О(№). Этот дурашливый пример демонстрирует алгоритм сорти- ровки со сложностью О(ЛП). template <class Bidirectionallterator, class StnctWeakOrdering> void snail_sort(Bidirectional!terator first, Bidirectionallterator last, StrictWeakOrdering comp) { while (next_permutation(first, last, comp)) {} 12.7.6. prev_permutation (предыдущая перестановка) 1) template <class Bidirectionallterators> bool prev_permutation(Bidirectional!terator first, Bidirectionallterator last); 2) template <class Bidirectionallterator, class StrictWeakOrdering> bool prev_permutation(BidirectionalIterator first, Bidirectionallterator last, StrictWeakOrdering comp); Перестановки набора элементов могут быть организованы в словарном, то есть леК' сикографическом порядке (см. с. 273, где приведено полное объяснение). Поскольку существует конечное число перестановок, то лексикографический порядок задаеТ однозначное определение последующей и предыдущей перестановок.
12.7. Алгоритмы перестановки 275 Алгоритм prev_permutation преобразует диапазон элементов [first, last) в лекси- иоГраФически предшествующую перестановку тех же элементов. £сли существует подобная перестановка, то prev_permutation преобразует в нее иапазон [first, last) и возвращает true. В противном случае, если каждая переста- рка элементов в [first, last) будет “больше”, чем исходный диапазон [first, last), ой преобразует [first, last) в лексикографически наибольшую перестановку (кото- оаяпо определению отсортирована в нисходящем порядке) и возвращает false. Постусловие таково: новая перестановка элементов лексикографически меньше старой (в смысле lexicog raphical_compare) тогда и только тогда, когда возвращаемое значение истинно. Две версии prev_permutat 1 on различаются способом определения меньшего элемен- та, а значит, лексикографически меньшего диапазона. Версия 1 сравнивает объекты с помощью operator^ а версия 2 — с помощью предоставленного пользователем функ- ционального объекта comp. Где определено Вреализации HP prev_permutation был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Bidirectional Iterate г — модель Двунаправленного Итератора. • Bidirectionallterator — изменяемый итератор. • Тип значения Bidirectionallterator является Строго Слабо Сравнимым. Версия 2: • Bidirectionallterator — модель Двунаправленного Итератора. • Bidirectionallterator — изменяемый итератор. • StrictWeakOrdering — модель Строго Слабо Сравнимого. • Тип значения Bidirectionallterator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия • [first, last) — допустимый диапазон. Оценка сложности ^Инейная. Не более (last - f i rst)/2 вызовов swap (с. 243). Пример * lr|t main() { mt A[] = {2 3. 4. 5. 6. 1}, const int N = sizeof(A) / sizeof(int). cout о 'Initially
276 Глава 12. Основные изменяющие алгоритмы сору(А А + N, ostream_iterator<int>(cout, )), cout << endl prev_permu1ation(A A + N), cout << After prev_permutation copy(A. A+N, ostream_iterator<int>(cout, ”)), cout << endl next_permutation(A A+N) cout « After next_permutation ”, copy(A, A + N, ostream_iterator<int>(cout, " ")), cout « endl, 12.8. Разбиения 12.8.1. partition (разбиение) • template <class Bidirectionallterator, class Predicate> Bidirectionallterator partition(Bidirectionallterator first, Bidirectionallterator last, Predicate pred); Алгоритм partition переставляет элементы в диапазоне [first, last), используя функ- циональный объект pred, так что элементы, удовлетворяющие pred, предшествуют элементам, ему не удовлетворяющим. Постусловие pa rtition заключается в том, что для некоторого итератора middle в ди- апазоне [first, last) pred(*i) истинно для каждого итератора 1 в диапазоне [first, middle) и ложно для каждого итератора i в диапазоне [middle, last). Заметим, что partition не обязательно сохраняет относительный порядок элементов. Гарантиру- ется, что элементы в [fi rst, middle) удовлетворяют pred, а элементы в [middle, last)- нст, но не предоставляется никаких гарантий по поводу порядка элементов внутри [first, middle) или [middle, last). Другой алгоритм, stable_partition (с. 277), напро- тив, сохраняет относительный порядок. Возвращаемое значение — итератор middle. Где определено В реализации HP partition был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам • Bid 1 rectionallterator — модель Двунаправленного Итератора. • Predicate — модель Предиката. • Тип значения Bidirectionallterator можно преобразовать к типу аргумента Predicate. Предусловия • [first, last) — допустимый диапазон.
12.8 Разбиения 277 уценка сложности Линейная. Ровно last - first применений pred и не более (last - first)/2 вызовов 'swap (с-243). Пример Изменить порядок элементов последовательности так, чтобы четные числа предше- ствовали нечетным. Заметим, что хотя в результате исходный диапазон отсортиро- ван, однако ни четные, ни нечетные числа не отсортированы: partition, в отличие от gtable-Partition, не сохраняет относительного порядка элементов. int main() I int А[] - {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, const int N = sizeof(A) / sizeof(int), partition(A. A + N, composel(bind2nd(equal_to<int>(), 0), bind2nd(modulus<int>(), 2))), copy(A. A + N. osl ream_iterator<int>(cout, ” // Результат 10 284657391 12.8.2. stable_partition (стабильное разбиение) template <class Forwarditerator, class Predicate> Forwarditerator stable^partit ion (Forward I terator first, ForwardIterator last, Predicate pred); Алгоритм stable_partition во многом похож на partition. Он изменяет порядок эле- ментов в диапазоне [fi rst, last), используя функциональный объект pred, так что все элементы, удовлетворяющие pred, предшествуют элементам, ему не удовлетворя- ющим. Как и у partition, постусловие stable_partition таково, что для некоторого итера- тора middle в диапазоне [first, last) значение р red (*i) истинно для каждого итерато- ра/ в диапазоне [first, middle) и ложно для каждого итератора i в диапазоне [middle, last). Возвращаемое значение — итератор middle. Различие между алгоритмами partition и stable_partition заключается в том, что У stable_partition есть дополнительное постусловие: он сохраняет относительный Порядок элементов. Если х и у — элементы в [first, last) — таковы, что pred (х) == Pred(y), и если х предшествует у, то и после разделения х будет предшествовать у. Сохранение относительного порядка — это единственное семантическое различие алгоритмами stable_partition и partition. Так как partition работает быст- • не следует использовать stable_partition, если для вашего приложения не тре- мся гарантии сохранения стабильности. определено реализации HP stable_partition был объявлен в заголовке <algo h>. В соответствии СТаНдартом C++ он объявлен в заголовочном файле <algorithm>.
278 ['лава 12. Основные изменяющие алгоритмы Требования к типам • Forwarditerator — модель Однонаправленного Итератора.’* • Predicate — модель Предиката. • Тип значения Forwarditerator можно преобразовать к типу аргумента Р red ica-e Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности В отличие от partition, алгоритм stable_partition — адаптивный', он пытается выде- лить временный буфер памяти, и время выполнения зависит от доступного объема памяти. В худшем случае (если вспомогательная память недоступна) возможны мак- симум Nlog(N) вызовов swap (с. 243), где Nравно last - f i rst, а в лучшем случае (при достаточно большом буфере вспомогательной памяти) сложность линейно зависит от N. В обоих случаях pred применяется ровно ЛГраз. Напомним, что partition работает быстрее. Он не использует временный буфер, и его сложность всегда линейна. Пример Изменить порядок элементов последовательности так, чтобы четные числа предшест- вовали нечетным. Последовательность изначально отсортирована. После того как разбиение построено, и четные и нечетные числа остаются отсортированными, по- скольку stable_partition, в отличие от partition (с. 276), сохраняет относительный порядок элементов. int main() { int А[] = {1. 2, 3. 4, 5, 6. 7. 8, 9, 10}, const int N = sizeof(A) / sizeof(int), stable_partition(A, A + N, compose!(bind2nd(equal_to<int>(), 0), bind2nd(modulus<int>(). 2))), copy(A, A + N, ostream_iterator<int>(cout. ” ")). // Результат 2468 10 13579 } 12.9. Случайные перестановки и выборки Первым в этом разделе рассмотрим перестановочный алгоритм random_shuff 1е. Как и алгоритмы в разделе 12.7, он изменяет порядок в диапазоне, сохраняя все первона- чальные значения. Два других алгоритма этого раздела не являются перестановоч ными. Вместо случайной перестановки диапазона они выполняют очень схожую за дачу формирования случайной выборки из входного диапазона. * В соогвсчсгвии со стандартом C++ требуется, чтобы тип итератора был моделью Двунаправл енног° Итератора, однако требование не является строгим stable_partition можно реализовать для люй(’|(’ Однонаправленного Итератора
12.9. Случайные перестановки и выборки 279 |2.9-1- random_shuffle (случайная перетасовка) Р template <class RandomAccessIterator> void random_shuffle(RandomAccessIterator first, RandomAccessIterator last): 2^ template <class RandomAccessIterator, class RandomNumberGenerator> void random_shuffle(RandomAccessIterator first, RandomAccessIterator last, RandomNumberGenerator& rand) Алгоритм random_shuf f le в случайном порядке перемешивает диапазон [f i rst, last), то есть случай но выбирает одну изМ возможных перестановок элементов, где N рав- но last -first. Существует Nl способов расставить последовательность из N элементов. random_shuf f 1е выдает равномерно распределенные результаты. Вероятность каждой отдельной перестановки составляет 1/М Этот комментарий важен, так как некото- рые алгоритмы выполняют, на первый взгляд, случайное перемешивание элементов в последовательности, но на самом деле они не дают равномерного распределения на М возможных перестановках, в результате чего можно получить некорректное слу- чайное перемешивание. Этот алгоритм описан в разделе 3.4.2 у Кнута [Кпи98а]. Кнут ссылается на работы [МО63] и [Dur64], Две версии random_shuffle различаются способом получения случайных чисел. •Версия 1 использует внутренний генератор случайных чисел, а версия 2 — Генератор Случайных Чисел, специальный вид функционального объекта, который непосред- ственно передается в качестве аргумента. (Следует отметить, что он передается по ссылке, а не по значению, так как суть Генератора Случайных Чисел заключается в том, что локальное состояние изменяется при каждом обращении.) При использовании случайных чисел иногда необходимо явно задать стартовое значение генератора случайных чисел. В этом случае используется версия 2. Где определено В реализации HP random_shuff 1е был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам версия 1: RandomAccessIterator — модель Итератора Произвольного Доступа. версия 2: RandomAccessIterator — модель Итератора Произвольного Доступа. RandomNumberGenerator — модель Генератора Случайных Чисел Разностный тип RandomAccessIterator можно преобразовать к типу аргумента RandomNumberGenerator. ^условия [first, last) — допустимый диапазон.
280 Глава 12. Основные изменяющие алгоритмы________________ • last - first меньше максимального значения rand. Оценка сложности Линейно зависит от last - f i rst. Для непустых диапазонов random_shuf f 1е вызывает swap ровно (last - first) - 1 раз. Пример int main() i const int N = 8, int A[ ] = {1. 2, 3, 4, 5, 6, 7, 8}, random_shuffle(A, A + N), ccpy(A. A + N. ostream_iterator<int>(cout, ' )), cout << endl, // Результат может быть такой 71632548 // или любой другой из еще 40319 возможных 12.9.2. random_sample (случайная выборка) 1) template <class Inputiterator, class RandomAccessIterator> RandomAccessIterator random_sample(Inputiterator first, Inputiterator last, RandomAccessIterator ofirst, RandomAccessIterator olast) 2) template <class Inputiterator, class RandomAccessIterator, class RandomNumberGenerator> random_sample(Inputiterator first, Inputiterator last, RandomAccessIterator ofirst, RandomAccessIterator olast, RandomNumberGenerator& rand) Алгоритм random_sample копирует случайную выборку элементов из диапазона [f i rst. last) в диапазон [ofirst, olast) в случайном порядке. Он копирует п элементов, где?/ равно nnn(last - fi rst, olast - ofirst). Каждый элемент из входного диапазона мак- симум один раз появляется в выходном диапазоне, а выборки формируются с равно- мерным распределением. Возвращаемое значение равно of i rst + п. Поскольку не принимается во внимание порядок элементов, то существует /V’/(/?!(N - ??)!) вариантов выборки п элементов из набора N элементов Этот алго- ритм выдаст равномерно распределенные результаты. Вероятность выбора какого либо конкретного элемента составляет n/N, а вероятность какой-либо определенной выборки (без учета порядка элементов) — л?!(^ — В выходном диапазоне элементы могут оказаться в любом порядке. Не гарантир) стся, что относительный порядок элементов входного диапазона будет сохранен. 1 Если необходимо сохранил» oi носи юльный порядок эчемешов входною диапазона, ю и<_ ноль юна и» random_sampleji (с 282) Главное oi раничение random_sample_n заключаемся в ЮМ- 4 входной диапазон должен с ос гоя i ь из Однонаправленных Итераторов, а нс Итераторов Ввода
12.9. Случайные перестановки и выборки 281 ----------------------- _ _ Это “Алгоритм R” из раздела 3.4.2 Кнута [Кпи98а]. Кнут ссылается на Алана Уоч ер- мена(А1ап Waterman) Две версии random_sample различаются способом получения случайных чисел. Вер- сия 1 использует внутренний генератор случайных чисел, а версия 2 — Генератор Слу- чайных Чисел как специальный вид функционального объекта, который передается непосредственно в качестве аргумента. При использовании случайных чисел иногда необходимо явно задать стартовое значение генератора случайных чисел. В таком случае используется версия 2. Где определено Алгоритм random_sample отсутствует в исходной реализации HP STL и в стандарте C++. В реализации SGI он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • RandomAccessIte rato г — модель Итератора Произвольного Доступа. • RandomAccessIte ratoг — изменяемый итератор. • Тип значения Inputiterator можно преобразовать к типу значения RandomAccessIte rato г. Версия 2: • Inputiterator — модель Итератора Ввода • RandomAccessIte ratoг — модель Итератора Произвольного Доступа. • RandomAccessIteratoг является изменяемым. • RandomNumberGenerator является моделью Генератора Случайных Чисел. • Тип значений Inputiterator преобразуется к типу значений RandomAccessIterator. • Разностный тип RandomAccessIterator можно преобразовать к типу аргумента RandomNumbe rGene rato г. Предусловия ’ [fi rst, last) — допустимый диапазон. • [of i rst, olast) — допустимый диапазон.^ [first, last) и [ofirst, olast) не перекрываются. last - fi rst меньше, чем максимальное значение rand. Оценка сложности Линейно зависит от last - f i rst. He более last - fi rst элементов копируется из вход- Ог° диапазона в выходной диапазон. пример int main() { const int N = 10, const int n = 4,
282 Глава 12. Основные изменяющие алгоритмы int Af ] = {1, 2, 3. 4. 5. 6. 7. 8. 9. 10}, int B[n], rar-dGm_saiiiplc(A. A + N B, d + n>, copy(B. В + n. ostream_iterator<int>(cout, ’)), // Результат может быть такой 1635 // или любой другой из еще 5039 возможных } 12.9.3. random_sample_n (случайная выборка из п) 1 ) template <class Forwarditerator, class Outputiterator, class Distance> Outputiterator random_sample_n(Forwarditerator first, Forwarditerator last, Outputiterator out, Distance n) 2 ) template <class Forwarditerator, class Outputiterator, class Distance, class RandomNumberGenerator Outputiterator random_sample_n(Forwarditerator first, ForwardIterator last, Outputiterator out, Distance n, RandomNumberGenerator& rand) Алгоритм randon_sample_n случайным образом копирует некоторые элементы из диа- пазона [fi rst, last) в диапазон [out, out + п). Количество копируемых элементов рав- но т, где т = min(last - first, п). Каждый элемент из входного диапазона попадает в выходной диапазон максимум один раз, а выборки формируются с равномерным распределением. Возвращаемое значение равно out + т. Без учета порядка элементов существует N\/(n\(N - и)!) вариантов выборки п элементов из диапазона, содержащего N элементов. Алгоритм выдает равномерно рас- пред елейные результаты. Вероятность выбора конкретного элемента составляет и/А, а вероятность определенной выборки — nl(N-п)\/№. Одно из важных различий между random_sample и random_sample_n состоит в том, что random_sample_n сохраняет относительный порядок элементов, который был во входном диапазоне, тогда как random_sample этого не делает. Другое различие межД) этими двумя алгоритмами заключается в том, что random_sample_n требует, чтобы его входной диапазон состоял из Однонаправленных Итераторов, а выходной — из Итера- торов Вывода, тогда как random sample требует, чтобы его входной диапазон состоя! из Итераторов Ввода, а выходной диапазон — из Итераторов Произвольного Доступа Эю “Алгоритм S” из раздела 3.4.2 Кнута [Knu98aJ. Кнут ссылается на труды Фан^ Мюллера и Резухи [FMR62], а также Т. Г. Джонса (Т. G. Jones). Версии random_sample различаются способом получения случайных чисел. Версия^ использует внутренний генератор случайных чисел, а версия 2 — Генератор Случай' ных Чисел, специальный функциональный объект, который непосредственно перед3 ется в качестве аргумента.
12.9. Случайные перестановки и выборки 283 рде определено ддгоритм random_sample_n не входит в исходную реализацию HP STL и в стандарт C++. В реализации SGI он определен в заголовке <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • Output Iterate г — модель Итератора Вывода. • Тип значения Forwarditerator можно преобразовать к одному из типов из набо- ра типов значений Outputiterator. • Distance является целочисленным типом, достаточным для того, чтобы вместить значение last - first. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • Outputiterator — модель Итератора Вывода. • RandomNumberGenerator -- модель Итератора Произвольного Доступа. • Distance является целочисленным типом, достаточным для того, чтобы вместить значение last - first. • Тип значения Forwarditerator можно преобразовать к одному из набора типов значений Outputlte rato г. • Distance можно преобразовать к типу аргумента RandomNumbe rGene rato г. Предусловия • [fi rst, last) — допустимый диапазон. • п — неотрицательное. • [fi rst, last) и [out, out + n) не перекрываются. • Достаточно места для сохранения всех копируемых элементов, [out, out + min(w, last - f i rst)) является допустимым диапазоном. • last - fi rst меньше максимального значения rand. Оценка сложности Линейно зависит от last - f i rst. Рассматривается максимум last - f i rst элементов Годного диапазона, и ровно min(/7, last - first) элементов копируется в выходной диапазон. Пример int main() { const int N = 10, int A[] = {1, 2. 3, 4, 5, 6, 7, 8, 9, 10}. random_sample_n(A, A + N. ostream_iterator<int>(cout, ” "), 4), cout « endl,
284 Глава 12. Основные изменяющие алгоритмы // Результат может быть такой 3 5 6 10 // или любой другой из еще 209 возможных } 12.10. Обобщенные числовые алгоритмы 12.10.1. accumulate (накопление) 1 ) template <class Inputiterator, class T> T accumulate(Inputiterator first, Inputiterator last, T init); 2 ) template <class Inputiterator, class T, class BinaryFunction> T accumulatednputlterator first, Inputiterator last, T init, BinaryFunction binary_op); Алгоритм accumulate — это обобщение суммирования. Он вычисляет сумму (или вы- полняет какую-либо другую бинарную операцию) начального значения in it и каж- дого элемента в [f i rst, last). Заметим, что интерфейс accumulate несколько неуклюж. Всегда нужно задавать начальное значение init, потому что какой-то результат должен получиться, даже когда диапазон [first, last) является пустым. Если, например, вы хотите найти сум- му всех чисел в диапазоне [first, last), то в качестве начального значения нужно задать 0. Бинарная операция не обязательно должна быть коммутативной или ассоциатив- ной. Порядок всех операций accumulate строго определен. Результат сначала уста- навливается равным init. Затем для каждого итератора! в [first, last), в порядке от начала к концу, он изменяется по формуле result = result + *1 (версия 1) или result - binary_op( result. *i) (версия 2). Где определено В реализации HP accumulate был объявлен в заголовке <algo. h>. В соответствии со стандартом С+т он объявлен в заголовке <numeric>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • Т — модель Присваиваемого. • Если х является объектом типа Т, а у является объектом типа значения Inputiterator, то выражение х + у определено. • Тип выражения х + у можно преобразовать к Т. Версия 2: • Input Iterator — модель Итератора Ввода. • Т — модель Присваиваемого. • BinaryFunction — модель Бинарной Функции. • Т можно преобразовать к типу первого аргумента BinaryFunction.
12.10. Обобщенные числовые алгоритмы 285 9 Тип значения Inputiterator можно преобразовать к типу второго аргумента BinaryFunction. • Тип, возвращаемый BinaryFunction, можно преобразовать к Т. (1реДУсл0ВИЯ • [f 1 rst, last) — допустимый диапазон. Оценка сложности Линейная. Ровно last - f i rst обращений к бинарной операции. Пример Найти сумму и произведение всех элементов в диапазоне. Этот пример демонстриру- ет обе версии accumulate. Поскольку тождественные преобразования при сложении и умножении различны, мы принимаем 0 в качестве начального значения при вычис- лении суммы и 1 при вычислении произведения. int main() { int А[] = {1. 2, 3, 4 5}. const int N = sizeof(A) / sizeof(int), cout « 'The sum of all elements in A is « accumulate(A, A+N 0) « end]. cout « ’The product of all elements in A is ” « accumulated, A+N, 1, multiplies<int>()) « endl, } В случае непустых диапазонов легко создать обертку для accumulate, что позволяет избежать необходимости задавать аргумент init. (Нельзя использовать эту обертку, если диапазон [firstl, lastl) пуст, потому что тогда нечего взять в качестве возвра- щаемого значения.) template <class Inputlterator> typename iterator_traits<lnputlterator> value_type accumulate_nonempty(lnputlterator first, Inputiterator last) { assert(first '= last) typename iterator_traits<lnputlterator> value_type sum = *first++ return accumulate^irst. last, sum), template <class Inputiterator, class BinaryFunction> typename iterator_traits<lnputlterator> value_type accumulate_nonempty(lnputlterator first, Inputiterator last, BinaryFunction binary_op)
286 Глава 12. Основные изменяющие алгоритмы assert(first ’ = last). typenamc 11eratorj.raits<lnputlterator> value_type init = *first++, return accuffiulate(firsL, last, irut, binary_op), 12.10.2. inner_product (скалярное произведение) 1) template <class Inputlteratorl, class Inputlterator2, class T> T inner_product(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, T init); 2) template <class Inputlteratorl, class Inputlterator2, class T, class BinaryFunctionl, class BinaryFunction2> T inner_product(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, T init, BinaryFunctionl binary_op1, BinaryFunction2 binary_op2); Алгоритм inner -product вычисляет обобщенное скалярное произведение двух диапа- зонов [firstl, lastl) и [first2, first2 + (lastl - firstl)). Заметим, что интерфейс inner_product несколько неуклюж. Всегда нужно задавать начальное значение init, потому что какой-то результат должен получиться, даже если диапазон [first, last) является пустым. Если, например, вы хотите вычислить обычное скалярное произведение двух массивов, то в качестве начального значения нужно задать 0. Версия 1 вычисляет скалярное произведение двух диапазонов плюс unit (сумми- рование начинается с init), то есть результат сначала устанавливается равным i nit, а затем для каждого итератора 1 в [firstl, lastl) в порядке от начала к концу диапа- зона результат изменяется по формуле result = result + (*i)* *(first2 + (i - firstl)) Версия 2 inner_product идентична версии 1 за исключением того, что использует два предоставленных пользователем функциональных объекта, а не operator* и operator*, то есть сначала результат устанавливается равным init, а затем для каж- дого итератора 1 в [firstl, lastl) в порядке от начала к концу диапазона изменяется по формуле result = binary_op1 ( result, binary_op2(*1. *(first2 + (i - firstl)) )• lie требуется, чтобы бинарные операции были ассоциативными или коммутатив- ными. Порядок всех операций строго определен. Где определено В реализации HP inner product был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовке<питег1с>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • -Inputlterator2 — модель Итератора Ввода.
12.10. Обобщенные числовые алгоритмы 287 # Т является Присваиваемым. • Если х является объектом типа Т, у является объектом типа значения Inputlteratorl, a z является объектом типа значения Inputlterator2, то выраже- ние х + у * z должно быть определено. • Тип х + у * z можно преобразовать к Т. Версия 2: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Т является Присваиваемым. • BinaryFunction! — модель Бинарной Функции. • BinaryFunction2 — модель Бинарной Функции. • Тип значения Inputlteratorl можно преобразовать к типу первого аргумента BinaryFunction2. • Тип значения Inputlterator2 можно преобразовать к типу второго аргумента BinaryFunction2. • Т можно преобразовать к типу первого аргумента BinaryFunction!. • Тип значения BinaryFunction2 можно преобразовать к типу второго аргумента BinaryFunction!. • Тип значения BinaryFunction! можно преобразовать кТ. Предусловия • [f i rst!, 1 ast!) — допустимый диапазон. • [first2, first2+ (last! - first!)) —допустимый диапазон. Оценка сложности Линейная. Ровно last! - f i rst! обращений к каждой бинарной операции. Пример int main() { mt А![] = {!, 2, 3), int А2[] = {4. !. -2}. const int N! = sizeof(A!) / sizeof(int); cout « "The inner product of A! and A2 is " « inner_product(A!, A! + Nl. A2, 0) . « endl. ’2.10.3. partial_sum (частичная сумма) template <class Inputiterator, class Outputlterator> Outputiterator partial_sum(Inputiterator first, Inputiterator last, Outputiterator result);
288 Глава 12. Основные изменяющие алгоритмы 2) template cclass Inputiterator, class Outputiterator, class BinaryFunction> Outputiterator partial_sum(lnputlterator first, Inputiterator last, Outputiterator result, BinaryFunction binary_op); Алгоритм partial_sum вычисляет обобщенную частичную сумму. Он присваивает зн ченис *first элементу * result, сумму *first и * (first + 1) — элементу *( result + / и т. д. Заметим, что result может быть тем же самым итератором, что и first Это полезно для вычисления частичных сумм “на месте”. Текущая сумма сначала устанавливается равной *first и присваивается элементу * result. Для каждого итератора i в [first + 1, last) в порядке от начала к концу сумма изменяется по формуле sum = sum+ *i (версия 1) или sum=binary_op(sum, *i) (версия 2) и присваивается элементу *( result + (i - first)). Не требуется, чтобы бинарная опе- рация была ассоциативной или коммутативной. Порядок всех операций строго опре- делен. Возвращаемое значение — конец выходного диапазона: result + (last - first). Где определено В реализации HP partial_sum был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовке <numeric>. Требования к типам Версия 1: • Irputlterator — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Если х и у являются объектами типа значения Inputlteratoг, то выражение х + у определено. • Тип выражения х + у можно преобразовать к типу значения InputIte ratoг. • Тип значения Inputlte rato г можно преобразовать к одному из набора типов зна- чений Outputiterator. Версия 2: • Inputlte ratoг — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • BinaryFunction — модель Бинарной Функции. • Тип значения Input Ite rato г можно преобразовать к типам первого и второго ар' гументов BinaryFunction. • Тип результата BinaryFunction можно преобразовать к типу значения Inputiterator. • Тип значения Inputlteratoг можно преобразовать к одному из набора типов зна ченпй Outputiterator. Предусловия • [first, last) — допустимый диапазон. • [result, result + (last - first)) — допустимый диапазон.
12 10 Обобщенные числовые алгоритмы 289 О11енка сложности нейная. Отсутствуют обращения к бинарной операции, если [f i rst, last) — пустой ;<япазон; в противном случае — ровно (last - first) - 1 обращений. дна* Пример int main() 1 const mt N = 10, int A[N] fill(A. A + N 1). cout « "A copy(A A + N ostream_iterator<int>(cout, ”)), cout << endl cout « "Partial sums of A partial_sum(A A + N, ostream_iterator<int>(cout, ” ')), cout « endl, 12.10.4. adjacent-difference (разность соседних элементов) 1) template cclass Inputiterator, class Outputlterator> Outputiterator adjacent_difference(lnputlterator first, Inputiterator last, Outputiterator result); 2) template <class Inputiterator, class Outputiterator, class BinaryFunction> Outputiterator adjacent_difference(Inputiterator first, Inputiterator last, Outputiterator result, BinaryFunction binary_op); Алгоритм adjacent_dif ference вычисляет разности соседних элементов в диапазоне Ifirst, last), то есть присваивает элементу * result значение *fi rst и для каждого ите- раторах в диапазоне [fi rst +1, last) присваивает элементу * (result + (i - f i rst)) раз- ность - 1). Заметьте, что result может быть тем же итератором, что и f i rst. Можно использо- вать adjacent-difference для подсчета разностей “на месте”. Значение первого элемента сохраняется вместе со значениями разностей, пото- ку что это дает достаточную информацию для восстановления входного диапазона, ели сложение и вычитание имеют обычные арифметические определения, то Jacent_dif ference и partial_sum взаимно обратны. версия 1 использует для подсчета разностей operate г-, а версия 2 — предоставлен- ную пользователем бинарную функцию. В версии 1 для каждого итератора 1 в диапа- Не [fi rst+ 1, last) значение выражения *i - *(i - 1) присваивается элементу *( result+ 'first)). В версии 2 значение присваивается элементу *( result + (i - f i rst)) вмес- °binary_op(*i, *(i-1)).
290 Глава 12. Основные изменяющие алгоритмы Где определено В реализации HP adjacent_dif ference был объявлен в заголовке <algo. h>. В соотВет ствии со стандартом C++ он объявлен в заголовке<питег1с>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • Output Iterate г — модель Итератора Вывода. • Если х и у являются объектами типа значения Forwarditerator, то выражение х - у определено. • Тип значения Input Ite ratoг можно преобразовать к одному из набора типов зна- чений Outputiterator. • Тип выражения х - у можно преобразовать к одному из набора типов значений Outputiterator. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • Outputiterator — модель Итератора Вывода. • BinaryFunction — модель Бинарной Функции. • Тип значения Inputiterator можно преобразовать к типам первого и второго аргументов BinaryFunction. • Тип значения Inputiterator можно преобразовать к одному из набора типов зна- чений Outputiterator. • Тип результата BinaryFunction можно преобразовать к одному из набора типов значений Outputiterator. Предусловия • [fi rst, last) — допустимый диапазон. • [result, result + (last - first)) — допустимый диапазон. Оценка сложности Линейная. Нет обращений к бинарной операции, если [f i rst, last) -- пустой диапа- зон; в противном случае — ровно (last - first) - 1 обращений. Пример В этом примере мы формируем разности соседних элементов в массиве целых чисел. а затем используем partial_sum для восстановления исходного массива. int main() { int А[ ] = {1. 4, 9, 16, 25, 36, 49, 64, 81, 100}, const int N = sizeof(A) / sizeof(int), int B[N], cout « ’A[]
12.10. Обобщенные числовые алгоритмы 291 сору(А- А + N, ostream_iterator<int>(cout, ’)); cout « endl; adjacent_difference(A, A+N, В), cout « ’’Differences ”. сору(В. В + N, ostream_iterator<int>(cout, " ”)), cout « endl, cout « ’’Reconstruct partial_sum(B, В + N, ostream_iterator<int>(cout, " ’’)), cout « endl.
13 Сортировка и поиск Класс наиболее важных алгоритмов в информатике связан с общей проблемой сор- тировки диапазона значений. В этой главе описаны алгоритмы STL, которые полно- стью или частично упорядочивают диапазоны либо работают на отсортированных диапазонах. Под “отсортированным” подразумевается такой диапазон, в котором элементы рас- положены в порядке возрастания, или, точнее, в неубывающем порядке, поскольку диапазон может содержать повторяющиеся элементы. Однако все алгоритмы, опи- санные в этой главе, полностью обобщены — есть версии, параметризованные функ- циональными объектами, — поэтому в каждом конкретном случае можно использо- вать необходимый метод упорядочения. Более того, говорить, что диапазон может включать в себя повторяющиеся элемен- ты, не совсем корректно. Более уместно утверждение, что в диапазоне могут содер- жат ься эквивалентные элементы. Понятие эквивалентности не равнозначно понятию равенства. Все алгоритмы в этой главе сравнивают элементы, используя строгое слабое упорядочение. И как уже говорилось при обсуждении концепции Строго Слабо Сравнимого (с. 104), всякий раз. когда применяется строгое слабое упорядочение, неявно вводится понятие эквива- лентности. Два элемента эквивалентны, если ни один из них не меньше другого. Если, например, вы сортируете список людей по фамилии, то два человека эквивалентны- если их фамилии совпали. Пи один из алгоритмов в этой главе не сравнивает элементы на равенство. 13.1. Сортировка диапазонов Данный раздел описывает алгоритмы сортировки STL, то есть алгоритмы, которыс упорядочивают диапазоны. Алгоритмы сортировки относятся к перестановочны*1 подобно алгоритмам в разделе 12.7, они перемещают значения в диапазоне, а не вв° дят новые значения. В дополнение к алгоритмам данного раздела STL содержит три другие Ф°Р'\ч сортировки. Во-первых, у шаблонов контейнерных классов list (с. 434) и slist (с. 4-^
13.1. Сортировка диапазонов 293 ь собственные функции so rt, которые упорядочивают весь контейнер Во-вторых, еяементы Сортированных Ассоциативных Контейнеров (с. 167) всегда упорядочены, ^третьих, отсортировать диапазон можно с помощью алгоритма “сортировки кучей” /heapsorl)- Сначала нужно создать кучу с помощью make_heap (с. 334), а затем преоб- разовать ее в отсортированный диапазон с помощью sort_heap (с. 339). Нет такого алгоритма сортировки, который был бы во всех случаях наилучшим. Поскольку сортировка очень важна и потенциально может занять много вычисли- тельного времени, нужно весьма осторожно выбирать алгоритм сортировки. Как пра- вило, sort оказывается лучшим выбором, но иногда больше подходят другие методы. 13.1.1. sort (сортировка) template <class RandomAccessIteratoO void sort(RandomAccess!terator first, RandomAccessIterator last); template <class RandomAccessIterator, class StrictWeakOrdering> void sort(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм sort сортирует элементы в диапазоне [first, last) в возрастающем поряд- ке (или, точнее, в неубывающем порядке). Это означает, что если i и j — любые разы- менуемые итераторы в диапазоне [first, last), такие что i предшествует ], то *j га- рантированно не меньше *1. Заметим, что sort не обязательно является стабильным (stable) алгоритмом сор- тировки. Предположим, что *1 и * j эквивалентны: ни один из них не меньше другого. Нет никакой гарантии, что sort сохранит относительный порядок этих элементов. Стабильность требуется только в том случае, когда диапазон содержит эквивален- тные, но не идентичные элементы,Ф) и когда порядок этих элементов в исходном диа- пазоне существенен. Стабильность важна также, когда сортируются записи с несколькими полями. Иногда необходимо сначала отсортировать по одному полю, а затем по другому, и тог- Даважно, чтобы вторая операция сортировки была стабильной. Алгоритм stable_sort (с. 295), как подсказывает его название, сохраняет относительный порядок эквива- лентных элементов. Две версии sort различаются способом определения меньшего элемента. Версия 1 сравнивает объекты с помощью operate г<, а версия 2 — с помощью функционального °бъекта comp. Постусловие версии 1 таково, что is_sorted( first, last) (с.304)истин- Но»аверсии 2 — is_sorted(first, last, comp) истинно. Где определено Реализации HP sort был объявлен в заголовке <algo. h>. В соответствии со стандар- т°м C++ он объявлен в заголовочном файле <algorithm>. требования к типам ВеРсия 1: RandomAccessIterator — модель Итератора Произвольного Доступа. 4‘i ^Пример. сортируется список людей по фамилии Две записи с одинаковыми фамилиями .жвива- !|Ы для целей сортировки, по, гак как имена могут не совпадать, они не обязательно равны
294 Глава 13. Сортировка и поиск • RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator является Строго Слабо Сравнимым. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • St rictWeakOrdering — модель Строго Слабо Сравнимого. • Тип значения RandomAccessIterator можно преобразовать к типу аргумент StrictWeakOrdering. Предусловия • [first, last) — допустимый диапазон. Оценка сложности В соответствии со стандартом C++ алгоритм sort в среднем обязан выполнить O(N log N) сравнений, а в худшем случае сложность будет O(N2), где Nравно last - first. Однако эта “свобода” не всегда необходима. Можно реализовать sort таким обра- зом, что и среднее и максимальное количество сравнений будет только О(ЛГ log N). Реализация STL в SGI, а также некоторые другие реализации, представляет именно такие гарантии. Ранняя версия sort имела квадратичную сложность в худшем случае, потому что был использован алгоритм быстрой сортировки (quicksort) [Ноа62], с ведущей точ- кой, выбираемой по медиане трех [Sin69]. Быстрая сортировка имеет среднюю слож- ность N, но квадратичную сложность в наихудшем случае. См. раздел 5.2.2 Кнута [Кпи98Ь]. Более поздние версии sort используют алгоритм внутренней сортировки (introsort) [Mus97], сложность которого в худшем случае O(N log N). Внутренняя сортировка очень похожа на быструю сортировку по медиане трех, и в среднем она не хуже. Пример Отсортировать диапазон целых чисел в возрастающем порядке. Это самый простои способ использования sort. int main() { mt A[] = {1. 4. 2, 8, 5, 7}; const int N = sizeof(A) / sizeof(int). sort(A. A + N), assert(is_sorted(A, A + N)); copy(A. A+N. ostream_iterator<int>(cout, ” ”)), cout << endl, // Результат 124578 } В предыдущем примере мы сортировали диапазон целых чисел в порядке возра^3 ния. Точно так же можно отсортировать диапазон в порядке убывания. Мы все
13.1. Сортировка диапазонов 295 лИ1ПЬ передадим соответствующий функциональный объект в версию 2 алгоритма oft Функциональный объект g reate г (с. 381) является Строгим Слабым Упорядоче- нием» а значит, подходит для этой цели. По определению если диапазон отсортиро- ван с помощью g геате г, то он отсортирован в порядке убывания. int main() { vector<string> fruits, fruits push_back("apple"), fruits push_back("banana’ ), fruits push_back("pear"), fruits push_back("grapefruit"), fruits push_back("cherry"); fruits push_back("orange"), fruits push_back("watermelon”), fruits push_backC mango"), sort(fruits begin(), fruits end(), greater<stnng>()), assert(is_sorted(fruits begin(). fruits.end(), greater<string>())), copy(fruits begin(), fruits end(), ostream_iterator<string>(cout, '‘\n”)), } 13.1.2. stable_sort (стабильная сортировка) 1) template <class RandomAccessIterator> void stable_sort(RandomAccessIterator first, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> void stable_sort(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм stable_sort очень похож на sort (с. 293). Он сортирует элементы диапазо- "’[first, last) в возрастающем (точнее, неубывающем) порядке. Между stable_so rt и so rt есть два важных отличия. Во-первых, у них разная слож- ить выполнения. Обычно sort намного быстрее. Во-вторых, как подсказывает на- Звание алгоритма, stable_sort является стабильным алгоритмом. Он сохраняет от- носительный порядок эквивалентных элементов. Если х и у — элементы из диапазона l rst, last), такие что х предшествует у, и если эти элементы эквивалентны (ни один п «их не меньше другого), то stable_sort имеет дополнительное постусловие кроме ^Условий sort: х по-прежнему предшествует у. нрантия стабильности чрезвычайно важна, потому что при некотором упорядоче- Два элемента могут быть эквивалентны, не являясь при этом идентичными. Рас- трим распространенный пример: упорядочение последовательности Ф. И. О. по фа- ИИ ^сли У Д2 * * * * * ВУХ человек одинаковая фамилия, но разные имена, то их записи 1^ь^алентиы с точки зрения сравнения по фамилии (ни одно из них “не меньше” дру- г ” Но» тем не менее, они не равны. Это одна из причин, по которой stable_sort ино- ^Ывает полезен. Если вы сортируете последовательность записей с несколькими
296 Глава 13. Сортировка и поиск разными полями, то можно отсортировать записи по одному полю, не нарущая По док, который ранее был достигнут при сортировке по другому полю. Например, но выполнить сортировку по имени, а затем запустить алгоритм стабильной сорт*' ровки по фамилии. Две версии stable_so rt различаются методом определения меньшего элемента, сия 1 сравнивает объекты с помощью operatorc, а версия 2 — с помощью функ нального объекта comp. Вер. Нио- Постусловие в случае версии 1 таково, что is_so версии 2 — is_sorted(first, last, comp) истинно, эквивалентные элементы после выполнения stable_sort имеют тот же относитель- ный порядок, что и до выполнения. С точки зрения реализации, stable_sort основан на алгоритме сортировки слияни- ем (см. с. 315 и раздел 5.2.4 Кнута [Knu98b]). rted(first, last) истинно,авслуча. Постусловие обеих версий таково Где определено В реализации HP stable_sort был объявлен в заголовке <algo.h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algonthm>. Требования к типам Версия 1: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIteratoг — изменяемый итератор. • Тип значения RandomAccessIterator является Строго Слабо Сравнимым. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента St rictWeakOrde ring. Предусловия • [fi rst, last) — допустимый диапазон. Оценка сложности В отличие от sort, алгоритм stable_sort является адаптивным. Он пытается выДс лить временный буфер памяти, и время его выполнения (сложность) зависит от ко личества доступной памяти. В худшем случае (если дополнительной памяти совсем нет) потребуется O(N(logN)2) сравнений, где Nравно last - fi rst. а в лучшем сД^ чае (если доступен достаточно большой буфер дополнительной памяти) O(N log N). Как sort, так и stable_sort имеют одинаковую оценку сложное?1’ O(Nlog N), но время работы sort меньше на некоторый константный коэффицпе Пример чТС Упорядочить последовательность символов, игнорируя регистр букв. Заметим, оз носительный порядок символов, отличающихся только регистром, сохраняется-
13.1. Сортировка диапазонов 297 inline bool lt_nocase(char c1, char c2) { return tolower(cl) < tolower(c2); } int main() { char A[] = "fdBeACFDbEac', const int N = sizeof(A) - 1 stable_sort(A, A + N, lt_nocase). cout « A << endl, // Результат AaBbCcdDeEfF } Предположим, что vector содержит список элементов, в котором каждый имеет соот- ветствующий приоритет. Мы хотим упорядочить элементы по приоритету, но в слу- чаеодинаковых приоритетов элементы, поступившие раньше, не должны терять сво- ей очередности. Этот пример может иллюстрировать задания в очереди (пакетная обработка) или сообщения, передаваемые системе управления окнами. class job { public: enum priority_code {standby, normal, high, urgent}, job(string id, prionty_code p - normal) nam(id), pri(p) {} string name() const { return nam, } priority_code priorityO const { return pri, } private string nam, priority_code pri, >; ostream& operator«(ostream& os, const job& j) { os « j name() « return os. (" « j priorityO « )", bool operator<(const job& j1, const job& j2) { return j! priorityO > j2 priorityO, int main() { vector<job> jobs. Jobs push_back(job("Long computation", job standby)), Jobs push_back(job(’System reboot", job urgent)), jobs push_back(jobC’Print )). Jobs push_back(job("Another long computation", job standby)),
298 Глава 13. Сортировка и поиск jobs.pubh_back(jub(”Copy file”)), stable_sort(jobs begin(), jobs end()), copy(jobs begin(), jobs end() ostream_iterator<job>(cout, "\n”)). 13.1.3. partial-sort (частичная сортировка) 1) template <class RandomAccessIterator> void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм partial_sort переставляет элементы диапазона [first, last) таким обра- зом, чтобы наименьшие middle - first из них были отсортированы по возрастанию. Постусловие алгоритма partial_sort таково, что наименьшие middle - first эле- ментов из входного диапазона будут собраны в поддиапазоне [f i rst, middle) и отсор- тированы по возрастанию. Оставшиеся last - middle элементов из входного диапазо- на будут собраны в неопределенном порядке в диапазоне [middle, last). Заметим, что при вызове sort также гарантируется, что наименьшие Nэлементов будут собраны и отсортированы по возрастанию в диапазоне [ f i rst,f i rst + N). Един- ственной возможной причиной предпочтения алгоритма partial_sort алгоритму sort является эффективность: выбрать наименьшие N элементов быстрее, чем сортиро- вать весь диапазон. Две версии pa rtial_so rt различаются методом определения меньшего из двух эле- ментов. Версия 1 сравнивает объекты с помощью operators а версия 2 — с помощью функционального объекта comp. Формально, постусловие версии 2 таково: если i и j — любые два допустимых ите- ратора в диапазоне [f i rst, middle), такие что i предшествует j, и если к — допустимый итератор в диапазоне [middle, last), то выражения *j < *1 и *к < *1 ложны. Соответ- ствующее постусловие версии 2: оба выражения comp(*j, *i) и comp(*k, *1) ложны. Таким образом, первые middle - first элементов расположены в порядке возраста- ния, и нет таких элементов в диапазоне [middle, last), которые меньше хотя бы одно- го элемента в диапазоне [first, middle). Алгоритм partial_sort реализован с помощью алгоритма сортировки кучеи (heapsort). См. работу Вильямса [Wil64] и раздел 5.2.3 Кнута [Кпи98Ь]. Где определено В реализации HP partial_sort был объявлен в заголовке <аIgo. h>. В соответствии стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • RandomAccessIterator — модель Итератора Произвольного Доступа.
13.1. Сортировка диапазонов 299 9 RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator является Строгим Слабо Сравнимым. версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия • [f i rst, middle) — допустимый диапазон. • [middle, last) — допустимый диапазон. Из этих двух условий следует, что [first, last) также является допустимым диапа- зоном. Оценка сложности Приблизительно (last - first) х log(middle - first) сравнений. Пример Найти пять наименьших значений в массиве целых. Эти пять элементов от А[0] до А[4] должны быть отсортированы по возрастанию, остальные элементы не отсор- тированы. int main() { mt А[] = {7, 2, 6, 11, 9, 3. 12, 10, 8, 4. 1. 5}; const int N = sizeof(A) / sizeof(int); partial_sort(A, A + 5. A+N), copy(A. A + N. ostream_iterator<int>(cout, ” ”)), cout « endl, // Результат 1 2 3 4 5 11 12 10 9 8 7 6 общем случае выражение pa rtial_sort(first, middle, last) означает: взять middle - lrst наименьших элементов из диапазона [first, last), поместить их в диапазон I lrst, middle) и отсортировать [first, middle) в порядке возрастания. Существует очевидных специальных случая: middle == f i rst и middle == last. с-сли middle == first, to partial_sort, собственно, ничего не делает: нуль наимень- х элементов он помещает в (пустой) диапазон [first, first), а затем оставшиеся ость все) элементы — в неопределенном порядке в диапазон [f i rst, last). Таким Разом, partial_sort(first, first, last) гарантирует только, что диапазон [first, -у будет “переставлен” в некотором произвольном порядке. Специальный случай middle == last более интересен, last - first наименьших бе- .УТ'ея из диапазона [first, last), помещаются в диапазон [first, last), и диапазон rst, last) сортируется по возрастанию. Другими словами, выражение
300 Глава 13. Сортировка и поиск рнН iAl_sorr(f । гч1 Iasi, last) сортирует весь диапазон [first, last), int main() { int A[] - {7, 2, 6, 11, 9, 3, 12, 10, 8, 4, 1, 5}, const int N = sizeof(A) / sizeof(int), partial_sort(A A A + N), copy(A A + N ostream_iterator<int>(cout, ” )). cout << endl, partial_sort(A, A + N, A + N), copy(A, A + N, ostream_iterator<int>(cout, )), cout << endl, // Результат 123456789 10 11 12 Есть ли причины такого использования partial_sort? Нет, если используется версия STL, где sort (с. 293) реализован на основе алгоритма внутренней сортировки При сортировке диапазона из Nэлементов у обоих алгоритмов, sort и partial_sort, оцен- ка сложности O(A4og N), но sort обычно как минимум в два раза быстрее. Однако если используется более старая версия STL, где sort реализован на основе алгоритма быстрой сортировки, то существуют случаи, когда можно рассмотреть ис- пользование pa rt ial_so rt вместо so rt для сортировки всего диапазона. Обычно слож- ное гь быстрой сортировки O(NlogN), в редких случаях — O(2V2). 13.1.4. partial_sort_copy (частичная сортировка с копированием) 1) template <class Inputiterator, class RandomAccessIterator> RandomAccessIterator partial_sort_copy(lnputlterator first, Inputiterator last, RandomAccessIterator result-first, RandomAccessIterator result-last); 2) template <class Inputiterator, class RandomAccessIterator, class StrictWeakOrdering> RandomAccessIterator partial-sort_copy(lnputlterator first, Inputiterator last, RandomAccessIterator result-first, RandomAccessIterator result-last, StrictWeakOrdering comp); Как подсказывает имя, алгоритм partial_sort_copy является копирующей верен011 алгоритма partial_sort. Однако он не подчиняется обычному соглашению для копя рующих версий алгоритмов STL, поскольку не является прямым аналогом вызо^ алгоритма сору, азатем partial_sort. Скорее, partial_sort_copy использует диапазон [result_first, result_last) так же, как partial_sort использует диапазон [firS" middle)
13.1. Сортировка диапазонов 301 ддгоритм partial_sort_copy копирует ЛГнаименьших элементов из диапазона[fi rst, st) в диапазон [result_first, result-first + N), r^eN— это либо last - first, либо ^ultj-ast result_f i rst — меньшее из двух. Элементы, которые копируются в диа- fla30H[reSLJlt-flrst> result_first +N), отсортированы в порядке возрастания. розврашаемое значение — result_first + N. Две версии pa rtial_so rt_copy различаются методом определения меньшего элемен- Версия 1 сравнивает объекты с помощью ope ratorc, а версия 2 — с помощью функ- ционального объекта comp. Постусловие версии 1 таково: is_sorted( result_f i rst, result J-ast) истинно, а версии 2 — is_sorted( result_f i rst, result-last, comp) истинно. Где определено В реализации HP partial_sort_copy был объявлен в заголовке <algo h>. В соответ- ствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputiterator — модель Итератора Ввода. • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • Типы значения Inputiterator и RandomAccessIterator одинаковы. • Тип значения RandomAccessIterator является Строго Слабо Сравнимым. Версия 2: • Inputiterator — модель Итератора Ввода. • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • Типы значения Inputiterator и RandomAccessIterator одинаковы. • StrictWeakOrdering — модель Строго Слабо Сравнимого. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StrictWeakOrdenng. Предусловия • [first, last) — допустимый диапазон. • [result_first, result_last)— допустимый диапазон. Диапазоны [first, last) и [result_first, result_last) не перекрываются. Оценка сложности Приблизительно (last - first) х logATсравнений, где N — либо last - first, либо result_iast - result_fi rst — меньшее из двух. Пример Скопировать четыре наименьших значения из массива целых в vector. Мы не можем & Пользовать partial_sort_copy для копирования результата непосредственно стандартный вывод, потому что partial_sort_copy требует, чтобы его выходной ди- *пазон был задан Итераторами Произвольного Доступа.
302 Глава 13. Сортировка и поиск int main() { mt А[] = {7, 2, 6, 11. 9. 3. 12. 10, 8, 4, 1, 5}. const int N = sizeof(A) / sizeof(int), vector<int> V(4); partial_sort_copy(A, A + N, V begin(), V end()). copy(V begin(), V end(), ostream_iterator<int>(cout. *’ ”)). coot « endl. // Результат 1234 } 13.1.5. nth_element (n-й элемент) 1 ) template <class RandomAccessIterator void nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last); 2 ) template <class RandomAccessIterator, class StrictWeakOrdering> void nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм nth_element похож на partial_sort (c. 298), поскольку тоже частично упо- рядочивает диапазон элементов, а именно размещает элементы в диапазоне [first, last) так, что итератор nth указывает на элемент, который находился бы на этом мес- те в случае упорядочения всего диапазона [f i rst, last). Кроме того, итератор nth ис- пользуется для разбиения диапазона. Это гарантирует, что ни один из элементов в диапазоне [nth, last) не меньше любого элемента из диапазона [f i rst, nth). Отличие nth_element от partial_sort в следующем: диапазоны [first, nth) и [nth, last) не обязательно являются отсортированными. Гарантируется только то, что эле- менты из диапазона [first, nth) меньше (точнее, не больше), чем элементы в [nth, last). В этом смысле nth_element больше похож на partition (с. 276), чем на sort или partial_sort. Так как nth_element обеспечивает меньшие гарантии, чем partial_sort, то вполне объяснимо, что он работает быстрее, и это главная причина для использования nth_element вместо partial_sort. Две версии nth_element различаются методом определения меньшего элемента. Версия 1 сравнивает объекты с помощью operator^ а версия 2 — с помощью функци- онального объекта comp. Формально, постусловие версии 1 состоит в том, что не существует такого итера- тора 1 в диапазоне [f i rst, nth), что *nth < *i, и не существует такого итератора j в диапа- зоне [nth + 1, last), что *j < *nth . Аналогично, постусловие для версии 2 состоит в том, что не существует такого ите ратора 1 в диапазоне [first, nth), что comp(*nth, *1) истинно, и не существует такого итератора j в диапазоне [nth + 1, last), что comp( *j, *nth) истинно. Где определено В реализации HP nth_element был объявлен в заголовке <algo h>. В соответствии стандартом C++ он объявлен в заголовочном файле <algorithm>.
13.1. Сортировка диапазонов 303 fpe6oRaH ия к типам * \ RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator является Строго Слабо Сравнимым. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StnctWeakOrdering. Предусловия • [fi rst, nth) — допустимый диапазон. • [nth, last) — допустимый диапазон. Отсюда следует, что [f i rst, last) также является допустимым диапазоном. Оценка сложности В среднем линейно зависит от last - f i rst. Заметим, что это существенно меньше, чем сложность выполнения partial_sort. Если дополнительные гарантии partial_sort не важны для вашего приложения, луч- ше использовать вместо него nth_element. Пример Дан массив А из 12 целых чисел. Изменить порядок элементов в массиве так, чтобы: • элемент А[ 6 ] имел такое значение, как если бы весь массив был отсортирован; • элементы отА[0]доА[5] были меньше, чем элементы отА[6]доА[11]. int main { mt А[] = {7. 2. 6, 11, 9. 3, 12, 10, 8. 4. 1, 5}; const int N = sizeof(A) / sizeof(int), nth_element(A, A + 6, A + N), С°РУ(А, A + N, ostream_iterator<int>(cout, " ”)); cout « endl. } Результат может быть таким: 5 2 6 1 4 3 7 8 9 10 11 12 Или любым из многих других возможных вариантов. Гарантировано только то, что Массив разделен на две части и ни один из элементов первой части не больше, чем лЮбой элемент из второй части. Внутренний порядок в диапазонах [А, А + 6) и [А + 7. *12) не важен.
304 Глава 13. Сортировка и поиск 13J.6. is_sorted (является отсортированным) 1) template <class Fo rwa rdlte rato r> bool is_sorted(Forward!terator first, Forwarditerator last) 2) template <class Forwarditerator, class StrictWeakOrdering> bool is_sorted(ForwardIterator first, Forwarditerator last, StrictWeakOrdering comp) Алгоритм is_sorted проверяет, является ли диапазон отсортированным. Он возвра. щает true, если диапазон [first, last) отсортирован в порядке возрастания, и f alSe в противном случае. Входной диапазон не изменяется. Алгоритм is_sorted служит предусловием и/или постусловием многих алгорит- мов в этой главе Две версии is_sorted различаются методом определения меньшего элемента. Вер- сия 1 сравнивает объекты с помощью operator*;, а версия 2 — с помощью функцио- нального объекта comp. Формально, версия 1 возвращает истинность утверждения, что для любых двух итераторов 1 и j в диапазоне [first, last), таких что 1 предшествует j, выражение < *1 ложно. Версия 2 возвращает истинность утверждения, что для любых таких итераторов ложно comp( * j, *1). Если [first, last) является пустым диапазоном, то есть first == last, обе версии is_sorted возвращают true. Пустой диапазон всегда тривиально отсортирован. Где определено Алгоритм is.sorted отсутствует в оригинальной реализации HP STL и в стандарте C++. В реализации SGI он объявлен в заголовке <algorithm>. Требования к типам Версия 1: • Fo rwa rdlte rate г — модель Однонаправленного Итератора. • Тип значения Forwarditerator — модель Строго Слабо Сравнимого. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Forwarditerator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Для непустого диапазона не более (last - fi rst) - 1 сравнений. Пример int main() { mt A[] = {1, 4. 2 8, 5. 7},
13.2 Операции над отсортированными диапазонами 305 const int N = sizeof(A) / sizeof(int), assert(is_sorted(A, A)), assert(is_sorted(A A, greater<int>())). assert(1is_sorted(A, A f N)), assert(1is_sorted(A, A + N, greater<int>())), sort(A A + N), assert(is_sorted(A, A + N)), assert('is_sorted(A, A + N, greater<int>())), sort(A A + N, greater<int>()), assert(1is_sorted(A, A + N)). assert(is_sorted(A A + N greater<int>())), 13.2. Операции над отсортированными диапазонами Алгоритмы сортировки из раздела 13.1 очень важны, поскольку многие алгоритмы работают с отсортированными диапазонами. Сортировка упрощает поиск конкрет- ного значения, объединение с другим отсортированным диапазоном и многое другое. 13.2.1. Двоичный поиск Каждый, кто когда-либо работал со словарем, понимает достоинство отсортирован- ного диапазона: в нем легко можно что-либо найти. Если диапазон из N элементов совсем не отсортирован, то поиск элемента в нем является линейной операцией. Как мы видели в случае с find (с. 208), может понадобиться проверить каждый элемент в диапазоне. Если диапазон отсортирован, то поиск элемента в нем является только логарифмической операцией. Метод двоичного поиска (binary search) работает после- довательным делением отсортированного диапазона пополам. STL содержит четыре различных алгоритма двоичного поиска, которые предназ- начены ответить на разные вопросы. Самый основной (но в действительности не са- йый полезный) — содержится ли определенный элемент в заданном диапазоне. На этот вопрос отвечает алгоритм binary search. В более общем случае, однако, такой информации недостаточно. Если элемент уже содержится в диапазоне, вам, вероятно, понадобится знать, где он находится, а если Эт°го элемента в диапазоне нет, то полезно знать, где бы он находился, если бы был там. Это базовые принципы трех алгоритмов: lower_bound, upper_bound и equal_range. КИХ алгоритмов три, а не один, потому что элемент, который вы ищете, может встре- ться в диапазоне более одного раза. Если надо найти число 17, а в массиве четыре Пии этого числа, то какую из них следует вернуть: первую, последнюю или весь Речень элементов, равных 17? Этим трем вариантам выбора соответствуют алго- ТМы lower_bound, upper_bound и equal_range. В большинстве случаев lower_bound ^Вает наиболее подходящим.
306 Глава 13. Сортировка и поиск 13.2.1.1. binary_search (двоичный поиск) 1 ) template <class Forwarditerator, class StrictWeaklyComparable> bool binary_search(ForwardIterator first, Forwarditerator last. const StrictWeaklyComparableA value); 2 ) template <class Forwarditerator, class T, class StrictWeakOrdering> bool binary_search(ForwardIterator first, Forwarditerator last, const T& value, StrictWeakOrdering comp); Алгоритм binary_search — одна из версий двоичного поиска. Он пытается найти эле- мент value в отсортированном диапазоне [first, last). В результате возвращается true, если в диапазоне [first, last) существует элемент, эквивалентный value и false, если такого элемента не существует. Простой результат булевского типа может оказаться недостаточным. Другие алго- ритмы из раздела 13.2.1 — lower_bound, upper_bound и equal_range — выполняют дво- ичный поиск и предоставляют дополнительную информацию. Версия 1 использует для сравнения operator*:, а версия 2 — функциональный объект comp. Формально версия 1 возвращает истинность утверждения, что в диапазоне [first, last) существует итератор 1, для которого ложны оба неравенства *i < value и value < *i. Версия 2 возвращает истинность утверждения, что в диапазоне [f i rst, last) существу- ет итератор 1, для которого ложны оба выражения comp(*i, value) и comp (value, *i). Где определено В реализации HP binary_search был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • St rictWeaklyComparable — модель Строго Слабо Сравнимого. • Тип значения Forwarditerator совпадает с типом St rictWeaklyComparable. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Forwarditerator совпадает с типом Т. • Тип значения Forwarditerator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия Версия 1: • [fi rst, last) — допустимый диапазон. • Диапазон [f i rst, last) отсортирован по возрастанию с помощью operators то есТЬ is_sorted(first, last) истинно.
13.2. Операции над отсортированными диапазонами 307 аерсия 2: • [first, last) — допустимый диапазон. • Диапазон [f i rst, last) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то есть is_sorted(first, last, comp) истинно. Оценка сложности Количество сравнений логарифмическое: не более log(last - first) + 2. Если тип Forwarditerator является Итератором Произвольного Доступа, то количество шагов 0 диапазоне тоже логарифмическое, иначе говоря, количество шагов пропорциональ- но last - first. Оценка сложности для случая Итераторов Произвольного Доступа отличается от других типов итераторов, потому что advance (с. 192) для Итераторов Произвольного Доступа выполняется за константное время, а для Однонаправленных Итераторов — за линейное. Пример Поиск элементов в отсортированном массиве целых чисел. int main() { int А[] = { 1 2, 3, 3, 3, 5. 8 }; const int N = sizeof(A) / sizeof(int): for (int i=1, i <= 10, ++i) cout « "Searching for " « i « (binary_search(A, A + « endl, } { « ". " N, i) ? "present" "not present") Результат: Searching Searching Searching Searching Searching Searching Searching Searching Searching Searching for 1 present for 2- present for 3 present for 4 not present for 5 present for 6 not present for 7 not present for 8 present for 9 not present for 10 not present 13 9 • *•1.2. lower_bound (поиск первого возможного вхождения) ' template <class Forwarditerator, class StrictWeaklyComparable> Forwarditerator lower_bound(Forwarditerator first, Forwarditerator last, const StrictWeaklyComparable& value);
308 Глава 13. Сортировка и поиск 2) template <class Forwarditerator, class T, class StrictWeakOrdering> Forwarditerator lower_bound(Forwarditerator first, Forwarditerator last, const T& value, StrictWeakOrdering comp); Алгоритм lower_bound является одной из версий двоичного поиска: он пытается най ти элемент value в отсортированном диапазоне [first, last). Если элемент, эквцВа лентный value, уже присутствует, то lower_bound возвращает итератор, указывающей на первый такой элемент. Если подобных элементов нет, то возвращается позиция где он должен бы быть, если бы входил в диапазон, то есть возвращается итератор указывающий на первый элемент, не меньший, чем value, или last, если value больше любого элемента в [first, last). Если несколько иначе взглянуть на lower_bouna,то можно сказать, что он возвращает первую позицию, на которую можно было бы по- ставить value, не нарушая упорядоченности. Версия 1 использует для сравнения operator^ а версия 2 — функциональный объект comp. Формально версия 1 возвращает самый дальний итератор 1 в диапазоне [first, last), такой что для каждого итератора j в диапазоне [first, 1) выполняется условие -2 < value. Аналогично, версия 2 возвращает самый дальний итератор 1 в диапазоне |nrst, last), такой что для каждого итератора j в диапазоне [first, 1) выражение comp(*j , value) истинно. Где определено В реализации HP lower_bound был определен в заголовке <algo. h>. В соответствии со стандартом C++ он определен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • St rictWeaklyComparable - модель Строго Слабо Сравнимого. • Тип значения Forwarditerator совпадает с типом St rictWeaklyComparable. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Гип значения Fo rwa rdIte rato г совпадает с типом T. • Тип значения Forwarditerator можно преобразовать к типу аргумент3 St rictWeakOrdering. Предусловия Версия 1: • [first, last) — допустимый диапазон. • Диапазон [first, last) отсортирован по возрастанию с помощью operator то есть is_sorted(first, last) истинно. Версия 2: • [fi rst, last) — допустимый диапазон.
13.2 Операции над отсортированными диапазонами 309 • Диапазон [f i rst, last) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то есть is_sorted( first, last, comp) истинно. Оценка сложности количество сравнений логарифмически зависит от размера диапазона: не более ]og(last - first) ч- 1. Если тип Forwarditerator является Итератором Произвольного доступа, то количество шагов внутри диапазона также имеет логарифмическую зави- симость, в противном случае количество шагов пропорционально last - first. Сложность выполнения для Итераторов Произвольного Доступа отличается от слож- йости выполнения для других типов итераторов, потому что advance (с. 192) выпол- няется за константное время для Итераторов Произвольного Доступа и за линейное время для Однонаправленного Итератора. Пример int main() { int А[] -{1.2 3, 3 3. 5. 8}. const int N - sizeof(A) / sizeof(int), for (int i-1. i <- 10. ++i) { int* p - lower_bound(A, A + N, i), cout << Searching for ” « i « " ", cout « Result index = << p - A « ", ". if (p '= A + N) cout << "A[" « p - A « "] == " « *p « endl, else cout << ‘which is off-the-end " << endl, } } Результат: Searching for 1 Result index - 0, A[0] == 1 Searching for 2 Result index = 1, A[1] == 2 Searching for 3 Result index = 2. A[2] == 3 Searching for 4 Result index = 5. A[5] == 5 Searching for 5 Result index - 5 A[5] == 5 Searching for 6 Result index = 6. A[6] == 8 Searching for 7 Result index = 6 A[6] == 8 Searching for 8 Result index = 6 A[6] == 8 Searching for 9 Result index = 7 which is off-the-end Searching for 10 Result index - = 7 which is off-the-end горитм lower_Dound очень похож на функцию bsearch библиотеки Си. иДно из главных отличий в том, что происходит в случае отсутствия искомого зна- Ия в Диапазоне: lower bound возвращает позицию, где это значение должно было Ка Нах0Д11ТЬСЯ> если бы оно присутствовало, a bsearch возвращает нулевой указатель Признак неудачного поиска.
310 Глава 13. Сортировка и поиск Если искомое значение val отсутствует, то lower_bound возвратит либо last, итератор 1, такой что val < *i. Это означает, что можно легко написать обертк lower_oound, которая придаст ему интерфейс, похожий на bsearch. либ0 У Для template <class Forwarditerator, class StrictWeaklyComparable> Forwarditerator stl_bsearch(ForwardIterator first. Forwarditerator last, const StnctWeaklyComparable& value) { Forwarditerator loc = lower_bound(first, last, value), return loc == last || value < «loc 9 last loc. } template <class Forwarditerator, class T, class StnctWeakOrdering> Forwarditerator stl_bsearch(ForwardIterator first, Forwarditerator last. const T& value, StrictWeakOrdering comp) { Forwarditerator loc = lower_bound(first, last, value, comp), return loc == last || comp(value, *loc) 9 last . loc. } 13.2.1.3. upper_bound (поиск последнего нужного элемента) 1) template cclass Forwarditerator, class StrictWeaklyComparable> Forwarditerator upper_bound(Forwarditerator first, Forwarditerator last, const StrictWeaklyComparable& value); 2) template <class Forwarditerator, class T, class StrictWeakOrdering> Forwarditerator upper_bound(Forwarditerator first, Forwarditerator last, const T& value, StrictWeakOrdering comp); Алгоритм upper_bound — одна из версий двоичного поиска. Он пытается найти в от- сортированном диапазоне ff i rst, last) элемент, эквивалентный value. Конкретно, он возвращает последнюю позицию, куда можно было бы вставить value без нарушения упорядоченности элементов. Поскольку диапазоны не симметричны в отношении начала и конца (диапазон [first, last) включает в себя first, но не включает last), значение, возвращаемо6 upper_bound, имеет совсем другой смысл, чем значение, возвращаемое lower_bound. Если ищется значение, которое присутствует в диапазоне, lower_bound возвращает итера тор, указывающий на этот элемент. Алгоритм upper bound делает иначе. Он возвра щает последнюю позицию, где можно вставить value без нарушения упорядоченности- Если value присутствует, то на него указывает итератор, предшествующий итераЮ ру, возвращенному алгоритмом upper_bound, а не сам этот возвращенный итератор Версия 1 использует для сравнения operator^ а версия 2 — функциональный объеь comp. Формально версия 1 возвращает самый дальний итератор i в диапазоне [first, laSL такой что для каждого итератора j в диапазоне [f i rst, i) выражение value < -J лоЖ^- Аналогично, версия 2 возвращает самый дальний итератор i в диапазоне [first, laSL такой что для каждого итератора j в диапазоне [first, i) выражение comp(value, ложно.
13.2. Операции над отсортированными диапазонами 311 Рд® определено о реализации HP upperbound был определен в заголовке <algo. h>. В соответствии со стандартом C++ он определен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • StrictWeaklyComparable — модель Строго Слабо Сравнимого. • Тип значения Forwarditerator совпадает с типом St rictWeaklyComparable. Версия 2: • Forwarditerator — модель Однонаправленного Итератора. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Forwarditerator совпадает с типом Т. • Тип значения Forwarditerator можно преобразовать к типу аргумента St rictWeakO rde ring. Предусловия Версия 1: • [first, last) — допустимый диапазон. • Диапазон [f i rst, last) отсортирован с помощью operator<, то есть is.sorted( f i rst, last) истинно. Версия 2: • [first, last) — допустимый диапазон. • Диапазон [first, last) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то ecTbis_sorted( first, last, comp) истинно. Оценка сложности Логарифмическое количество сравнений: не более log(last - first) + 1. Если тип Forwarditerator является Итератором Произвольного Доступа, то количество шагов внутри диапазона также имеет логарифмическую зависимость. В противном случае количество шагов пропорционально last - f i rst. Сложность выполнения отличается для Итераторов Произвольного Доступа по срав- нению с другими типами итераторов, потому что advance (с. 192) выполняется за Константное время для Итераторов Произвольного Доступа и за линейное время для ^направленных Итераторов. ^Ример int main() { int А[] = { 1. 2. 3. 3. 3. 5. 8 }. const int N = sizeof(A) / sizeof(int); f0r (int 1=1. i <= 10, ++i) { int* р = upper_bound(A. A+N, i), cout << "Searching for " « i « ”
312 Глава 13. Сортировка и поиск cout << Result index = ' << р - А « " if (р 1= А + N) cout << А[ << р - А << ] == ’ << *р << endl. else cout << which is off-the-end << endl Результат: Searching for 1 Result index = 1 A[1] == 2 Searching for 2 Result index = 2. A[2] == 3 Scaiching for 3 Result index = 5. A[5] == 5 Searching for 4 Result index = 5 A[5] == 5 Searching for 5 Result index = 6, A[6] == 8 Searching for 6 Result index = 6, A[6] == 8 Searching for 7 Result index - 6, A[6] == 8 Searching for 8 Result index = 7, which is off-the-end Searching for 9 Result index = 7, which is off-the-end Searching for 10 Result index - 7, which is off-the-end 13.2.1.4. equal_range (поиск интервала возможного вхождения) 1) template <class Forwarditerator, class StrictWeaklyComparable> pair<Forwardlterator, Forwardlterator> equal_range(Forwarditerator first, Forwarditerator last, const StrictWeaklyComparable& value); 2) template <class Forwarditerator, class T, class StrictWeakOrdering> pair<ForwardIterator, Forwardlterator> equal_range(Forwarditerator first, Forwarditerator last, const T& value, StrictWeakOrdering comp); Алгоритм equal_range — одна из версий двоичного поиска, которая ищет элемент value в отсортированном диапазоне [f i rst, last). Значение, возвращаемое equal_range, является по существу комбинацией значе- ний, возвращаемых lower_bound и upper bound. Существуют i и j — пара итераторе2 * * * 6’ причем 1 — первая позиция, куда можно было бы вставить value без нарушения упо рядоченности элементов, a j — последняя такая позиция. Как следствие, каждый элемент в диапазоне [i, j) эквивалентен value, и этот дна пазон [i, j) является максимальным поддиапазоном с подобным свойством в [firs^ last). Есть несколько другой взгляд на equal_range: рассмотрим диапазон всех элемеН тов в [first, last), которые эквивалентны value. (Поскольку [first, last) отсортнр^ ван, то все элементы, эквивалентные value, должны соседствовать друг с другом )г горитм lower bound возвращает первый итератор в диапазоне, алгоритм ирре r_b°b^ возвращает итератор, указывающий за последний элемент, а алгоритм equal-га' - возвращает пару этих итераторов. Предложенное описание имеет смысл, даже если [ f i rst, last) не содержит элсМе тов, эквивалентных value. В таком случае диапазон подобных элементов — пуст°
13.2. Операции над отсортированными диапазонами 313 чествует только одна позиция, куда можно вставить value без нарушения упоря- доченности диапазона, следовательно, значение, возвращаемое equal_range, является парой pair, оба элемента которой являются итераторами, указывающими на эту по- зИцИЮ. Версия 1 использует для сравнения operator^ а версия 2 — функциональный объект comp- формально версия 1 возвращает пару итераторов [i, j), где i — самый дальний итератор в диапазоне [first, last), такой что для каждого итератора к в диапазоне [first, i) выполняется условие *к < value, a j — самый дальний итератор в диапазоне [first, last), такой что для каждого итератора к в диапазоне [first, j) выражение value < *к ложно. Для каждого итератора к в [i,j) как выражение value < *k, так и вы- ражение *k < value равны false. Аналогично, версия 2 возвращает пару итераторов [i, j), где i — самый дальний итератор в диапазоне [first, last), такой что для каждого итератора к в диапазоне [first, 1) значение comp(*k, value) истинно,aj — самый дальний итератор в диапазо- не [first, last), такой что для каждого итератора к в диапазоне [first, j) значение comp(value, *к) ложно. Для каждого итератора кв [i,j) как comp( value, *к),таки comp(*k, value) равны false. Где определено Вреализации HP equal_range был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Forwarditerator — модель Однонаправленного Итератора. • StrictWeaklyComparable — модель Строго Слабо Сравнимого. • Тип значения Forwa rdlterato г совпадает с типом St nctWeaklyComparable. Версия 2: • Forward Iterate г — модель Однонаправленного Итератора. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Forwardlteratoг совпадает с типом Т. Тип значения Forwarditerator можно преобразовать к типу аргумента StrictWeakOrdering. предусловия версия 1: [first, last) — допустимый диапазон. Диапазон [first, last) отсортирован по возрастанию с помощью operate г<, то есть is_sorted(first, last) истинно. версия 2: [first, last) — допустимый диапазон. Диапазон [first, last) отсортирован по возрастанию с помощью функцио- - нального объекта comp, то есть is_sorted(first, last, comp) истинно.
314 Глава 13. Сортировка и поиск Оценка сложности Число сравнений имеет логарифмическую зависимость: не более 2 log(last - f i rst) + Если тип Forwarditerator является Итератором Произвольного Доступа, то колИч ство шагов внутри диапазона также имеет логарифмическую зависимость, в проТИв ном случае количество шагов пропорционально last - f i rst. Сложность выполнения отличается для Итераторов Произвольного Доступа по срав нению с другими типами итераторов, потому что advance (с. 192) выполняется за кОн стантное время для Итераторов Произвольного Доступа и за линейное время для Од нонаправленного Итератора. Пример int main() { int А[ ] -{1.2. 3, 3. 3, 5, 8 }. const int N = sizeof(А) / sizeof(int), for (int 1=2; i <= 5. ++i) { pair<int*, int*> result = equal_range(A, A+N, i); cout « endl, cout « 'Searching for " « i « endl, cout « ” First position where ” « i « " could be inserted “ « result first - A << endl; cout « " Last position where " « i « " could be inserted ” « result.second - A « endl; if (result first < A + N) cout « ” *result first = ” « *result.first « endl, if (result second < A + N) cout << " * result.second = ” « * result second « endl; Результат: Searching for 2 First position where 2 could be inserted 1 Last position where 2 could be inserted 2 ♦result first = 2 ♦result second = 3 Searching for 3 First position where 3 could be inserted 2 Last position where 3 could be inserted 5 ♦ result first = 3 ♦ result second = 5
13.2. Операции над отсортированными диапазонами 315 Searching for 4 First position wnere 4 could De inserted 5 Last position where 4 could De inserted 5 ♦ result first = 5 ♦ result second = 5 Searching for 5 First position where 5 could be inserted 5 Last position where 5 could be inserted' 6 -♦result first = 5 ♦ result second = 8 13.2.2. Слияние двух отсортированных диапазонов 1 3.2.2.1. merge (слияние) 1) template <class Inputlteratorl, class Inputlterator2, class Outputlterator> Outputiterator merge(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result); 2) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class StrictWeakOrdering> Outputiterator merge(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result, StrictWeakOrdering comp); Алгоритм merge объединяет два отсортированных диапазона в один отсортирован- ный диапазон. Он копирует элементы из [firstl, lastl) и [first2, last2) в [result, result + (last 1 - f i rst 1) + (last2 - f i rst2)) так, что результирующий диапазон являет- ся отсортированным в порядке возрастания. Возвращаемое значение — result + (lastl - firstl) + (last2 - first2). Эта операция стабильна в том смысле, что относительный порядок элементов внут- ри каждого входного диапазона сохраняется, и для эквивалентных элементов вход- ных диапазонов элементы первого предшествуют элементам второго. Две версии merge различаются методом сравнения элементов. Версия 1 использует °Perator<, то есть входные диапазоны и выходной диапазон отсортированы в порядке врастания в соответствии с operators Версия 2 использует функциональный объект с°тР» то есть входные диапазоны и выходной диапазон отсортированы в порядке воз- растания в соответствии с comp. Заметим, что merge и set_union (с. 322) очень похожи способом построения отсор- Рованного диапазона, копируя элементы из двух отсортированных входных диа- онов. Разница в том, что происходит в случае наличия определенного значения Ооих входных диапазонах: set_union исключает дубликаты, a merge — нет. Следо- д ^ьно, одно из постусловий merge заключается в равенстве длины его выходного т вазона сумме длин входных диапазонов. Для set_union, напротив, гарантируется ЛЬко то, что длина его выходного диапазона меньше или равна сумме длин входных Сазонов.
316 Глава 13. Сортировка и поиск Где определено В реализации HP merge был объявлен в заголовке <algo h>. В соответствии со ста11 дартом C++ он объявлен в заголовочном файле <algonthm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Тин значения Inputlteratorl совпадает с типом значения Inputlterator2 • Тип значения Inputlteratorl является Строго Слабо Сравнимым. • Тип значения Inputlteratorl можно преобразовать к типу из набора типов зна- чений Outputiterator. Версия 2: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Тип значения Inputlteratorl совпадает с типом значения Inputlterator2. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Inputlteratorl можно преобразовать к типу аргумента StrictWeakOrdering. • Тип значения Inputlteratorl можно преобразовать к типу из набора типов зна- чений Outputiterator. Предусловия Версия 1: • [firstl, lastl) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью operators то есть is_sorted(first1, lastl) истинно. • [f i rst2, last2) — допустимый диапазон. • Диапазон [first2, last2) отсортирован по возрастанию с помощью operator^ то есть is_sorted(f irst2, last2) истинно. • Имеется достаточно места для хранения всех элементов, которые должны быть скопированы. Более формально требование выглядит так: [result, result + (lastl fi rst!) + (last2- fi rst2)) является допустимым диапазоном. • Диапазоны [first!, lastl) и [result, result + (lastl - first!) + (last2 - first-• не перекрываются. • Диапазоны [first2, last2) и [result, result + (last! - first!) + (last2 - firstl не перекрываются. Версия 2: • [first!, last!) — допустимый диапазон. • Диапазон [first!, last!) отсортирован по возрастанию с помощью функциоНа-1Ь ного объекта comp, то есть is_sorted( fi rst!, last!, comp) истинно.
13.2 Операции над отсортированными диапазонами 317 . [fi rst2, last2) — допустимый диапазон. • Диапазон [f i rst2, last2) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то ecTbis_sorted(first2, last2, comp) истинно • Имеется достаточно места для хранения всех элементов, которые должны быть скопированы. Более формально требование выглядит так: [result, result + (lastl - firstl) + (last2 - fi rst2)) является допустимым диапазоном. • Диапазоны [firstl, lastl) и [result, result + (lastl - firstl) + (last2 - first2)) не перекрываются. . Диапазоны [first2, last2) и [result, result + (lastl - firstl)+ (last2 - first2)) не перекрываются. Оценка сложности Линейная. Для непустых диапазонов не более (lastl - firstl) + (last2 - first2) - 1 сравнений. Пример int main() int A1[] = { 1. 3, 5. 7 }, int A2[j - { 2. 4. 6, 8 }, const int N1 = sizeof(A1) / sizeof(int), const int N2 = sizeof(A2) / sizeof(int), merge(A1. A1 + N1, A2, A2 + N2, ostream_iterator<int>(cout. ” ’ )), cout « endl, // Результат 12345678 > 13*2.2.2. inplace_merge (слияние “на месте”) 0 template <class Bidirectionallterator inline void inplace_merge(BidirectionalIterator first, Bidirectionallterator middle, Bidirectionallterator last); 2) template <class Bidirectionallterator, class StrictWeakOrdering> inline void inplace_merge(BidirectionalIterator first, Bidirectionallterator middle, Bidirectionallterator last, StrictWeakOrdering comp); p ^ДИдва последовательных диапазона [first, middle) и [middle, last) отсортированы, c wQce_merge объединяет их в один отсортированный диапазон, то есть он начинает Диапазона [fi rst, last), который состоит из двух частей, отсортированных в поряд- в0зрастания, и перестраивает его таким образом, чтобы целый диапазон был от- °Ртирован в порядке возрастания.
318 Глава 13. Сортировка и поиск Как и merge, алгоритм inplace_merge стабилен. Относительный порядок элемент внутри каждого входного диапазона сохраняется, а для эквивалентных элемент^ обоих диапазонов элемент первого предшествует элементу второго. Две версии inplace_merge различаются методом определения меньшего элемента Версия 1 сравнивает элементы с помощью operators а версия 2 — с помощью фуНк ционального объекта comp. Где определено В реализации HP inplace_merge был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • В1 d 1 rectiопа1 Iterator — модель Двунаправленного Итератора. • Bidi rectionallterator — изменяемый итератор. • Тип значения Bidirectionallterator — модель Строго Слабо Сравнимого. Версия 2: • Bidi rectionallterator — модель Двунаправленного Итератора. • Bidi rectionallterator — изменяемый итератор. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Bidirectionallterator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия Версия 1: • [first, middle) — допустимый диапазон. • [middle, last) — допустимый диапазон. • Диапазон [first, middle) отсортирован по возрастанию с помощью operator^ тоecTbis_sorted(first, middle) истинно. • Диапазон [middle, last) отсортирован по возрастанию с помощью operator^ то есть is_sorted(middle, last) истинно. Версия 2: • [fi rst, middle) — допустимый диапазон. • [middle, last) — допустимый диапазон. • Диапазон [f i rst, middle) отсортирован по возрастанию с помощью функционала ного объекта comp, то есть is_sorted(first, middle, comp) истинно. • Диапазон [middle, last) отсортирован по возрастанию с помощью функциональ ного объекта comp, то есть is_sorted(middle, last, comp) истинно. Оценка сложности Алгоритм inplace_merge адаптивен. Он пытается выделить временный буфер паМя ти, и время его выполнения (сложность) зависит от количества доступной памя1*11’ Для непустых диапазонов в худшем случае (если дополнительная память недостУг1
13.2. Операции над отсортированными диапазонами 319 ч сложность O(N\og TV), где Nравно last - first, а в лучшем случае (если может wrb выделен достаточно большой буфер дополнительной памяти) потребуется е более N - 1 сравнений. Пример выполнить слияние в диапазоне, состоящем из двух последовательных отсортиро- ванных поддиапазонов целых чисел. int mam () { int А[] ={1,3, 5. 7, 2, 4, 6, 8 }, tinplace_merge(A, А + 4, А + 8); сору(А, А + 8, ostream_iterator<int>(cout, " ")), // Результат 12345678 Эффективный способ слияния двух последовательных отсортированных поддиапа- зонов подсказывает алгоритм сортировки диапазона в стиле “разделяй и властвуй”, а именно: разделить диапазон пополам, отсортировать каждую половину, а затем ис- пользовать inplace_merge для формирования целиком отсортированного диапазона. template <class Bidirectionallteo void mergesort(BidirectionalIter first, Bidirectionallter last) { typename iterator_traits<Bidirectional!ter> difference_type n = distance(first, last), if (n == 0 || n == 1) return; else { Bidirectionallter mid = first + n / 2, mergesort(first mid), mergesort(mid, last), inplace_merge(first, mid, last), } } ^0 алгоритм сортировки слиянием (mergesort). Фактически алгоритм stable_sort \с-295) реализован именно таким способом (с некоторыми небольшими модифика- ции для повышения эффективности). 13.2.3. Операции надмножествами на отсортированных диапазонах Одним из фундаментальных понятий математики является множество — неупорядо- нный набор элементов. Основные операции теории множеств — это объединение, Пресечение и разность множеств. с Ьсли множества не упорядочены, то почему операции над множествами приводят- в главе об отсортированных диапазонах? Дело в том, что есть разница между мно- вСТвами в чисто абстрактном математическом смысле и тем, как эти множества пред- Цяются в компьютерных программах. В абстрактной математике множества не- °Рядочены. Например, множество {1, 2, 3} — это то же самое, что и {3, 2, 1}. Однако
320 Глава 13. Сортировка и поиск в в программе мы должны выбрать некий способ для представления этого множ виде структуры данных, упрощающего выполнение операций объединения и есч 'ере- сечения. Одно из наиболее удобных представлений — отсортированный диапазон эде.\1е тов. Оказывается, что все базовые операции теории множеств могут быть эффект * но выполнены на отсортированных диапазонах, и что полученные диапазоны есге ственным образом отсортированы. Алгоритмы данного раздела реализуют операц1п над множествами в виде операций над отсортированными массивами. Кроме этих алгоритмов STL содержит контейнерный класс set (с. 456). Как подСКа. зывает название, set — удобный способ представления набора элементов, который мЬ1 собираемся использовать с алгоритмами над множествами в данном разделе. Каждое значение встречается в set максимум один раз, и элементы set всегда образуют массив Так как эти алгоритмы работают с множествами, представленными в виде отсор- тированных массивов, а не с множествами в абстрактном математическом понима- нии, они обобщают определения математических операций над множествами в од- ном важном аспекте: некоторое значение может встречаться в отсортированном диа- пазоне более одного раза. Вместо введения предусловия, что каждый элемент должен встречаться максимум один раз в каждом входном диапазоне, алгоритмы обобщают математические операции над множествами таким образом, что они вполне опреде- лены для входных диапазонов с повторяющимися значениями. Эти обобщенные опе- рации по-прежнему удовлетворяют свойствам обычных операций над множествами, и когда входные диапазоны не содержат повторяющихся элементов, алгоритмы рабо- тают в соответствии с обычными математическими определениями. 13.2.3.1. includes (проверка включения подмножества) 1 ) template <class Inputlteratorl, class Inputlterator2> bool includes(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2); 2 ) template <class Inputlteratorl, class Inputlterator2, class StrictWeakOrdering> bool includes(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, StrictWeakOrdering comp); Алгоритм includes проверяет, является ли одно множество подмножеством другою, причем оба множества представлены в виде отсортированных массивов, то есть этот алгоритм возвращает истинность того, что для каждого элемента из [first2, ias-^ эквивалентный элемент присутствует в [firstl, lastl). Как и все алгоритмы STL, работающие с множествами, includes расширяет мате магическое определение операций над множествами, а именно отсутствует треоов* нис, что все элементы в [firstl, lastl) и [first2, last2) должны быть уникальньь В данном случае определение включения подмножества расширено таким зом, что для каждого элемента диапазона [first2, last2) должен присутствовать аь Бивалентный элемент в диапазоне [fi г st 1, lastl). Следовательно, если элемент встрс чается п раз в [first2, last2) и m раз в [firstl, lastl), то includes возвратитзначеН1К false, если т < л.
13.2. Операции над отсортированными диапазонами 321 Две версии includes различаются методом определения меньшего элемента. Вер- я 1 сравнивает объекты с помощью operator^ а версия 2 — с помощью функцио- нального объекта comp. до определено Рреализации HP includes был объявлен в заголовке <algo.h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • InputIte rato r2 — модель Итератора Ввода. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • Тип значения Inputiterator — модель Строго Слабо Сравнимого. Версия 2: • Inputlteratorl — модель Итератора Ввода. • InputIterato r2 — модель Итератора Ввода. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения Inputlteratorl можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия Версия 1: • [firstl, lastl) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью operators то есть is_sorted( firstl, lastl) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью operators ToecTbis_sorted(first2, last2) истинно. Версия 2: [firstl, lastl) — допустимый диапазон. [first2, last2) — допустимый диапазон. Диапазон [firstl, lastl) отсортирован по возрастани. с помощью функциональ- ного объекта comp, то есть is_sorted(fi rst 1, lastl, comp) истинно. Диапазон [f i rst2, last2) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то ecTbis_sorted(first2, last 2, comp) истинно. Оценка сложности ^Нейная. Нуль сравнений, если либо [firstl, lastl), либо [first2, last2) являются Истыми диапазонами. В противном случае не более 2((last1 - firstl) + (last2 - lrst2)) - 1 сравнений.
322 Глава 13. Сортировка и поиск Пример int main() { int A1[] = { 1, 2. 3, 4. 5. 6. 7 }, int A2[] = { 1. 4, 7 }, int A3[] - { 2, 7, 9 }, int A4[] = { 1, 1, 2, 3, 5, 8, 13. 21 }, int A5[] = { 1, 2, 13, 13 }. int A6[] = { 1, 1, 3, 21 }. const int N1 = sizeof(A1) I sizeof(int); const int N2 zz sizeof(A2) / sizeof(int). const int N3 - sizeof(A3) / sizeof(int); const int N4 - sizeof(A4) / sizeof(int), const int N5 = sizeof(A5) / sizeof(int); const int N6 sizeof(A6) / sizeof(int); cout « "А2 contained in A1 ” « (includes(A1, A1 + N1, « endl, cout « ”A3 contained in A1• « (includes(A1, A1 + N2, « endl, cout « "A5 contained in A4 « (includes(A4, A4 + N4, « endl; cout « ”A6 contained in A4: « (includes(A4, A4 + N4, « endl, } A2, A2 + N2) 9 "true" "false") A3, A3 + N3) 9 "true" "false") A5, A5 + N5) 9 "true" "false") A6, A6 + N6) 9 "true" "false”) Результат: A2 contained in A1 true A3 contained in A1 false A5 contained in A4 false A6 contained in A4 true 13.2.3.2. set_union (объединение множеств) 1) template <class Inputlteratorl, class Inputlterator2, class Outputlterator> Outputiterator set_union(lnputlterator1 firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result); 2) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class StrictWeakOrdering>
13.2. Операции над отсортированными диапазонами 323 Qutputlterator set_union(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result, StrictWeakOrdering comp); ддгори™ set-union создает объединение двух множеств, то есть множество 5t U 52, остояшее из всех элементов, присутствующих либо в 5i, либо в 52, либо в обоих. И , &и их объединение представлены в виде отсортированных массивов. Возвращае- те значение — конец выходного диапазона. Как все алгоритмы STL, работающие с множествами, set_union обобщает матема- Тйческое определение операций над множествами. Отсутствует требование уни- кальности элементов во входных диапазонах [firstl, lastl) и [first2, last2). В дан- ном случае определение объединения множеств расширено таким образом, что если значение встречается п раз в [f i rst1, lastl) и то же значение встречается т раз в [f i rst2, last2),то оно будет встречаться шах(и, т) раз в выходном диапазоне. (Обычное опре- деление объединения — это просто случай, когда п < 1 и т < 1.) Данная операция является стабильной в том смысле, что относительный порядок элементов внутри каждого входного диапазона сохраняется, и если какой-то элемент присутствует в обоих входных диапазонах, то он копируется из первого диапазона, а не из второго. Несколько неточно говорить о значении, встречающемся п раз в [firstl, lastl) и т pa3B[first2, last 2). Более строгая формулировка: [firstl, lastl) содержит диапазон из л эквивалентных элементов, a [first2, last2) содержит т элементов из того же класса эквивалентности. Выходной диапазон будет содержать шах(и, т) значений из этого класса эквивалентности; п таких элементов выходного диапазона будут скопи- рованы из [fi rst 1, lastl), а оставшиеся тах(и - т, 0) элементов будут скопированы из [first2, last2). Две версии set_union различаются методом определения меньшего элемента. Вер- сия 1 сравнивает объекты с помощью operate г <, а версия 2 — с помощью функцио- нального объекта comp. Где определено В реализации HP set_union был объявлен в заголовке <algo. h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: Inputlteratorl — модель Итератора Ввода. Inputlterator2 — модель Итератора Ввода. OutputIte ratoг — модель Итератора Вывода. Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. Тип значения In put Ite rat о г — модель Строго Слабо Сравнимого. Тип значения Input Ite rato г можно преобразовать к одному из набора типов зна- чений Output Ite rato г. 8еРсия2: Inputlteratorl — модель Итератора Ввода. Inputlterator2 — модель Итератора Ввода. Outputiterator — модель Итератора Вывода.
324 Глава 13. Сортировка и поиск • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Inputlteratorl и Input Iterate г2 имеют один и тот же тип значений. • Тип значения Inputlteratorl можно преобразовать к типу StrictWeakOrdering. • Тип значения Input Iterate г можно преобразовать к одному из набора типов зн. чений Outputiterator. Предусловия Версия 1: • [firstl, lastl) — допустимый диапазон. • [first2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью operator то есть is_sorted(first1, lastl) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью operator то есть is_sorted(first2, last2) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + ri) являет- ся допустимым диапазоном, где п — количество элементов в объединении двух входных диапазонов. • Диапазоны [firstl, lastl) и [result, result + п) не перекрываются. • Диапазоны [f i rst2, last2) и [ result, result + n) не перекрываются. Версия 2: • [firstl, lastl) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью функцио- нального объекта comp, то ecTbis_sorted(first1, lastl, comp) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью функцио- нального объекта comp, то ecTbis_sorted(first2, last2, comp) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + ri) являв!- ся допустимым диапазоном, где п — количество элементов в объединении входных диапазонов. • Диапазоны [firstl, lastl) и [result, result + п) не перекрываются. • Диапазоны [first2, last2) и [result, result + п) не перекрываются. Оценка сложности Линейная. Для непустых диапазонов не более 2(( lastl - firstl) + (last2 - first2))" сравнений. Пример inline bool It_nocase(char cl. char c2) { return tolower(c!) < tolower(c2),
13.2. Операции над отсортированными диапазонами 325 int roainO int А1[] = (1. 3. 5. 7. 9. int А2[] = И. 1. 2, 3. 5, char А3[] = { а', 'Ь , В' char А4[] = {’А’ 'В . Ь' 11}. 8, 13}, . ’B'. T. H’}. , 'С, ’D 'F', const int N1 = sizeof(A1) / sizeof(int), const int N2 = sizeof(A2) / sizeof(int), const int N3 - sizeof(A3), const int N4 - sizeof(A4), cout « "Union of A1 and A2 ", set_union(A1, A1 + N1. A2. A2 + N2, ostream_iterator<int>(cout, " ”)), cout « endl « "Union of A3 and A4 ", set_union(A3, A3 + N3 A4. A4 + N4, ostream_iterator<char>(cout, " "), lt_nocase), cout « endl, } Результат: Union of A1 and A2 1 1 2 3 5 7 8 9 11 13 Union of A3 and A4 a b В В C D f F H h 13.2.3.3. set_intersection (пересечение множеств) О template <class Inputlteratorl, class Inputlterator2, class Outputlterator> OutputIterator set-intersection(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result); 2) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class StrictWeakOrdering> Outputiterator set-intersection(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result, StrictWeakOrdering comp); ^г°ритм set-intersection строит пересечение двух множеств. Это значит, что он со- дает множество A S-2, состоящее из всех элементов, присутствующих и в 5Ь и в 52 ^Повременно. Множества S2 и их пересечение представлены в виде отсортированных масси- Возвращаемое значение — конец выходного диапазона.
326 Глава 13. Сортировка и поиск Как и все алгоритмы STL, работающие с множествами, set-intersection обобт математическое определение операций над множествами. Отсутствует требова^т уникальности элементов в [firstl, lastl) и [f i rst2, last2). Определение пересечен^ множеств обобщено таким образом, что если значение встречается п раз в [firstH lastl) и то же значение встречается т раз в [first2, last2), то оно будет встреча^ ’ min(w, т) раз в выходном диапазоне. (Определение из обычной теории множеств^ это случай, когда п < 1 и т < 1.) Данная операция стабильна в том смысле, что таки^ элементы копируются из первого диапазона, а не из второго, и относительный поря док элементов в выходном диапазоне такой же, как и в первом входном. Несколько неточно говорить о значении, встречающемся п раз в [f i rst 1, lastl) и m раз в [ fi rst 2, last2). Более строгая формулировка: [firstl, lastl) содержит диапазон из п эквивалентных элементов, a [first2, last2) содержит т элементов из того же класса эквивалентности. Выходной диапазон содержит min(n, т) значений из этого класса эквивалентности, и все они скопированы из [firstl, lastl). Две версии set-intersection различаются методом определения меньшего элемен- та. Версия 1 сравнивает объекты с помощью ope rato г<, а версия 2 — с помощью функ- ционального объекта comp. Где определено В реализации HP set-intersection был объявлен в заголовке <algo.h>. В соответ- ствии со стандартом C++ он объявлен в заголовочном файле <algonthm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • Тип значений Inputiterator — модель Строго Слабо Сравнимого. • Тип значения Inputlte rator можно преобразовать к одному из набора типов зна- чений Outputiterator. Версия 2: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Inputlteratorl и Inputlteratoг2 имеют один и тот же тип значений. • Тип значений Inputlteratorl можно преобразовать к типу аргумента StrictWeakOrdering. • Тип значения Inputlterator можно преобразовать к одному из набора типов зна чений Outputiterator. Предусловия Версия 1: • [firstl, lastl) — допустимый диапазон.
13.2. Операции над отсортированными диапазонами 327 [firsl2, last2) — допустимый диапазон. Диапазон [firstl, lastl) отсортирован по возрастанию с помощью operator^ тоесть is_sorted(first1, lastl) истинно. . Диапазон [first2, last2) отсортирован по возрастанию с помощью operators то есть is_sorted(first2, last2) истинно. . Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + п) являет- ся допустимым диапазоном, где п — количество элементов в пересечении двух входных диапазонов. « Диапазоны [firstl, lastl) и [result, result + п) не перекрываются. • Диапазоны [fi rst2, last2) и [result, result + ri) не перекрываются. Версия 2: • [firstl, lastl) — допустимый диапазон. • [f i rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью функцио- нального объекта comp, то есть is_sorted( first 1, lastl, comp) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью функцио- нального объекта comp, то ecTbis_sorted(first2, last2, comp) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + ri) являет- ся допустимым диапазоном, где п — количество элементов в пересечении двух входных диапазонов. • Диапазоны [firstl, lastl) и [result, result + ri) не перекрываются. • Диапазоны [fi rst2, last2) и [result, result + ri) не перекрываются. Оценка сложности Линейная. Нуль сравнений, если либо [firstl, lastl), либо [first2, last2) является пустым диапазоном. В противном случае не более 2 ((lastl - firstl) + (last2 - first2)) - 1 сравнений. Пример inline bool lt_nocase(char cl, char c2) { return tolower(cl) < tolower(c2), int main() { int A1[] = {1, 3, 5, 7. 9, 11}. int A2[] = {1, 1, 2. 3. 5. 8, 13}. char A3[] = {’a’, ’b’. ‘b‘. В’. 'B‘, ’f’. ’h’. ’H’}. Char A4[ ] = {’ A', В ’В*. ‘C. *D', ’F', 'F*. ‘H’}. Const int N1 ~ sizeof(A1) / sizeof(int), const int N2 - sizeof(A2) / sizeof(int), const int N3 = sizeof(A3),
328 Глава 13. Сортировка и поиск const int К,Л = sizeof(A4), cout « ’Intersection of A1 and A2 ”. set_intersection(A1, A1 + N1. A2, A2 + N2, ostream_iterator<int>(cout, " ”)). cout « endl « ‘Intersection of A3 and A4 ”, set_intersection(A3. A3 + N3, A4, A4 + N4, ostream_iterator<char>(cout, " ”), lt_nocase), cout « end], Результат: Intersection of A1 and A2 135 Intersection of A3 and A4 a b b f h 13.2.3.4. set-difference (разность множеств) 1 ) template <class Inputlteratorl, class Inputlterator2, class Outputlterator> Outputiterator set_difference(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result); 2 ) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class StrictWeakOrdering> Outputiterator set_difference(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result, StrictWeakOrdering comp); Алгоритм set.dif ference вычисляет разность двух множеств, то есть он строит мно- жество \ 52, состоящее из всех элементов, присутствующих в 5Ь но не в 52- Множества 5r S2 и их разность представлены в виде отсортированных массивов Возвращаемое значение — конец выходного диапазона. Как и все алгоритмы STL, работающие с множествами, set-difference обобщает математическое определение операций над множествами. Отсутствует требование уникальности элементов в [firstl, last1)H [first2, last2). Определение разности мн°' жеств расширено таким образом, что если значение встречается п раз в [f i rstl, lastO и то же значение встречается т раз в [first2, last2), то в выходном диапазоне это значение будет встречаться тах(т? — т, 0) раз. Данная операция стабильна в том смьЮ ле, что эти элементы копируются из первого диапазона, а не из второго, и относитель ный порядок элементов в выходном диапазоне такой же, как и в первом входной (Определение этой операции в обычной теории множеств — это случай, когда Д S * и т < 1.)
329 13.2. Операции над отсортированными диапазонами Несколько неточно говорить о значении, встречающемся г? раз в [firstl, lastl) и т - [first2, last2). Более строгая формулировка такова: [firstl, lastl) содержит Р амазон из п эквивалентных элементов, a [first2, last2) содержит т элементов из ^гОже класса эквивалентности. Выходной диапазон будет содержать тах(и - т, 0) ^ачений из данного класса эквивалентности, и все они будут скопированы из [f i rst1, last!)- Две версии set-difference различаются методом определения меньшего элемента ВерсиЯ 1 сРавнивает объекты с помощью operators а версия 2 — с помощью функци- онального объекта comp. Где определено В реализации HP set-difference был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • Inputlteratorl и InputIteratoг2 имеют один и тот же тип значений. • Тип значения Inputiterator — модель Строго Слабо Сравнимого. • Тип значения In put Ite rato г можно преобразовать к одному из набора типов зна- чений Out putlte rator. Версия 2: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Outputiterator — модель Итератора Вывода. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • Тип значения Inputlteratorl можно преобразовать к типу аргумента StrictWeakOrdering. Тип значения Input Ite ratoг можно преобразовать к одному из набора типов зна- чений Outputiterator. Предусловия Версия 1: [firstl, lastl) — допустимый диапазон. [f 1 rst2, last2) — допустимый диапазон. * Диапазон [f i rst 1, lastl) отсортирован по возрастанию с помощью operators то есть is_sorted(firstl, lastl) истинно. Диапазон [first2, last2) отсортирован по возрастанию с помощью operators, то есть is_sorted(first2, last2) истинно.
330 Глава 13. Сортировка и поиск • Имеется достаточно места для сохранения всех элементов, которые должны бь скопированы. Иначе говоря, требование следующее: [result, result + п) явдЯетЬ ся допустимым диапазоном, где п — количество элементов в разности двух вХо^ ных диапазонов. • Диапазоны [firstl, lastl) и [result, result + п) не перекрываются. • Диапазоны [first2, last2) и [result, result + n) не перекрываются. Версия 2: • [fi rst 1, last 1) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью функцио- нального объекта comp, то есть is_sorted(first1, lastl, comp) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью функцио- нального объекта comp, то ecTbis_sorted(first2, last2, comp) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + п) являет- ся допустимым диапазоном, где п — количество элементов в разности двух вход- ных диапазонов. • Диапазоны [firstl, lastl) и [result, result + п) не перекрываются. • Диапазоны [first2, last2) и [result, result + и) не перекрываются. Оценка сложности Линейная. Для непустых диапазонов не более 2 ((lastl - firstl) + (last2 - first2)) - 1 сравнений. Пример inline bool lt_nocase(char d, char с2) { return tolower(d) < tolower(c2); } int main() { int A1[] = {1, 3, 5. 7, 9, 11}, int A2[] = {1, 1, 2, 3, 5, 8, 13}, char A3[] = {’a’, ’b’, ‘b‘, ‘B‘, ’B‘, ‘f, g’t h’, ’H}, char A4[] = {'A', ‘B‘ ’B', 'C‘ ’D’, F’. F‘ ’H'}, const int N1 = sizeof(A1) / sizeof(int), const int N2 - sizeof(A2) / sizeof(int), const int N3 - sizeof(A3), const int N4 = sizeof(A4), cout << "Difference of A1 and A2 ", set_difference(A1, A1 + N1. A2, A2 + N2. ostream_iterator<int>(cout, " ’)), cout « endl « "Difference of A3 and A4 ",
13.2. Операции над отсортированными диапазонами 331 set_difference(A3, АЗ + N3, А4, А4 + N4, ostream_iterator<char>(cout. " lt_nocase), cout « endl, результат: Difference of A1 and A2 7 9 11 Difference of A3 and A4- В В g H 13.2.3.5. set_symmetric_difference (симметрическая разность множеств) f) template <class Inputlteratorl, class Inputlterator2, class Outputlterator> Outputlterator set_symmetric_difference(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result); 2) template <class Inputlteratorl, class Inputlterator2, class Outputiterator, class StrictWeakOrdering> Outputiterator set_symmetric_difference(Inputlteratorl firstl, Inputlteratorl lastl, Inputlterator2 first2, Inputlterator2 last2, Outputiterator result, StrictWeakOrdering comp): Алгоритм set_symmetric_difference вычисляет симметрическую разность двух мно- жеств и 52, то есть он строит множество, состоящее из всех элементов 5Ь которые отсутствуют в 52, и из всех элементов 52, отсутствующих в (Математически это может быть записано (^ \ 52) U (52 \ 5t).) Множества 8Ь 52 и их симметрическая раз- ность представлены в виде отсортированных диапазонов. Возвращаемое значение — конец выходного диапазона. Как и все алгоритмы STL, работающие с множествами, set_symmetric_difference Расширяет математическое определение операций над множествами. Отсутствует требование уникальности элементов в [firstl, lastl) и [first2, last2). В данном слу- чае определение расширено таким образом, что если значение встречается п раз в [firstl, lastl) и то же значение встречается т раз в [first2, last2), то оно будет ВстРечаться \п - т\ раз в выходном диапазоне. (Определение из обычной теории мно- жеств — это случай, когда п < 1 и т < 1.) Данная операция стабильна в том смысле. Что относительный порядок элементов внутри каждого входного диапазона сохраня- Несколько неточно говорить о значении, встречающемся п раз в [f i г s 11, lastl) и m Р^з в [first2, last2). Более строгая формулировка такова: [firstl, lastl) содержит диапазон из п эквивалентных элементов, a [first2, last2) содержит т элементов 113 того же класса эквивалентности. Выходной диапазон содержит - тп\ значений 113 этого класса эквивалентности; если п > т, то выходной диапазон содержит по- здние п - т таких элементов из входного диапазона [firstl, lastl), а если п < т,
332 Глава 13. Сортировка и поиск го выходной диапазон содержит последние т - и таких элементов из входного диапа зона [first2, last2). Две версии set_symmetric_difference различаются методом определения меньще го элемента. Версия 1 сравнивает объекты с помощью operator^ а версия 2 — с пом0. щыо функционального объекта comp. Где определено В реализации HP set_symmetric_difference был объявлен в заголовке <algo h>. В со- ответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • Inputlteratorl — модель Итератора Ввода. • Inputlterator2 — модель Итератора Ввода. • Outputlrerator — модель Итератора Вывода. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • Тип значения Inputiterator — модель Строго Слабо Сравнимого. • Тип значения In put Ite rato г можно преобразовать к одному из набора типов зна- чений Outputiterator. Версия 2: • Inputlteratorl — модель Итератора Ввода. • InputIterato r2 — модель Итератора Ввода. • OutputIteratoг - модель Итератора Вывода. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Inputlteratorl и Inputlterator2 имеют один и тот же тип значений. • Тип значения Inputlteratorl можно преобразовать к типу аргумента St rictWeakOrdering. • Тип значения Inputiterator можно преобразовать к одному из набора типов зна- чений Outputiterator. Предусловия Версия 1: • [firstl. lastl) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью operate^ то есть is_sorted(firstl, lastl) истинно. • Диапазон [first2, last2) отсортирован по возрастанию с помощью operator^ то есть is_sorted(first2, last2) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + ri) являет ся допустимым диапазоном, где п — количество элементов в симметрической разности двух входных диапазонов. • Диапазоны [firstl, lastl) и [result, result + п) не перекрываются
13.2. Операции над отсортированными диапазонами 333 • Диапазоны [first2, last2) и [result, result + п) нс перекрываются. рерсия 2: • [firstl, lastl) — допустимый диапазон. • [fi rst2, last2) — допустимый диапазон. • Диапазон [firstl, lastl) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то ecTbis_sorted( firstl, lastl, comp) истинно. • Диапазон [f i rst2, last2) отсортирован по возрастанию с помощью функциональ- ного объекта comp, то ecTbis_sorted( first 2, last2, comp) истинно. • Имеется достаточно места для сохранения всех элементов, которые должны быть скопированы. Иначе говоря, требование следующее: [result, result + п) являет- ся допустимым диапазоном, где п — количество элементов в симметрической разности двух входных диапазонов. • [firstl, lastl) и [result, result + п) не перекрываются. • [fi rst2, last2) и [ result, result + ri) не перекрываются. Оценка сложности Линейная. Для непустых диапазонов не более 2 ((lastl - firstl) + (last2 - first2)) - 1 сравнений. Пример inline bool lt_nocase(char cl, char c2) { return tolower(cl) < tolower(c2), int main() int A1[] = {1, 3. 5, 7, 9, 11}, int A2[] = {1, 1. 2, 3, 5, 8, 13}, char A3[] = {'a'. ’b’, ’b', ’B’, ’B’, ’f, 'g', 'h', ’H’}, char A4[ ] = {'A'. 'B' ’B’, 'C. 'D', 'F', 'F', 'H'}, const int N1 = sizeof(A1) / sizeof(int), const int N2 = sizeof(A2) / sizeof(int), const int N3 = sizeof(A3), const int N4 = sizeof(A4), cout « 'Symmetric difference of A1 and A2 ", set_symmetric_difference(A1. A1 + N1, A2, A2 + N2. ostream_iterator<int>(cout, " ”)), cout << endl « 'Symmetric difference of A3 and A4 ", set_symmetric_difference(A3, A3 + N3. A4, A4 + N4, ostream_iterator<char>(cout, ' "), lt.nocase), cout « endl, }
334 Глава 13. Сортировка и поиск Результат- Symmetric difference of А1 and А2- 12789 11 13 Symmetric difference of A3 and A4 BBCDFgH 13.3. Операции с кучами Куча (heap) — это неотсортированный диапазон. Элементы кучи выстроены не в по- рядке возрастания, а более сложным способом. Внутри куча представляет собой де- рево как последовательный диапазон. Дерево построено так, что каждый узел мень- ше или равен своему родительскому узлу. Кучи тесно связаны с отсортированными диапазонами по трем причинам. Во-пер- вых, куча, как и отсортированный диапазон, обеспечивает эффективный доступ к сво- ему наибольшему элементу. Если [f i rst, last) — это куча, то *f i rst — ее наибольший элемент. Во-вторых, можно добавить элемент в кучу с помощью push_heap или из- влечь из нее элемент с помощью pop_heap за логарифмическое время. В-третьих, су- ществует простой и эффективный алгоритм sort_heap, который преобразует кучу в от- сортированный диапазон. Куча — удобный механизм представления приоритетных очередей (priority queues), где элементы вставляются в произвольном порядке, а извлекаются в порядке от наи- большего к наименьшему. Например, в STL контейнер-адаптер prior ity_queue (при- оритетная очередь) реализован с помощью кучи. 13.3.1. make_heap (создание кучи) 1) template <class RandomAccessIterator void make_heap(RandomAccessIterator first, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> void make_heap(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм make_heap преобразует произвольный диапазон [f i rst, last) в кучу. Две версии make_heap различаются методом определения меньшего элемента. Вер- сия 1 сравнивает объекты с помощью operators а версия 2 — с помощью функцио- нального объекта comp. Постусловие версии 1 таково: is_heap( f i rst, last) истинно. Соответственно, в слУ' чае версии 2 истинно is_heap(first, last, comp). Где определено В реализации HP make_heap был определен в заголовке <algo.h>. В соответствии со стандартом C++ он определен в заголовочном файле <algorithm>. Требования к типам Версия 1: • RandomAccessIterator — модель Итератора Произвольного Доступа.
13.3. Операции с кучами 335 • RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator — модель Строго Слабо Сравнимого. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия • [f i rst, last) — допустимый диапазон. Оценка сложности Линейная. Не более 3(last - f i rst) сравнений. Пример int main() { int A[] = {1, 4, 2, 8, 5, 7}, const int N = sizeof(A) / sizeof(int), make_heap(A, A+N); assert(is_heap(A, A+N)), copy(A, A+N, ostream_iterator<int>(cout, ” ”)), cout « endl; sort_heap(A, A+N), assert(is_sorted(A, A+N)), copy(A, A+N, ostream_iterator<int>(cout, ” ”)); cout « endl, •3.3.2. push_heap (добавление элемента в кучу) •) template <class RandomAccessIterator> void push_heap(RandomAccessIterator first, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> void push_heap(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); An x горитм push_heap добавляет элемент в кучу. Интерфейс несколько необычен: куча, в Которую будет добавляться элемент, — это диапазон [first, last - 1), а добавляемый Бемент - *(last - 1). Две версии push_heap различаются методом определения меньшего элемента. Вер- С11я 1 сравнивает объекты с помощью operatorc, а версия 2 — с помощью функцио- нального объекта comp.
336 Глава 13. Сортировка и поиск Постусловие версии 1 таково: is_heap(first, last) истинно. Соответственно, в Cnv час версии 2 истинно is_heap(first, last, comp). Где определено В реализации HP push_heap был определен в заголовке <algo h>. В соответствии Со стандартом C++ он определен в заголовочном файле <algorithm>. Требования к типам Версия 1: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator — модель Строго Слабо Сравнимого. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа • RandomAccessIterator — изменяемый итератор. • St rictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StnctWeakOrdenng. Предусловия Версия 1: • [fi rst, last) — допустимый диапазон. • [first, last - 1) — допустимый диапазон, то есть [first, last) — непустой диа- пазон. • [first, last -1)является кучей, следовательно, is_heap(first, last -1) истинно Версия 2: • [first, last) — допустимый диапазон. • [first, last - 1) — допустимый диапазон, то есть [first, last) — непустой диа- пазон. • [first, last -1) является кучей, следовательно, is_heap(fi rst, last-1, comp) ис- тинно. Оценка сложности Логарифмическая. Не более log(last - first) сравнений. Пример int main() { int А[10] = {0. 1, 2, 3. 4, 5 6, 7. 8, 9}, make_heap(A А + 9), cout << '[А, А + 9) = ”, сору(А, А + 9, ostream_iterator<int>(cout, " ")), cout << endl,
13.3. Операции с кучами 337 push_neap(A, А + 10), cout « '[А, А+ 10) = сору(А, А + 10. ostream_iterator<int>(cout. ")). cout « endl, } результат: [А, А + 9) = 876345210 [А, А + Ю) = 9 8 6 3 7 5 2 1 0 4 13.3.3. pop.heap (извлечение из кучи) q template <class RandomAccessIterator> void pop_heap(RandomAccess!terator first, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> void pop_heap(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм pop_heap извлекает наибольший элемент, то есть *first, из кучи [first, last). Две версии pop_heap различаются методом определения меньшего элемента. Версия 1 сравнивает объекты с помощью operators а версия 2 — с помощью функ- ционального объекта comp. Постусловие обеих версий pop_heap таково: *(last -1) — это элемент, который был удален из кучи. Кроме того, постусловие версии 1 следующее: is_heap(fi rst, last - 1) истинно, а версии 2 — is_heap(first, last-1, comp) истинно. Где определено В реализации HP pop_heap был объявлен в заголовке <algo h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам Версия 1: • RandomAccessIterator — модель Итератора Произвольного Доступа. • RandomAccessIterator — изменяемый итератор. • Тип значения RandomAccessIterator — модель Строго Слабо Сравнимого. Версия 2: RandomAccessIterator — модель Итератора Произвольного Доступа. RandomAccessIterator — изменяемый итератор. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. Тип значения RandomAccessIterator можно преобразовать к типу аргумента StrictWeakOrdering. ^Редусло вия ^еРсия 1: [fi rst, last) — допустимый диапазон.
338 Глава 13. Сортировка и поиск • [first, last - 1) — допустимый диапазон, то есть [first, last) — непустой дИа пазон. • [first, last) является кучей, следовательно, is_heap(first, last) истинно. Версия 2: • [first, last) — допустимый диапазон. • [first, last - 1) — допустимый диапазон, то есть [first, last) — непустой дИа^ пазон. • [first, last)является кучей, следовательно, is_heap(first, last, comp) истинно Оценка сложности Логарифмическая. Не более 21og(last - f i rst) сравнений. Пример Извлечь наибольший элемент из кучи. int main() { int А[] = {1. 2, 3. 4, 5, 6}; const int N = sizeof(A) / sizeof(int); make_heap(A, A+N), cout << "Before pop* ”, copy(A, A+N, ostream_iterator<int>(cout, " ”)), pop_heap(A, A + N); cout « endl « "After pop* copy(A, A + N - 1, ostream_iterator<int>(cout, ” ”)); cout « endl « ”A[N-1] = " « A[N-1] « endl, } Результат: Before pop* 653421 After pop 54312 A[N-1] = 6 Алгоритм pop_heap удаляет наибольший элемент из кучи, а затем уплотняет ее. Это означает, что если обратиться к pop_heap несколько раз, пока в ней не останется толь- ко один элемент, то в результате можно получить отсортированный диапазон на том месте, где была куча. int main() mt А[] = {1, 2, 3, 4, 5, 6}, const int N = sizeof(A) / sizeof(int), make_heap(A. A + N),
13.3. Операции с кучами 339 int n = N, while (п > 1) { сору(А, А + N, ostream_iterator<int>(cout, ” ")), cout << endl, pop_heap(A, A + n), --n; } copy(A, A + N, ostream_iterator<int>(cout, " ”)); cout « endl; } Именно так и реализован sort_heap. 13.3.4. sort_heap (сортировка кучи) 1 ) template <class RandomAccessIterator> void sort_heap(RandomAccessIterator first, RandomAccessIterator last); 2 ) template <class RandomAccessIterator, class StrictWeakOrdering> void sort_heap(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм sort_heap преобразует кучу [f i rst, last) в отсортированный диапазон. За- метим, что это не стабильная сортировка: она не обязательно сохраняет относитель- ный порядок эквивалентных элементов. (См. с. 295, где обсуждается стабильность сортировки.) Две версии sort_heap различаются методом определения меньшего элемента. Вер- сия 1 сравнивает объекты с помощью operate г <, а версия 2 — с помощью функцио- нального объекта comp. 1де определено ° реализации HP sort_heap был объявлен в заголовке <algo.h>. В соответствии со стандартом C++ он объявлен в заголовочном файле <algorithm>. Требования к типам ^Рсия 1: RandomAccessIterator — модель Итератора Произвольного Доступа. RandomAccessIterator — изменяемый итератор. Тип значения RandomAccessIterator — модель Строго Слабо Сравнимого. ^Рсия 2: RandomAccessIterator — модель Итератора Произвольного Доступа. * RandomAccessIterator — изменяемый итератор.
340 Глава 13. Сортировка и поиск_______________ • StrictWeakOrdering - модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумент. StrictWeakOrdering. Предусловия Версия Г • | fi rst, last) — допустимый диапазон. • [first, last) является кучей, следовательно, is_heap(first, last) истинно. Версия 2: • (first, last) — допустимый диапазон. • [first, last) является кучей, следовательно, is_heap(first, last, comp) истинно. Оценка сложности Не более TV log TV сравнений, где N равно last - f i rst. Пример int main() int A[] - {1, 4, 2, 8, 5, 7}, const int N - sizeof(A) / sizeof(int), make_neap(A. A copy(A A+N, cout << endl, so^t_heap(A A copy(A, A+N, cout « endl, + N), ostream_iterator<int>(cout, ” ”)), + N), ostream_iterator<int>(cout, ")), 13.3.5. is_heap (является ли кучей) 1) template <class RandomAccessIterator bool is_heap(RandomAccess!terator first, RandomAccessIterator last); 2) template <class RandomAccessIterator, class StrictWeakOrdering> bool is_heap(RandomAccessIterator first, RandomAccessIterator last, StrictWeakOrdering comp); Алгоритм is.heap возвращает истинность того, что диапазон [first, last) является кучей. Две версии различаются методом определения меньшего элемента. Версия сравнивает объекты с помощью operator*:, а версия 2 — с помощью функционального объекта comp. Где определено Алгоритм is_heap не присутствует ни в оригинальной реализации HP STL, ни в стан дарте C++. В реализации SGI он определен в заголовке <algonthm>.
13.3. Операции с кучами 341 ия к типам версия 1: . RandomAccessIterator — модель Итератора Произвольного Доступа. • Тип значения RandomAccessIterator — модель Строго Слабо Сравнимого. Версия 2: • RandomAccessIterator — модель Итератора Произвольного Доступа. • StrictWeakOrdering — модель Строгого Слабого Упорядочения. • Тип значения RandomAccessIterator можно преобразовать к типу аргумента StrictWeakOrdering. Предусловия • [first, last) — допустимый диапазон. Оценка сложности Линейная. Для непустых диапазонов не более (last - fi rst) - 1 сравнений. Пример int main() { int A[] = {1, 2, 3, 4, 5. 6, 7}, const int N = sizeof(A) / sizeof(int), assert(‘is_heap(A A + N)), make_heap(A, A + N) assert(is_heap(A, A + N)), }
14 Классы итераторов В каждом контейнере STL организованы вложенные классы итераторов. (Это вклю- чено в понятие “контейнер”.) Кроме того, STL содержит несколько самостоятельных классов итераторов. Большинство из них — адаптеры итераторов. Стандарт языка C++ требует, чтобы все стандартные классы итераторов наследо- вались от базового класса iterator (с. 193), что и принято в книге. Однако это всего лишь деталь реализации. В действительности необходимо, чтобы классы итераторов содержали в себе вложенные типы, наличия которых требует iterator_traits (с. 186), а наследование от iterator — только один из возможных способов задания typedef- определений. И это не самый простой способ. Наследование описаний типа от базо- вых классов шаблона имеет несколько сложных технических ограничений, и зачас- тую легче обеспечить вложенные typedef-определения явно. 14.1. Итераторы вставки 14.1.1. front_insert_iterator front_insert_iterator<FrontInsertionSequence> Класс f ront_insert_iterator есть адаптер итератора, который функционирует как Итератор Вывода. Присваивание с помощью front_insert_iteratoг вставляет объект перед первым элементом Последовательности с Начальной Вставкой (с. 153). Отметим, что у Последовательности с Начальной Вставкой уже есть итераторы В частности, Контейнер, а у всех Контейнеров имеются свои собственные итераторы Почему же в таком случае нам нужен еще один вид итератора для Последовательно сти с Начальной Вставкой? Дело в том, что его поведение существенно отличается от поведения собственны* итераторов последовательности. Если Seq — это Последовательность с Начальн Вставкой, то Seq - : iterator имеет семантику перезаписывания, в то время f ront_insert_iterator<Seq> имеет семантику вставки. Если 1 — допусти*1Ь! Seq : iterator, указывающий внутрь последовательности S, то он указывает на ков кретный элемент. Выражение *i = t заменяет этот элемент на t и не меняет обШ
14.1. Итераторы вставки 343 Одичество элементов в S. Но если ii — это допустимый f ront_insert_iterator<Seq>, запись *ii = t эквивалентна записи seq push_f ront(t). Он добавляет новый эле- в S, а не перезаписывает один из существующих элементов. Пример int main() { list<int> L, L. push_front(3), front_insert_iterator<list<int> > ii(L), * ii++ = 0, * ii++ = 1. * ii++ = 2, copy(L begin(), L end(), ostream_iterator<int>(cout, ’’ ")). // Результат: 2103 } В предыдущем примере элементы появились в L в порядке, обратном тому, в кото- ром они добавлялись. Таков общий порядок. Каждое присваивание с помощью и прибавляло новый элемент в начало L. Это обеспечивает простой способ создания новой Последовательности, которая совпадает с перевернутой существующей Последовательностью: копировать все существующие элементы Последовательно- стивfront-insert iterator. int main() { vector<int> V(100), for (int 1=0. i < 100, ++i) V[i] = i. list<int> L; copy(V begin(), V.end(). front_inserter(L)). // В результате L содержит элементы V в обратном порядке } В последнем примере вместо явно написанного конструктора f ront_inse rt_ite rato г использовали вспомогательную функцию f ront_inserter. Использовать ее, как Правило, более удобно, так как пропадает необходимость записывать точный тип r°nt-insert_iterator. Поскольку мы передаем итератор в обобщенный алгоритм, а не с°здаем его на стеке, то нет необходимости в явном упоминании его типа. Hie определено Реализации HP f ront_insert_iterator был определен в <iterator. h>. В соответ- ^Ии со стандартом языка C++ он объявлен в <iterator>. Параметры шаблона ^rontInsertionSequence Тип Последовательности с Начальной Вставкой, в которую будут вставлены значения.
344 Глава 14. Классы итераторов Является моделью Итератора Вывода (с. 112) Требования к типу • Параметр шаблона FrontlnsertionSequence должен быть Последовательностью с Начальной Вставкой. Открытые базовые классы iterator<output_iterator_tag, void, void, void, void> Члены Некоторые члены f ront_insert_iterator нс определены в требованиях к Итератору Вывода, а специфичны для f ront_insert_iterator; они помечены символом ♦. ♦ front.insert-iterator::front_insert_iterator(FrontInsertionSequence& S) Конструктор. Создает f ront_insert_iterator, который вставляет элементы пе- ред первым элементом S. front_insert_iterator::front_insert_iterator(const front_insert_iterator&) Конструктор копирования. (Описан в Присваиваемом.) front_insert_iterator::-front_insert_iterator() Деструктор. (Описан в Присваиваемом.) front_insert_iterator& front_insert_iterator::operator=(const front_insert_iterator&) Оператор присваивания. (Описан в Присваиваемом.) front_insert_iterator& front_insert_iterator::operator*() Используется для реализации выражения Итератора Вывода *1 = х. front_insert_iterator& front_insert_iterator::operator=( const typename FrontlnsertionSequence::value-type&) Используется для реализации выражения Итератора Вывода *1 = х. front_insert_iterator& front_insert_iterator::operator++() Прсинкремент. (Описан в Итераторе Вывода.) front_insert_iterator front_insert_iterator::operator++(int) Постиикремент. (Описан в Итераторе Вывода.) front_insert_iterator::iterator_category Тег, представляющий категорию итератора: output_iterator_tag. (Описан в Итераторе Вывода.)
14.1. Итераторы вставки 345 Ф template <class FrontInsertionSequence> front_insert_iterator<Front!nsertionSequence> front_inserter(Front!nsertionSequence& S) Эквивалентно f ront_insert_iterator<Front!nsertionSequence>(S), вспомогатель- ная функция, существующая главным образом для удобства. Это не функция- член, поэтому параметры шаблона могут подразумеваться и нет необходимости явно описывать тип f ront_insert_iterator. Заметим, что требование определения Итератора Вывода заключается лишь в том, чтобы *и = t было допустимым выражением, и ничего не говорится о самих operator* или operators Очевидная реализация operator* может возвращать замещающий объект, у которого есть подходящий operateг=, и в этом случае замещающий объект непосредственно является самим f ront_insert_iterator. Однако это лишь деталь ре- ализации, па которую не надо полагаться. Всегда следует писать *и = t, как указано в требованиях к Итератору Вывода. 14.1.2. back_insert_iterator back_insert_iterator<BackInsertionSequence> Класс back_insert_iterator — это адаптер итератора, который функционирует как Итератор Вывода: присваивание с помощью back_insert_iterator вставляет объект после последнего элемента Последовательности с Концевой Вставкой (с. 155). Хотя у каждой Последовательности с Концевой Вставкой уже есть свои итераторы, back_insert_iterator необходим потому, что он обеспечивает семантику вставки, а не перезаписи. Если 1 — это допустимый Seq: iterator, который указывает на по- следовательность S, то он указывает на конкретный элемент. Выражение »i = t заме- няет этот элемент на t и не меняет общее количество элементов S. Если и — это back_insert_iterator<Seq>, то запись *и = t эквивалентна записи seq push_back(t). Он добавляет новый элемент в S, а не перезаписывает один из существующих эле- ментов. Пример int main() { list<int> L, L push_front(3), back_insert_iterator<list<int> > ii(L), *n++ = о *ii++ = 1, *n++ = 2. c°Py(L begin(), L end(), ostream_iterator<int>(cout, ’ ’)). // Результат 3012 Об Ратите внимание на отличия от соответствующего примера для f ront_insert_iterator л а 2)- В данном примере элементы появляются в L в том же порядке, в котором они С Являлись, и так всегда. Каждое присваивание прибавляло новый элемент в конец L. ^Довательно, копирование диапазона с помощью back_insert_iterator — это спо- создания Последовательности, являющейся копией другой Последовательности.
346 Глава 14. Классы итераторов int main() { list<int> L, for (int 1=0, i < 100, ++i) L push_back(i), vector<int> v1(L begin(), L end()), vector<int> v2, copy(L begin(), L end(), back_inserter(v2)), assert(v1 == v2), } Эти две версии примерно эквивалентны, но первая, вероятно, быстрее. КонструкТОр vector вставляет все элементы сразу, а сору вставляет их по одному. Если ваш компи- лятор не поддерживает шаблоны-члены, то вы вообще не сможете использовать пер- вую версию — по крайней мере для совсем общих диапазонов итераторов. Между тем вы можете использовать сору и back_insert_iterator, так как они не полагаются на шаблоны-члены. Где определено В реализации HP back_insert_iterator был определен в <iteratoг. h>. В соответствии со стандартом языка C++ он объявлен в <iterator>. Параметры шаблона BacklnsertionSequence Тип Последовательности с Концевой Вставкой, в которую будут вставлены значения. Является моделью Итератора Вывода (с. 112) Требования к типу • Параметр шаблона BacklnsertionSequence должен быть Последовательностью с Концевой Вставкой. Открытые базовые классы iterator<output_iterator_tag, void, void, void, void> Члены Некоторые члены back_insert_iterator не определены в требованиях к Итератору Вывода, а специфичны для back_insert_iterator; они помечены символом ♦. ♦ back_insert_iterator::back_insert_iterator(Back!nsertionSequence& S) Конструктор. Создает back_insert_iterator, который вставляет элементы пос^е последнего элемента S. back_insert_iterator::back_insert_iterator(const back_insert_iterator&) Конструктор копирования. (Описан в Присваиваемом.)
14.1. Итераторы вставки 347 back_insert_iterator::-back_insert_iterator() Деструктор. (Описан в Присваиваемом.) back.insert_ite rato r& back_insert_iterator::operator=(const back_insert_iterator&) Оператор присваивания. (Описан в Присваиваемом.) back.insert.iterator& back-insert_iterator::operator*() Используется для реализации выражения Итератора Вывода *1 = х. back.inse rt_ite ratо r& back-insert-iterator::operator= (const typename BacklnsertionSequence::value_type&) Используется для реализации выражения Итератора Вывода *1 = х. back-insert_iterator& back_insert-iterator::operator++() Преинкремент. (Описан в Итераторе Вывода.) back_insert_iterator back_insert_iterator::operator++(int). Постинкремент. (Описан в Итераторе Вывода.) back-insert_iterator::iterator_category Тег, представляющий категорию итератора: output_iterator_tag. (Описан в Итераторе Вывода.) ♦ template <class BackInsertionSequence> back-insert-iterator<Back!nsertionSequence> back_inserter(Back!nsertionSequence& S) Эквивалентно back_insert_iterator<BackInsertionSequence>(S). Эта вспомогатель- ная функция существует главным образом для удобства: она не является функ- цией-членом, поэтому параметры шаблона могут быть выведены и нет необхо- димости явно описывать тип back_insert_iterator. Заметим, что требование определения Итератора Вывода заключается лишь в том, чтобы *ii = t было допустимым выражением, и ничего не говорится относительно самих operator* или operators Очевидная реализация такова: operator* может воз- вРащать замещающий объект, у которого есть подходящий operators Тогда за- вещающий объект непосредственно является самим back_insert_iterator. Однако лишь деталь реализации, на которую нельзя полагаться. Всегда следует писать *lx г t, как указано в требованиях к Итератору Вывода. insert-iterator Xn8ert-iterator<Container> ^acc insert_i terator — это адаптер итератора, который функционирует как Итера- ор Вывода. Присваивание с помощью insert-iterator вставляет объект в Контейнер, ели ii — это insert-iterator, то и “знает” о Контейнере с и о точке вставки р. Выра- еНие *ii = t выполняет операцию с. insert(р, t).
348 Глава 14. Классы итераторов Хотя у каждого Контейнера уже есть итераторы, insert-iterator необходим пОт му, что обеспечивает семантику вставки, а не перезаписи. Если 1 — это допустимый С iterator, который указывает внутрь контейнера с, то он указывает на конкретны^ элемент. Выражение *1 = t заменяет этот элемент на t и не меняет общее число эЛе ментов С. Если и — допустимый insert_iterator<C>, то запись *и = t эквивалентна записи с inse rt (р, t). При этом добавляется новый элемент в 8, а не перезаписывает ся один из существующих элементов S. Вы можете использовать insert-iterator с любым видом Контейнера, для которо- го определено выражение С insert(р, t). Две разные концепции Контейнера опреде. ляют это выражение: Последовательность (с. 147) и Сортированный Ассоциативный Контейнер (с. 167). Обе концепции определяют вставку в контейнер с помощью вы- ражения с. insert(р, t), но его семантика различается в разных случаях. Для Последовательности S выражение S. insert(р, х) означает вставку значения х непосредственно перед итератором р, то есть версия insert с двумя параметрами по- зволяет контролировать позицию, в которую будет вставлен новый элемент. Для Сор- тированного Ассоциативного Контейнера такой контроль невозможен. Элементы Сор- тированного Ассоциативного Контейнера всегда расположены в порядке возрастания ключей. Версия insert с двумя параметрами в случае Сортированного Ассоциативно- го Контейнера определена лишь для оптимизации. Первый аргумент указывает, с ка- кой позиции начинать поиск. Если вы выполняете присваивание с помощью insert-iterator несколько раз, то в базовый контейнер будет вставлено несколько элементов. В случае Последователь- ности они окажутся в конкретной позиции базовой последовательности в том поряд- ке, в котором они были вставлены. Одним из аргументов конструктора inse rt_iterator является итератор р, и новый диапазон будет вставлен непосредственно перед р. В противоположность этому в случае Сортированного Ассоциативного Контейнера итератор, который вы передаете конструктору insert_iterator, почти не использует- ся. Новые элементы не обязательно сформируют непрерывный диапазон. Они ока- жутся в нужной позиции в контейнере в порядке возрастания ключа. Порядок, в ко- тором они вставляются, влияет только на эффективность; вставка уже отсортирован- ного диапазона в Сортированный Ассоциативный Контейнер выполняется за время O(N). Пример Вставляется диапазон элементов в list. int main() { list<int> L, L push_front(3), insert_iterator<],ist<int> > n(L, L begin()), * 11++ = 0, * 11++ = 1. * n++ = 2. copy(L begin(), L end(), ostream_iterator<int>(cout, ‘ ')), // Результат 0123
14.1. Итераторы вставки 349 объединим два отсортированных списка, вставляя результирующий диапазон в bet. Отметим, что set не может содержать повторяющихся элементов. int main() { consi int N = 6, int A1[N] = {1 3, 5, 7, 9. 11}. int A2[N] - {1. 2 3. 4, 5. 6}. set<int> result, merge(A1, A1 + N A2. A2 + N. inserter(result, result begin())), copy(result begin(), result end(), ostream_iterator<int>(cout, ” )), cout « endl, // Результат 1 2 3 4 5 6 7 9 11 Где определено В реализации HP insert-iterator был определен в <iterator. h>. В соответствии со стандартом языка C++ он объявлен в <iterator>. Параметры шаблона Container Тип Контейнера, в который будут вставлены значения. Является моделью Итератора Вывода (с. 112) Требования к типу • Параметр шаблона Container — модель Контейнера. • Contai ne г — это контейнер переменного размера, как описано в требованиях к Кон- тейнеру. • У Container есть функция-член insert с двумя аргументами. В частности, если с является объектом типа Container, р — объектом типа Container • iterator и v — объектом типа Container::value_type, то с insert (р, v) — допустимое выраже- ‘ ние, и его тип — Container.. iterator. ^крытые базовые классы tteratorcoutpui-iterator-tag, void. void, void, void> It еКоторые члены insert-iterator не определены в требованиях к Итератору Вывода, специфичны для insert-iterator; они помечены символом ♦. ♦ insert-iterator::insert_iterator(Container& с, Container::iterator i) Конструирует insert-iterator, который вставляет объекты в с. Если с — это Последовательность, то каждый объект будет вставлен непосредственно перед
350 Глава 14. Классы итераторов элементом, на который указывает 1. Если с — это Сортированный АссоциатИв ный Контейнер, то первая вставка будет использовать i в качестве указан^ для начала поиска. Итератор 1 должен быть допустимым итератором (разыще нуемым или указывающим за последний элемент) в с. insert-iterator::insert_iterator(const insert_iterator&) Конструктор копирования. (Описан в Присваиваемом.) insert-iterator::-insert_iterator() Деструктор. (Описан в Присваиваемом.) insert-iterator& insert-iterator::operator=(const insert_iterator&) Оператор присваивания. (Описан в Присваиваемом.) insert_iterator& insert-iterator::operator*() Используется для реализации выражения Итератора Вывода *1 = х. insert_iterator& insert-iterator::operator= (const typename Container::value_type&) Используется для реализации выражения Итератора Вывода *i = х. insert-iterator& insert-iterator::operator++() Преинкремент. (Описан в Итераторе Вывода.) insert-iterator insert-iterator::operator++(int) Постинкремент. (Описан в Итераторе Вывода.) insert-iterator::iterator_category Тег, представляющий категорию итератора: output_iterator_tag. (Описан в Итераторе Вывода.) ♦ template <class Container, class Iterator> insert-iterator<Container> inserter(Container& c, Iterator p) Эквивалентно insert_iterator<Container>(c, i). (Подразумевается, что тип i мож- но преобразовать к Container: iterator.) Эта вспомогательная функция суще- ствует главным образом для удобства; она не является функцией-членом, по- этому параметры шаблона могут подразумеваться и нет необходимости явно описывать тип insert_iterator. Заметим, что требование определения Итератора Вывода заключается лишь в т°*м’ чтобы *и = t было допустимым выражением, и ничего не говорится о самих ope rator или operator^. Очевидная реализация operator* может возвращать замешают1111 объект, у которого есть подходящий operators В этом случае замещающий объект непосредственно является самим f ront_insert_iterator. Но это лишь деталь реалп зации, на которую не следует полагаться. Всегда следует писать *n = г, как указан0 в требованиях к Итератору Вывода.
14.2. Итераторы потоков 351 0,2. Итераторы потоков большинство встроенных итераторов STL выполняют итерации над элементами к0Йтейнера. Однако поскольку концепции итераторов очень обобщены, они могут та1слсе обеспечивать итерации по любым упорядоченным значениям, а не только по значениям, размещенным в контейнере. Примером такой обобщенности являются ^ераторы потоков STL, которые выполняют ввод и вывод, используя библиотеку потоков ввода/вывода языка C++. (4.2.1. istream.iterator i8tream_iterator<T, charT, traits. Distance> 1Stream_iterator — это Итератор Ввода, который выполняет форматированный ввод объектов типа Т из некоторого basic_istream. По достижении конца потока 1stream_ite rate г принимает специальное значение “конец потока”, которое является итератором, указывающим за последний элемент. Должны быть выполнены все огра- ничения Итератора Ввода, включая ограничения на порядок операций operator* и ope rat о г++. Определение istream_iterator претерпело изменения по сравнению с перво- начальной версией HP STL, поскольку теперь в языке C++ имеются более общие возможности потоков ввода/вывода, чем прежде. Тем не менее нет необходимости усложнять описание: новые параметры шаблонов имеют значения по умолчанию, иу istream_iterator<int> то же поведение, что и было всегда. В первоначальной версии потоков ввода/вывода языка C++ ist ream был классом, который читал символы типа ch а г из некоторого устройства ввода. В стандарте языка C++, однако, istream не является классом, а определен как typedef. Это псевдоним Wbasic_istream<char, char_traits<char> >, где класс basic_istream — шаблон, кото- рый выполняет форматированный ввод, используя обобщенные символы. Разница между ist ream_ite rate г в HP STL и в стандарте, таким образом, заключается в добав- лении параметров шаблона charT и traits. Итератор типа istream_iterator<T, charT, traits, Distance> читает значения из basic_istream<charT, traits>. Сейчас, в 1998 году, не все реализации C++ обеспечивают новую шаблонную вер- сию потоков ввода/вывода. Для максимальной переносимости лучше ограничиться качениями по умолчанию. Можно быть уверенным, что istream_iterator<T> будет лести себя корректно независимо от того, новую или старую версию потоков ввода/ вывода вы используете. Пример int main() { vector<int> V, copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(V)), определено b Реализации HP istream_iterator был определен в <iterator. h>. В соответствии co ТаНдартом языка C++ он объявлен в <iterator>.
352 Глава 14. Классы итераторов Параметры шаблона Т Типзначения istream_iterator operator* итераторавозвра щает тип const Т&. charT Тип символов в потоке ввода. По умолчанию: char t raits Класс признаков символов в потоке ввода. По умолчанию: cha r_t raits<cha гТ> Distance Разностный тип итератора. По умолчанию: pt rdif f_t Является моделью Итератора Ввода (с. 109) Требования к типу • Тип значения Т — модель Конструируемого по Умолчанию. • Тип значения Т таков, что для объекта t типа Т и потока 1 типа basic_ist ream<charT, traits> определено выражение i >> t. • Типы cha гТ и t raits соответствуют требованиям к типам символов и признаков символов потока.Ф) • Тип Distance является, как описано в требованиях Итератора Ввода, целочис- ленным типом со знаком. Открытые базовые классы iterator<input_iterator_tag, Т, Distance, const Т*. const Т&> Члены Некоторые члены 1st ream_iterator не определены в требованиях к Итератору Ввода, а специфичны для istream_iterator; они помечены символом ♦. ♦ istream.iterator::char_type Тип символа итератора: charT ♦ ist ream_ite rato г::t raits_type Тип признаков символов итератора: t raits ♦ istream_iterator::istream_type Тип потока итератора: basic_istream<charT, traits> istream_iterator::iterator_category Категория итератора: input_iterator_tag (Описана в Итераторе Ввода.) } Типы символа и признаков символа — часть библиотеки ввода/вывода языка C++, а по STL. >|() mv в книге требования к -ним чипам нс описаны
14.2. Итераторы потоков 353 istream_iterator::value_type Тип значения итератора: Т (Описан в Итераторе Ввода.) istream_iterator::difference_type Разностный тип итератора: Distance (Описан в Итераторе Ввода.) istream_iterator::pointer Тип указателя итератора: const Т* (Описан в Итераторе Ввода.) istream_iterator::reference Ссылочный тип итератора: const Т& (Описан в Итераторе Ввода.) Ф istream_iterator::istream_iterator(istream_type& s) Конструктор. Создает istream_iterator, который читает значения из потока вво- да s. Когда s доходит до конца потока, то итератор будет равен итератору конца потока, который создается с помощью конструктора по умолчанию. ф istream_iterator::istream_iterator() Конструктор по умолчанию. Конструирует итератор конца потока. Это итера- тор за последний элемент, он полезен при создании “диапазона итераторов”. istream_iterator::istream_iterator(const istream_iterator&) Конструктор копирования. (Описан в Итераторе Ввода.) istream_iterator& istream_iterator::operator=(const istream_iterator&) Оператор присваивания. (Описан в Итераторе Ввода.) const Т& istream_iterator::operator*() const Возвращает следующий объект в потоке. (Описан в Итераторе Ввода.) const Т* istream_iterator::operator->() const i->mэквивалентно (*i). m. (Описан в Итераторе Ввода.) istream_iterator& istream_iterator::operator++() Преинкрсмент. (Описан в Итераторе Ввода.) istream_iterator istream_iterator::operator++(int) Постинкремент (Описан в Итераторе Ввода.) bool operator==(const istream_iterator&, const istream_iterator&) Оператор равенства. (Описан в Итераторе Ввода.)
354 Глава 14. Классы итераторов 14.2.2. ostream_iterator ostream.iterator<T, charT, traits> ostream_iterator — Итератор Вывода, который выполняет форматированный вывод объектов типа Т в некоторый basic.ost ream. Должны быть выполнены все ограничения Итератора Вывода, включая ограничения на порядок операций operator* и operator++ Определение ostream_iterator претерпело изменения по сравнению с оригиналь- ной версией HP STL по той же причине, что istream_iterator: библиотека потоков ввода/вывода языка C++ стала более обобщенной. Как и в случае с ist ream_iterator нет необходимости заботиться о дополнительной сложности, если она вам не нужна Новые параметры шаблонов имеют значения по умолчанию, и у ost ream_ite rator<int> то же поведение, что и всегда. В новой версии потоков ввода/вывода ost ream является не типом, a typedef-onpe- делением. Это псевдоним для basic_ost ream<char, char_traits<char> >. Соответствен- но, у новой версии ost ream_iterator есть дополнительные параметры шаблона, кото- рые передаются basic_ost ream. Сейчас, в 1998 году, не все реализации C++ обеспечивают новую версию шаблон- ных потоков ввода/вывода. Для максимальной переносимости лучше ограничиться значениями по умолчанию. Можно быть уверенным, что ost ream_ite rato г<Т> будет вести себя корректно независимо от того, новую или старую версию потоков ввода/ вывода вы используете. Пример Элементы vector копируются на стандартный вывод в обратном порядке по одно- му в строке. В примере используется двухаргументная версия конструктора ost ream_iterator. Второй аргумент — это строка, которая печатается после каждого элемента. int main() { vector<int> V, for (int i=0, i < 20, ++i) V push_back(i); reverse_copy(V begin(), V end(), ostream_iterator<int>(cout, ”\n’’)), } Где определено В реализации HP ostream_iterator был определен в <iterator. h>. В соответствии со стандартом языка C++ он объявлен в <iterator >. Параметры шаблона Т Тип значения, которое будет записано в поток вывода. На бор типов значений ost ream_iterator состоит из единствен ного типа Т. charT Тип символов в потоке вывода. По умолчанию: char t raits Класс признаков символов в потоке вывода. По умолчанию: cha r_t raits<cha rT>
14.2. Итераторы потоков 355 00ляется моделью итератора Вывода (с. 112) Требования к типу • Тип Т таков, что для значения t типаТ и потока вывода out типа basic_ost ream< charT, traits> определено выражение out « t. • Типы charT и traits соответствуют требованиям к типу символов и признаков символов в потоке.Ф) Открытые базовые* классы iterator<output_iterator_tag. void, void, void, void> Члены Некоторые члены ostream_iterator не определены в требованиях к Итератору Выво- да, а специфичны для ost ream_iterator; они помечены символом ф. Ф ostream_iterator::char_type Тип символа итератора: charT Ф ostream_iteгatoг::t raits_tyре Тип признаков символов итератора: traits Ф ostream_iterator::ostream_type > Тип потока итератора: basic_ost ream<cha rT, t raits> ostream_iterator::iterater„category Категория итератора: output_iterator_tag (Описана в Итераторе Вывода.) ♦ ostream_iterator::ostream_iterator(ostream_type& s) Конструктор. Создает такой ost ream_iterator, что присваивание ему элемента t эквивалентно s << t. ♦ ostream_iterator::ostream_iterator(ostream_type& s, const charT* delim) Конструктор с разделителем. Создает такой ost ream_iterator, что присваивание ему элемента t эквивалентно s << t « delim. ostream_iterator::ostream_iterator(const ostream_iterator&) Конструктор копирования. (Описан в Итераторе Вывода.) ostream_iterator& ostream_iterator::operater=(const ostream_iterator&) Оператор присваивания. (Описан в Итераторе Вывода.) ^Типы символа и признаков символа — часть библиотеки ввода/вывода языка C++, а не STL, поэто- У в книге 1ребования к этим типам не описаны
356 Глава 14. Классы итераторов ostream_iterator& ostream_iterator::operator=(const Т&) Используется для реализации выражения Итератора Вывода *1 = t. ostream_iterator& ostream_iterator::operator^) Используется для реализации выражения Итератора Вывода *1 = t. ostream_iterator& ostream_iterator::operator++() Преипкремент. (Описан в Итераторе Вывода.) ostream_iterator ostream_iterator::operator++(int) Постинкремент. (Описан в Итераторе Вывода.) Заметим, что требования к Итератору Вывода заключаются в том, чтобы *n = t было допустимым выражением, и ничего не говорится об operator* или operators Очевид- ная реализация operator* может возвращать замещающий объект, у которого есть подходящий operateг= В этом случае замещающий объект — непосредственно ost ream_iterato г. Но это лишь деталь реализации, на которую не следует полагаться. Всегда следует писать *и = t, как указано в требованиях к Итератору Вывода. 14.2.3. 1streambuf-iterator istreambuf_iterator<charT, traits> Класс ist reambuf_iterator очень похож на istream_iterator. Однако вместо форма- тированного ввода произвольных типов он читает одиночные символы из потока ввода. Это Итератор Ввода, и, как и istream_iterator, ist reambuf_iterator, он приоб- ретает специальное значение “за последний элемент”, когда доходит до конца потока. Пример В буфер читаются все оставшиеся символы из стандартного потока ввода. int ma1п() { istreambuf_iterator<char> first(cin), ist reambuf_iterator<char> end_of_stream, vector<char> buffer(first, end_of_stream); Где определено Реализация HP не содержит ist reambuf_iterator. В соответствии co стандартом язы ка C++ он объявлен в <iterator>. Параметры шаблона charT Тип значения ist reambuf_iterator. Итератор читает сиМБ° лы типа charT. t raits Класс признаков символов потока ввода. По умолчанию: cha r_t raits<cha rT>
14.2. Итераторы потоков 357 ^ляется моделью итератора Ввода (с. 109) Требования к типу • Типы charT и traits соответствуют требованиям к типам символов и признакам символов потока.Ф) Открытые базовые классы iteratorsnput_iterator_tag, charT. traits off_type, charT*, charT&> Члены Некоторые члены istreambuf_iterator не определены в требованиях к Итератору Ввода, а специфичны для ist reambuf..iterator; они помечены символом Ф. Ф istreambuf-iterator::char_type Эквивалентно cha гТ ф istreambuf_iterator::traits_type Эквивалентно t raits ф istreambuf-iterator::int_type Эквивалентно traits- int_type Ф istreambuf-iterator::streambuf_type Эквивалентно basic_st reambuf <cha rT, t raits> ♦ istreambuf-iterator::istream.type Эквивалентно basic_ist ream<charT, t raits> istreambuf-iterator::iterator_category Категория итератора: input_iterator_tag (Описана в Итераторе Ввода.) istreambuf-iterator::value_type Тип значения итератора: charT (Описан в Итераторе Ввода.) istreambuf-iterator::difference-type Тип разности итератора: t raits. off.type (Описан в Итераторе Ввода.) istreambuf-iterator::pointer Тип указателя итератора: charT* (Описан в Итераторе Ввода.) istreambuf-iterator::reference Ссылочный тип итератора: charT& (Описан в Итераторе Ввода.) _____________ Типы символа и при таков символа — част ь библиотеки ввода/вывода языка C++, а по STL, но >то У 8 Книге требования к типам не описаны
358 Глава 14. Классы итераторов ♦ istreambuf-iterator::istreambuf_iterator(istream_type& s) Конструктор. Создает ist reambuf „iterator, который читает значения из потока ввода s. Когда s доходит до конца потока, то итератор будет равен итератору “конец потока”, созданному с помощью конструктора по умолчанию. ♦ istreambuf-iterator::istreambuf„Iterateг(streambuf_type* s) Конструктор. Создает ist reambuf_iterator, который читает значения из st reambuf *s. Когда st reambuf доходит до конца потока, то итератор будет равен итератору конца потока, созданному с помощью конструктора по умолчанию. Если s - пустой указатель, то конструктор эквивалентен конструктору по умолчанию ♦ istreambuf-iterator::istreambuf_iterator() Конструктор по умолчанию. Конструирует итератор “конец потока”, который указывает за последний элемент и полезен при создании “диапазона итера- торов”. charT istreambuf-iterator::operator*() const Возвращает следующий символ в потоке. (Описан в Итераторе Ввода.) istreambuf_iterator& istreambuf-iterator::operator++() Преинкремент. (Описан в Итераторе Ввода.) istreambuf-iterator istreambuf-iterator::operator++(int) Постинкремент. Отметим, что реализация оператора постинкремента нетриви- альна, поскольку он должен “увеличить” st reambuf (то есть перейти к следующе- му символу), но вернуть итератор, который указывает на текущий символ. Обыч- но оператор постинкремента возвращает замещающий класс, который можно преобразовать к istreambuf_iterator. (Описан в Итераторе Ввода.) bool operator==(const istreambuf_iterator&, const istreambuf_iterator&) Оператор равенства. (Описан в Итераторе Ввода.) ♦ bool istreambuf-iterator::equal(const istreambuf_iterator&) const Выражение i equal(j) эквивалентно i == j. 14.2.4. ostreambuf_iterator ostreambuf_iterator<charT, traits> Класс ost reambuf_iterator — Итератор Вывода, который записывает символы в поток вывода. В отличие от ostream_iterator, вместо обобщенного форматированного вы- вода произвольных типов он записывает одиночные символы с помощью функции- члена sputc класса st reambuf. Пример Класс ost reambuf_iterator используется для записи строки на стандартный вывод*
14.2. Итераторы потоков 359 int main() { string s = "This is a test.\n", copy(s begin(), s end(), ostreambuf_iterator<char>(cout)); } Где определено gреализацию HP ostreambuf.iterator не входит. В соответствии со стандартом язы- каС++ он объявлен в <iterator>. Параметры шаблона charT Тип значения ostreambuf.iterator. Итератор записывает сим- волы типа charT. traits Класс признаков символов потока вывода. По умолчанию: char_traits<charT> Является моделью Итератора Вывода (с. 112) Требования к типу • Типы cha гТ и t raits соответствуют требованиям к типам символов и признакам символов потока. Открытые базовые классы iterator<output_iterator_tag, void, void, void, void> Члены Некоторые члены ost reambuf_ite rator не определены в требованиях к Итератору Вы- вода, а специфичны для ostreambuf.iterator; они помечены символом ♦. ♦ ostreambuf_iterator::char_type Эквивалентно cha rT ♦ ostreambuf_iterator;:traits_type Эквивалентно t raits ♦ ostreambuf-iterator::streambuf.type Эквивалентно basic_st reambuf<charT, t raits> ♦ ostreambuf_iterator::ostream.type Эквивалентноbasic_ostream<charT, traits> ♦ ostreambuf-iterator::ostreambuf_iterator(ostream_type& s) Конструктор. Создает такой ost reambuf_iterator, что присваивание ему эквива- лентно s rdbuf()->sputc(c). ^Типы символа и признаков символа — часть библиотеки ввода/вывода языка C++, а не STL, иоэто- В Книге требования к типам неонисаны.
360 Глава 14. Классы итераторов ostreambuf.iterator::iterator_category Категория итератора: output.iterator.tag (Описана в Итераторе Вывода.) ♦ ostreambuf.iterator::ostreambuf_iterator(streambuf_type* s) Конструктор. Создает такой ost reambuf _iterator, что присваивание ему эквива- лентно s->sputc(c). ostreambuf_iterator& ostreambuf.iterator::operator=(const charT&) Используется для реализации выражения Итератора Вывода *1 = с. Если i — Ите_ ратор типа ostreambuf.iterator, то выражение *1 = с эквивалентно s->sputc(c) где s — это st reambuf итератора i. ostreambuf_iterator& ostreambuf.iterator::operator^) Используется для реализации выражения Итератора Вывода *1 = с. ostreambuf_iterator& ostreambuf.iterator::operator++() Преинкремент. (Описан в Итераторе Вывода.) ostreambuf.iterator ostreambuf.iterator::operator++(int) Постинкремент. (Описан в Итераторе Вывода.) ♦ bool ostreambuf.iterator::failed() const Возвращает t rue тогда и только тогда, когда один из предыдущих вызовов sputc вернул eof. 14.3. reverse_iterator reverse.iterator<Iterator> Класс reve rse.ite rato r — это адаптер итератора, который обеспечивает возможность обратного прохода по диапазону. Применение operateг++ к объекту класса reverse_iterator<Iter> аналогично применению operator- к объекту класса Пег. и наоборот. Для диапазона [f, 1) диапазон [reverse_iterator<Iter>(D’ reverse.iterator<Iter>(f)) содержит те же элементы, но в обратном порядке. Данное свойство имеет важное следствие, f и 1 поменялись ролями: 1 представляет собой конец диапазона, a reverse.iterator(l) — начало перевернутого диапазона. На- чало и конец диапазона, тем не менее, не симметричны, так как f — это первый итера тор в диапазоне [f, 1), а 1 указывает за последний итератор в диапазоне. Следовательно, reverse.iterator(l) не может указывать на тот же элемент, что и (1 может вообще не указывать на какой-либо элемент, а быть итератором за последний элемент.) Вместо этого reverse.iterator(l) должен указывать на элемент *(1 - 1 Основное тождество реверсивных итераторов: &*(reverse_iterator(i)) == &*О ' , reverse.iterator<Iter> хранит значение базового итератора типа Iter, и можно оо ратиться к нему с помощью функции-члена base(). Таким образом, эквивалентный спо соб написания основного тождества реверсивных итераторов: &*ri == &*( ri. base()'
14.3. reverse iterator 361 p оригинальной версии HP STL было два разных класса: reverse_iterator, ко- торЫЙ можно было использовать только с Итераторами Произвольного Доступа, 0 reverse_bidirectional_iterator, используемый с другими Двунаправленными Ите- аторами- Причина этого разграничения была чисто техническая и теперь reverse__bidirectional_iterator не нужен. Некоторые реализации все еще обеспечи- ваК>т его для обратной совместимости, но из стандарта языка C++ он был удален. Пример int main() { const int N = 10, int A[N] = {2. 5. 7, 8, 1, 5, 3, 6. 9, 1}, list<int> L(A, A + N), // Печать элементов L в прямом порядке copy(L oegin(), L end(), ostream_iterator<int>(cout. ” ")), cout « endl. 11 Печать элементов L в обратном порядке copy(reverse_iterator<list<int>. iterator>(L end()), reverse_iterator<list<int> iterator>(L.begin()), ostream_iterator<int>(cout. " ")), cout « endl // Найти первое вхождение числа 5 list<int> iterator 11 = find(L begin(). L end(). 5). // Найти последнее вхождение числа 5 llst<int> iterator 12 = (find(reverse_iterator<list<int> .iterator>(L end()), reverse_iterator<list<int> iterator>(L begin()),5)) base(), -’12, assert(*i1 ~ 5 && *12 == 5 && distance(i1. 12) == 4); Скорее всего, вы никогда не стали бы писать подобный код, если бы нужно было Пройтись по списку в обратном порядке. Как и все другие Реверсивные Контейнеры, обеспечивает функции-члены и typedef-определения, которые облегчают ис- пользование реверсивных итераторов. Следовательно, вы записали бы hst<int> reverse_iterator ri = L rbegin(), aHe reverse_iterator<list<int> iterator> ri(L rbeginO), оригинальной версии HP STL не было iterator_traits (c 186)
362 Глава 14. Классы итераторов Если вы определяете Реверсивный Контейнер, то должны обеспечить такие typedef определения и функции-члены в вашем контейнере, и, возможно, соответствую^^ фрагмент кода будет выглядеть примерно так: typedef std reverse_iterator<iterator> reverse_iterator; typedef std reverse_iterator<const_iterator> const_reverse_iterator reverse_iterator rbegin() { return reverse_iterator(end()), } reverse_iterator rend() { return reverse_iterator(begin()), } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()), } Где определено В реализации HP reverse_iterator был определен в dterator. h>. В соответствии со стандартом языка C++ он объявлен в <iterator>. Параметры шаблона Iterator Тип базового итератора этого реверсивного итератора - Двунаправленный Итератор. Является моделью Двунаправленного Итератора (с. 117). Если Iterator — модель Итератора Произволь- ного Доступа (с. 118), то reverse_iterator<Iterator> — также модель Итератора Про- извольного Доступа. Требования к типу Параметр шаблона Iterator — модель Двунаправленного Итератора. Заметим, что не- которые функции-члены reverse_iterator можно использовать, только если Iterator является также моделью Итератора Произвольного Доступа. Открытые базовые классы iterator<typename iterator_traits<Iterator>. iterator_category, typename iterator_traits<Iterator>--value-type, typename iterator_traits<Iterator> difference_type, typename iterator_traits<Iterator>--pointer, typename iterator_traits<Iterator> reference> Члены Некоторые члены reverse_iterator не определены в требованиях к Двунаправленно му Итератору и Итератору Произвольного Доступа, а специфичны для reve rse_ite rato они помечены символом ♦. reverseJLterator::iterator_category Тег категории реверсивного итератора тот же, что и тег категории Iterator. (Описан в Итераторе Ввода.)
14.3. reverse.iterator 363 reverse_iterator::value_type Тип значения реверсивного итератора тот же, что и тип значения Iterator. (Описан в Итераторе Ввода.) reverse-iterator::difference.type Тип разности реверсивного итератора тот же, что и тип разности Iterator. (Описан в Итераторе Ввода.) reverse-iterator::pointer Тип указателя реверсивного итератора тот же, что и тип указателя Iterator. (Описан в Итераторе Ввода.) reverse-iterator::reference Ссылочный тип реверсивного итератора тот же, что и ссылочный тип Iterator (Описан в Итераторе Ввода.) ♦ reverse-iterator::iterator-type Тот же, что и Iterator. ♦explicit reverse-iterator::reverse_iterator(Iterator i) Конструктор. Создает reverse_iterator, базовым итератором которого является i. reverse-iterator::reverse_iterator() Конструктор по умолчанию. (Описан в Итераторе Ввода.) reverse-iterator:: reverse_iterator(const reverse_iterator&) Конструктор копирования. (Описан в Итераторе Ввода.) ♦ template <class I> reverse-iterator::reverse_iterator(const reverse_iterator<I>& i) Обобщенный конструктор копирования. Создает reverse_iterator, базовым ите- ратором которого является i. base(). Этот конструктор действует так: тип reve rse.ite rato г<Х> можно преобразовать к ти- пу reverse_iterator<Y> тогда и только тогда, когда X можно преобразовать к Y. В ос- новном он существует для того, чтобы тип reve rse.ite rato г из Реверсивного Контей- нера можно было преобразовать к его типу const.reverse.iterator, точно так же как каждый тип iterator из Контейнера можно преобразовать к его типу const-iterator. reverse_iterator& reverse-iterator::operator=(const reverse-iterator&) Оператор присваивания. (Описан в Итераторе Ввода.) ♦ Iterator reverse.iterator::base() const Возвращает базовый итератор. reference reverse-iterator::operator*() const Возвращает элемент, на который указывает итератор. (Описан в Однонаправленном Итераторе.)
/1 Глава 14. Классы итераторов reve rse_iterator& reverse_iterater::operator++() Преинкремент. (Описан в Однонаправленном Итераторе.) reverse_iterator reverse_iterator::operator++(int) Постинкремент. (Описан в Однонаправленном Итераторе.) reverse_iterator& reverse_iterator::operator--() Предекремент. (Описан в Двунаправленном Итераторе.) reverse_iterator reverse_iterator::operator--(int) Постдекремент. (Описан в Двунаправленном Итераторе.) reverse_iterator reverse_iterator::operator+(difference_type) Сложение итераторов. Этот метод можно использовать, только если Iterator - модель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) reverse-iterator& reverse-iterator::operator+=(difference-type) Сложение итераторов. Этот метод можно использовать, только если Iterator - модель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) reverse-iterator reverse-iterator::operator-(difference_type) Вычитание итераторов. Этот метод можно использовать, только если Iterator - модель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) reverse-iterator& reverse-iterator::operator-=(difference_type) Вычитание итераторов. Этот метод можно использовать, только если Iter at or - модель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) reference& reverse-iterator:;operator[](difference_type) const Доступ к элементу. Этот метод можно использовать, только если Iterator — мо- дель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) reverse-iterator operator+(difference_type, reverse-iterator) Сложение итераторов. Эту функцию можно использовать, только если Iterate^ " модель Итератора Произвольного Доступа (Описан в Итераторе Произвольного Доступа.) difference-type operator-(const reverse_iterator&, const reverse_iterator&) Возвращает разность двух итераторов. Эту функцию можно использовать, то чьк° если Iterator — модель Итератора Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.)
14.4. raw storage iterator 365 bool operator==(const reverse_iterator&, const reverse_iterator&) Оператор равенства. (Описан в Итераторе Ввода.) bool operator<(const reverse_iterator&, const reverse_iterator&) Возвращает истинность утверждения, что первый аргумент предшествует вто- рому. Этот метод можно использовать, только если Iterator — модель Итерато- ра Произвольного Доступа. (Описан в Итераторе Произвольного Доступа.) 14.4. raw_storage_iterator raw_storage_iterator<Forwardlterator, T> Класс raw_storage_iterator — адаптер, который позволяет объединять алгоритмы STL с низкоуровневым управлением памятью. Его можно использовать, когда необ- ходимо разделить распределение памяти и конструирование объекта. Если 1 — это итератор, указывающий на область неинициализированной памяти, то вы можете использовать construct (с. 198) или форму оператора new с указанием размещения для создания объекта на месте, на которое указывает 1. Адаптер raw_storage_iterator — Итератор Вывода, делающий эту процедуру более удобной. Он конструирует объекты в памяти, на которую указывают итераторы типа Однонап- равленного Итератора. raw_storage_iterator<Iter> г ссылается на то же место памяти, что и некоторый базовый итератор 1 типа Iter. Выражение *г = х эквивалентно construct(&*i, х). Адаптер raw_sto rage.ite rato г следует использовать только при написании контей- нера или Адаптируемого алгоритма. Скорее всего, он вам вообще не понадобится. Ин- терфейс адаптера raw_storage_iterator отличается от интерфейса construct, и обыч- но удобнее применять непосредственно const ruct (или один из алгоритмов, которые инициализируют все элементы в диапазоне). Можно (хотя довольно сложно) написать код с помощью raw_storage_iterator, на- дежно работающий при исключительных ситуациях. Однако гораздо проще исполь- зовать алгоритмы uninitialized_copy (с. 200), uninitialized-! ill (с. 202) и uninitialized-!ill_n (с. 204). Пример class Int { Public Int(inr x) val(x) {} // У Int нет конструктора по умолчанию int get() { return val, } private int val }, int main() { int A1[] = {1. 2, 3, 4, 5, 6, 7}, const int N = sizeof(A1) / sizeof(int),
366 Глава 14. Классы итераторов Int* А2 = (Int*) malloc(N * sizeof(Int)), transform(A1, A1 + N, raw_storage_iterator<Inr*, int>(A2). negate<int>()). } Где определено В реализации HP raw_storage_iterator был определен в iterator. h>. В соответствии со стандартом языка C++ он объявлен в <memory>. Параметры шаблона Forwarditerator Тип базового итератора raw_storage_iterator. Т Тип, который будет использоваться в качестве аргумента конструктора. Требования к типу • Forwarditerator — модель Однонаправленного Итератора. • Forwarditerator — изменяемый итератор. • У типа значения Forwarditerator есть конструктор, который принимает един- ственный аргумент типа Т. Открытые базовые классы iterator<output_iterator_tag, void, void, void, void> Члены Некоторые члены raw_storage_iterator не определены в требованиях к Итератору Вывода, а специфичны для raw_storage_iterator; они помечены символом ♦. ♦ raw_storage_iterator::raw_storage_iterator(Forwarditerator i) Конструктор. Создает raw_storage_iterator, базовым итератором которого яв- ляется 1. raw_storage_iterator::raw_storage_iterator(const raw_storage_iterator&) Конструктор копирования. (Описан в Итераторе Вывода.) raw.storage_ite rato r& raw_storage_iterator::operator=(const raw_storage_iterator&) Оператор присваивания. (Описан в Итераторе Вывода.) raw_storage_iterator& raw_storage_iterator: :operator*() Используется для реализации выражения Итератора Вывода *i = t. (Описан в Итераторе Вывода.) raw_storage_iterator& raw_storage_iterator::operator=(const T& val) Используется для реализации выражения Итератора Вывода *1 = t. Конструиру- ет объект в том месте, на которое указывает итератор, используя val как аргу- мент конструктора. Типом нового объекта является тип значения Forwardlteratoь
14.4. raw storage iterator 367 raw_storage_iterator& raw_storage_iterator::operator++() Преинкремент. (Описано в Итераторе Вывода.) raw_storage_iterator raw_storage_iterator::operator++(int) Постинкремент. (Описано в Итераторе Вывода.) raw_storage_iterator::iterator_category Тег, представляющий категорию итератора: output_iterator_tag. (Описан в Итераторе Вывода.) Отметим, что требования определения Итератора Вывода заключаются лишь в том, чтобы *1 = t было допустимым выражением, и ничего не говорится о самих operator* или operators Самая очевидная реализация operator* может возвращать заметаю- щий объект, у которого есть подходящий operators В этом случае замещающий объект является самим raw_storage_iterator. Но это лишь деталь реализации, на ко- торую не следует полагаться. Всегда следует писать *i = t, как указано в требованиях к Итератору Вывода.
15 Классы функциональных объектов Как обсуждалось в главе 8, многие алгоритмы STL используют функциональные объек- ты для параметризации своего поведения. STL включает в себя также большую кол- лекцию стандартных функциональных объектов, выполняющих основные арифме- тические и логические операции. Все стандартные функциональные объекты STL являются моделями Адаптируе- мой Унарной Функции (с. 127) или Адаптируемой Бинарной Функции (с. 128), то есть они содержат объявления вложенных типов, точно задающих типы их аргументов и тип возвращаемого значения. Если вы пишете свой собственный функциональный объект, то можно обеспечить наличие вложенных типов (способ не единственный и не обязательно предпочтительный), унаследовав объект от одного из пустых базовых классов unary_f unction (см. ниже) или binary_function (с. 369). Иногда проще напи- сать typedef-определения, потому что язык C++ накладывает сложные технические ограничения на наследование описаний типов от шаблонного базового класса. Стандарт языка C++ требует, чтобы все стандартные классы функциональных объектов наследовались от una ry_f unction или binary_f unction; данная книга следует стандарту в этом отношении, но необходимо помнить, что это просто деталь реализа- ции. Для функциональных объектов важно объявить вложенные типы, которые требу- ются концепциями Адаптируемой Унарной Функции и Адаптируемой Бинарной Функции 15.1. Базовые классы функциональных объектов 15.1.1. unary_function unary_function<Arg, Result> Класс unary_function — это пустой базовый класс. У него нет ни функций-членов, ни полей, но есть информация о типах. Он существует для удобства определения тип06, являющихся моделями концепции Адаптируемой Унарной Функции (с. 127). ЛюоаЯ модель Адаптируемой Унарной Функции должна содержать вложенные typedef-onpe деления, и один из способов определить их заключается в том, чтобы наследовать о1 базового класса unary_function.
15.1. Базовые классы функциональных объектов 369 Пример struct sine public unary_function<double, double> { double operator()(double x) const { return sin(x), } } Где определено g реализации HP unary_f unction был определен в заголовочном файле <f unction h>. В соответствии со стандартом языка C++ он объявлен в заголовочном файле «functional^ Параметры шаблона дгд Тип аргумента функционального объекта. Result Тип результата функционального объекта. Является моделью Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Открытые базовые классы Отсутствуют. Члены Все члены unary_f unction являются вложенными типами: unary.function::argument-type Тип аргумента функционального объекта. Это typedef параметра шаблона Arg. unary_function::result-type Тип результата функционального объекта. Это typedef параметра шаблона Result. 15.1.2. binary-.functi.on binary_function<Arg1, Arg2, Result> Класс binary function — пустой базовый класс. У него нет ни функций-членов, ни полей, есть только информация о типах. Он существует лишь для удобства определе- ния типов, являющихся моделями концепции Адаптируемой Бинарной Функции. Лю- бая модель Адаптируемой Бинарной Функции должна содержать вложенные typedef- °пределения типов, что можно сделать с помощью наследования от базового класса binary_function. Пример struct exponentiate public binary_function<double. double, double> { double operator()(double x double y) const { return pow(x, y), } } fye определено в QРеализации HP binary function был определен в <function. h>. В соответствии co стандартом языка C++ он объявлен в заголовочном файле <f unctional>.
370 Глава 15. Классы функциональных объектов Параметры шаблона А гg 1 Тип первого аргумента функционального объекта. Arg2 Тип второго аргумента функционального объекта. Result Тип результата функционального объекта. Является моделью Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Открытые базовые классы Отсутствуют. Члены Все члены binary_f unction являются вложенными типами: binary_function::first-argument-type Тип первого аргумента функционального объекта. Это typedef параметра шаб- лона Arg 1. binary-function::second-argument.type Тип второго аргумента функционального объекта. Это typedef параметра шаб- лона Arg2. binary-function::result-type Тип результата функционального объекта. Это typedef параметра шаблона Result. 15.2. Арифметические операции 15.2.1. plus (сложение) plus<T> Класс plus<T> является Адаптируемой Бинарной Функцией. Если f — объект класса plus<T>, а х и у — значения типа Т, то f (х, у) возвращает х + у. Пример Каждый элемент в v3 будет суммой соответствующих элементов из v1 и v2. int main() { const int N - 1000, vector<double> v1(N). vector<double> v2(N). vector<double> v3(N), generate(v1 begin(), v1.end(), rand), fill(v2 begin(), v2 end(), -RAND_MAX / 2 ), transform(v1 begin(), v1 end(), v2.begin(), v3 begin(), plus<double>()),
15.2. Арифметические операции 371 for (int 1=0, i < N; ++1) assert(v3[i] == v1[i] + v2[i]). } Где определено gреализации HP plus был определен в заголовочном файле <f unction. h>. В соответ- ствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101). Требования к типу • Т является Присваиваемым. • Т является числовым типом. Если х и у — значения типа Т, то выражение х + у определено, а его тип можно преобразовать к Т. Открытые базовые классы binary_function<T, Т, Т> Члены plus::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемой Бинарной Функции.) plus:: second_argiiment.tyре Тип второго аргумента: Т (Описан в Адаптируемой Бинарной Функции.) plus::result-type Тип результата: Т (Описан в Адаптируемой Бинарной Функции.) Т plus::operator()(const Т& х, const Т& у) const Оператор вызова функции. Результат: х + у. (Описан в Адаптируемой Бинарной Функции.) plus::plus() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) '5-2.2. minus (вычитание) Ш1пие<т> ^асс minus<T> является Адаптируемой Бинарной Функцией. Если f — объект класса ^nus<T>, а х и у — значения типаТ, то f(x, у) возвращает х - у.
372 Глава 15. Классы функциональных объектов Пример Создастся vector v3, каждый элемент которого — разность соответствующихэлемен то в из v1 и v2. int main() const int N = 1000, vector<double> v1(N), vector<double> v2(N), vecior<double> v3(N), generate(v1 begin(), v1 end(), rand) fi11 (v2 begin(), v2 end(), RAND.MAX / 2 ), tгansform(v1 begin(), v1 end(), v2 beginQ. v3 begin(), minus<double>()), for (int 1=0 i < N ++i) assert(v3[i] == vl[i] - v2[i]), } Где определено В реализации HP minus был определен в заголовочном файле <f unction. h>. В соответ- ствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101). Требования к типу • Т является Присваиваемым. • Т является числовым типом. Если х и у — значения типа Т, то выражение х - У определено, а его тип можно преобразовать к Т. Открытые базовые классы binary_fiinction<T, Т Т> Члены minus::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемой Бинарной Функции.) minus::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемой Бинарной Функции.)
15.2. Арифметические операции 373 minus::result_type Тин результата: Т (Описан в Адаптируемой Бинарной Функции.) Т minus::operator()(const Т& х, const Т& у) const Оператор вызова функции. Результат: х - у. (Описан в Адаптируемой Бинарной Функции.) minus::minus() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.2.3. multiplies (умножение) multiplies<T> Класс multiplies<T> является Адаптируемой Бинарной Функцией } Если f'является объектом класса multiplies<T>, ах иу — значения типа Т,то f(x, у) возвращает х * у. Пример Таблица заполняется значениями от 1! до 20!. int main() { const int N = 20, vector<double> V(N), for (int l = 0. i < N, ++i) V[i] ==, i + 1, partial_surn(V begin(), V end(), V begin(), niultiplies<double>()), copy(V begin(), V end(), ostream_iterator<double>(cout, ”\n"))( Где определено В реализации HP multiplies был определен в заголовочном файле <f unction. h>. В со- ответствии со стандартом языка C++ он объявлен в заголовочном файле <functionalx Параметры шаблона Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101) •) ----------- ей В STL .noi функциональный объект назывался nines, однако это имя конфликтовало с функци- Из заголовочного фай ia ^sys/times h> системы UNIX Поэтому в пандарге языка C++ оно было из- е,,С‘но |1а |Пи] tipnes.
374 Глава 15. Классы функциональных объектов Требования к типу • Т является Присваиваемым. • Т является числовым типом. Если х и у — значения типа Т, то выражение х определено, а его тип можно преобразовать к Т. Открытые базовые классы binary_function<T, Т, Т> Члены multiplies::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемой Бинарной Функции.) multiplies::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемой Бинарной Функции.) multiplies::result-type Тип результата: Т (Описан в Адаптируемой Бинарной Функции.) Т multiplies::operator()(const Т& х, const Т& у) const Оператор вызова функции. Результат: х * у. (Описан в Адаптируемой Бинарной Функции.) multiplies::multiplies() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.2.4. divides (деление) divides<T> Класс divides<T> является Адаптируемой Бинарной Функцией. Если f — объект класса divides<T>, а х и у — значения типа Т, то f (х, у) возвращает х / у. Пример Каждый элемент в vector делится на константу. int main() { const int N = 1000, vector<double> v(N), generate(v begin(), v end(), rand), transform^ begin(), v end(), v begin(), bind2nd(divides<double>(), double(RAND_MAX))),
15.2. Арифметические операции 375 for (int 1=0. i < N, ++1) assert(0 <= v[i] && v[i] <= 1.). } Где определено p реализации HP divides был определен в заголовочном файле <f unction. h>. В соот- ветствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101). Требования к типу • Т является Присваиваемым. • Т является числовым типом. Если х и у — значения типа Т, то выражение х / у . определено, а его тип можно преобразовать к Т. Открытые базовые классы binary_function<T, Т, Т> Члены divides::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемой Бинарной Функции.) divides:: second_argument_type Тип второго аргумента: Т (Описан в Адаптируемой Бинарной Функции.) divides::result_type Тип результата: Т (Описан в Адаптируемой Бинарной Функции.) Т divides::operator()(const Т& х, const Т& у) const Оператор вызова функции. Результат: х / у. (Описан в Адаптируемой Бинарной Функции.) divides: :divides() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) ’5.2.5. modulus (получение остатка при делении) rfi°dulus<T> ^асс modulus<T> является Адаптируемой Бинарной Функцией. Если f — объект класса niodulus<T>, а х и у — значения типаТ, то f (х, у) возвращает х % у.
37(5 Глава 15. Классы функциональных объектов Пример Каждый элемент вектора заменяется на последнюю цифру этого элемента. ini main() { const int N = Ю00, vector<int> v(N) generate(v begin(), v end() rand) transform(v begin(), v end() v begin(), bind2nd(modulus<int>(), 10)), for (int i=0. i < N, ++i) assert(0 <= v[i] && v[i] < 10), Где определено В реализации HP modulus был определен в заголовочном файле <f unction h>. В соот- ветствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional> Параметры шаблона Т Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101).' Требования типа • Т является Присваиваемым. • Т является целочисленным типом. Если х и у — значения типа Т, то выражение х % у определено, а его тип можно преобразовать к Т. Открытые базовые классы binary_function<T, Т, Т> Члены modulus::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемой Бинарной Функции.) modulus::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемой Бинарной Функции.) modulus::result_type Тип результата: Т (Описан в Адаптируемой Бинарной Функции.) Т modulus::operator()(const Т& х, const T& у) const Оператор вызова функции. Результат вызова: х % у. (Описан в Адаптируемой Бинарной Функции.)
15.2. Арифметические операции 377 modulus::modulus() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) |5.2.6. negate (изменение знака) negate<T> Класс negate<T> является Адаптируемой Унарной Функцией, то есть функциональным объектом с единственным аргументом. Если f — объект класса negate<T>, ах — значе- ние типа Т, то f (х) возвращает -х. Пример Создается вектор v2, каждый элемент которого — это отрицание (аддитивно обрат- ный) соответствующего элемента в v1. int main() { const int N - 1000. vector<int> v1(N). vector<int> v2(N), generate(vl begin(), vl end(), rand), transform(v1 begin(), v1 end(), v2 begin(), negate<int>()), for (int 1=0, i < N. ++i) } assert(v1[i] + v2[i] == 0), Где определено В реализации HP negate был определен в заголовочном файле <f unction п>. В соот- ветствии со стандартом языка C++ он объявлен в заголовочном файле functional >. Параметры шаблона Т Тип аргументов и тип результата функционального объекта. Является моделью Адаптируемой Унарной Функции (с. 127), Конструируемого по Умолчанию (с. 101). Требования к типу • Т является Присваиваемым. Т является числовым типом. Если х — значение типа Т, то выражение -х опреде- лено, а его тип можно преобразовать к Т. Скрытые базовые классы U|iary_function<T, Т> ^ены negate::argument-type Тип аргумента: Т (Описан в Адаптируемой Унарной Функции.)
378 Глава 15. Классы функциональных объектов negate::result_type Тип результата: Т (Описан в Адаптируемой Унарной Функции.) Т negate::operator()(const Т& х) const Оператор вызова функции. Результат вызова: -х. (Описан в Адаптируемой Унарной Функции.) negate::negate() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.3. Сравнения 15.3.1. equal.to (равно) equal_to<T> Класс equal_to<T> является Адаптируемым Бинарным Предикатом, то есть функцио- нальным объектом, который проверяет истинность некоторого условия. Если f - объект класса equal_to<T>, а х и у — значения типа Т, то f (х, у) возвращает истин- ность условия х == у. Пример Массив реорганизуется таким образом, что все элементы, равные нулю, предшеству- ют всем ненулевым элементам. int main() { const int N = 10, int A[N] = {1. 3, 0, 2. 5. 9, 0. 0. 6, 0}, partition(A, A + N, bind2nd(equal_to<int>(), 0)); copy(A, A + N, ostream_iterator<int>(cout, " ")); cout « endl, } Где определено В реализации HP equal_to был определен в заголовочном файле <f unction h>. В соот- ветствии со стандартом языка C++ он объявлен в заголовочном файле <functional>- Параметры шаблона Т Тип аргументов equal_to. Является моделью Адаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101 )• Требования к типу • Т является моделью =Сравнимого.
15.3. Сравнения 379 0тКрь1ТЬ,е базовые классы binary_function<T, Т, bool> Члены equal_to::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) 6qual_to::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) equal_to::result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool equal_to: :operator()(const Т& х, const Т& у) const Оператор вызова функции. Возвращаемое значение: х == у. (Описан в Бинарном Предикате.) equal_to::equal_to() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.3.2. not_equal_to (не равно) not_equal_to<T> Класс not_equal_to<T> является Адаптируемым Бинарным Предикатом, то есть функ- циональным объектом, проверяющим истинность некоторого условия. Если f — объект класса not_equal_to<T>, а х и у — значения типа Т, то f (х, у) возвращает истинность условия X ' = у. Пример Ищется первый ненулевой элемент в списке. int main() { const int N = 9: int A[N] = {0. 0. 0, 0, 1, 2, 4, 8. 0}; llSt<int> L(A, A + N); list<int> iterator i = find_if(L.begin(), L.endQ. bind2nd(not_equal_to<int>(). 0)). cout « "Elements after initial zeros (if any) ". copy(i, l end(), ostream_iterator<int>(cout. " ”)). cout « endl, fye определено b Q Реализации HP not_equal_to был определен в заголовочном файле <f unction h>. В co- ^Ьетствии co стандартом языка C++ он объявлен в заголовочном файле <f unctional>.
380 Глава 15. Классы функциональных объектов Параметры шаблона Т Тип аргументов not_equal_to. Является моделью Адаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. Требования к типу • Т является моделью ^Сравнимого. Открытые базовые классы binary_funct]on<T. Т, bool> Члены not?_equal_to:: first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) not_equal_to::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) not_equal_to::result-type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool not_equal_to::operator()(const T& x, const T& у) const Оператор вызова функции. Возвращаемое значение: x 1 = у. (Описан в Бинарном Предикате.) not._equal_to:: not_equal_toO Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.3.3. less (меньше) less<T> Класс less<T> является Адаптируемым Бинарным Предикатом, то есть функциональ пым объектом, проверяющим истинность некоторого условия. Если f — объект клас са less<T>, а х и у — значения типа Т, то f (х, у) возвращает истинность условия х > Многие классы и алгоритмы STL, такие как sort, set и map, используют функШ110 сравнения. Обычно по умолчанию используется less. Пример Массив реорганизуется таким образом, чтобы отрицательные элементы предшестВ° вали неотрицательным. int main() { const int N = 10. int A[N] = {1 -3. -7. 2, 5 -9, -2. 1, 6, -8}.
15.3 Сравнения 381 partition^, А + N. bind2nd(less<int>(). 0)), copy(A, A + N, ostream_iterator<int>(cout, ’ ")), cout « end], Где определено p реализации HP less был определен в заголовочном файле <function h>. В соответ- ствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип аргументов less. Является моделью Адаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101). Кроме того, less<T> является моделью Строгого Слабого Упорядочения (с. 132) тогда и только тогда, когда Т является моделью Строго Слабо Сравнимого (с. 104). Требования к типу • Т — модель <Сравнимого. Открытые базовые классы binary_function<T, T bool> Члены less:: first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) less:: second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) less:: result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool less::operator()(const T& х, const Т& у) const Оператор вызова функции. Возвращаемое значение: х < у. (Описан в Бинарном Предикате.) less::iess() Конструктор по умолчанию. \Описан в Конструируемом по Умолчанию. '5-3.4. greater (больше) 9r®ater<T> Кдал цсс g reate г<Т> является Адаптируемым Бинарным Предикатом, то есть функцио- *ьНым объектом, проверяющим истинность некоторого условия. Если f — объект
382 Глава 15. Классы функциональных объектов класса g reate г<Т>, ахи у - значения типа Т, то f (х, у) возвращает истинность yCJ1Q вия х > у. Пример Сортируется vector в порядке убывания, а не в порядке возрастания. int main() { const int N = 10, int A[N] = {1, -3, -7, 2, 5, -9, -2, 1, 6, -8}, vector<int> V(A, A + N), sort(V begin(), V end(), greater<int>()), copy(V begin(), V end(), ostream_iterator<int>(cout, ” ")), cout << endl, } Где определено В реализации HP g reater был определен в заголовочном файле <f unction h>. В соот- ветствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип параметров g reate г. Является моделью Адаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101). Кроме того, g reate г<Т> является моделью Строгого Слабого Упорядочения (с. 132) тогда и только тогда, когда Т является моделью Строго Слабо Сравнимого (с. 101). Требования к типу • Т — модель <Сравнимого. Открытые базовые классы binary_function<T, Т, bool> Члены greater::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) greater::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) greater::result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.)
15.3. Сравнения 383 bool greater::operator!)(const T& x, const T& y) const Оператор вызова функции. Возвращаемое значение: х > у. (Описан в Бинарном Предикате.) greater::greater() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.3.5 . less_equal (меньше или равно) iess_equal<T> Класс less_equal<T> является Адаптируемым Бинарным Предикатом, то есть функци- ональным объектом, проверяющим истинность некоторого условия. Если f — объект класса less_equal<T>, а х и у — значения типа Т, то f(х, у) возвращает истинность условиях <= у. Пример Создается список, состоящий из положительных чисел диапазона. int main() { const int N = 10; int A[N] = {3, -7, 0. 6, 5, -1, -3, 0, 4, -2}, list<int> L. remove_copy_if(A, A+N, back_inserter(L), bind2nd(less_equal<int>(), 0)), cout « "Elements in list’ ", copy(L begin(), L end(), ostream_iterator<int>(cout, ” ")), cout « endl. } Где определено ® реализации HP less_equal был определен в заголовочном файле <function. h>. В со- ответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона Т Тип аргументов less_equal. Является моделью Оптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101). ^^етим, что less_equal<T> не является моделью Строгого Слабого Упорядочения, ^пример, вы не можете использовать less_equal в качестве функции сравнения для г1- Этот объект не обеспечивает даже частичного упорядочения, не говоря уже строгом слабом упорядочении. Для частичного упорядочения f (х, х) должна всег- ^быть false (ложной).
384 Глава 15. Классы функциональных объектов Требования к типу • Т — модель <Сравнимого. Открытые базовые классы binary_function<T, Т, bool> Члены less_equal::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) less_equal::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) less_equal::result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool less_equal::operator()(const T& x, const T& у) const Оператор вызова функции. Возвращаемое значение: х <= у. (Описан в Бинарном Предикате.) less_equal::less_equal() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.3.6 . greater_equal (больше или равно) greater_equal<T> Класс g reate r_equal<T> является Адаптируемым Бинарным Предикатом, то есть функ- циональным объектом, проверяющим истинность некоторого условия. Если f — объект класса greater_equal<T>, а х и у — значения типа Т, то f (х, у) возвращает истинность условия х >= у. Пример Ищется первое неотрицательное число в vector. uit main() const int N = 10 int A[N] = {-4, -3. 0, -6, 5, -1. -3. 0, 4, -2}, vector<int> v(A, A + N), vector <int> iterator i = find_if(v begin(), v end() bind2nd(greater_equal<int>(). 0)), assert(i == v end() || *1 >= 0),
15.4. Логические операции 385 Рде определено ^реализации HP g reate r_equal был определен в заголовочном файле <f unction п>. В со- ответствии со стандартом языка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона Т Тип аргументов g reater_equal. Является моделью ддаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101) Отметим, что g reate r_equal<T> не является моделью Строгого Слабого Упорядочения. Этот объект не обеспечивает даже частичного упорядочения, не говоря уже о строгом слабом упорядочении. Для частичного упорядочения выражение f (х, х) должно всегда быть false. Требования к типу • Т — модель <Сравнимого. Открытые базовые классы binary_function<T, Т, bool> Члены greater_equal::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) greater_equal:: second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) greater_equal::result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool greater_equal::operator()(const T& x, const T& y) const Оператор вызова функции. Возвращаемое значение: х >= у. (Описан в Бинарном Предикате.) greater_equal::greater_equal() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.4. Логические операции Логические функциональные объекты очень похожи на арифметические функцио- Пильные объекты; они выполняют такие операции, как х j | у и х && у. Классы logical_and и logical_or полезны главным образом потому, что, объеди- ненные с функциональным объектом-адаптером binary_compose (с. 425), они выпол- няют логические операции над другими функциональными объектами.
386 Глава 15. Классы функциональных объектов 15.4.1. logical.and (логическое И) logical_and<T> Класс logical_and<T> является Адаптируемым Бинарным Предикатом, то есть фуНк циональным объектом, проверяющим истинность некоторого условия. Если f — объект класса logical_and<T>, а х и у — значения типа Т (при этом Т можно преобразовать к bool), то f (х, у) возвращает истинность того, что х и у оба истинны. Пример Ищется в списке первый элемент, который лежит в диапазоне 0...10. int main() { list<int> L, generate_n(back_inserter(L), 10000, rand), list<int> .iterator i = find_if(L begin(), L.end(), compose2(logical_and<bool>(), bind2nd(greater_equal<int>(), 1), bind2nd(less_equal<int>(), 10))), assert(i == L.end() || (*i >= 1 && *i <= 10)), } Где определено В реализации HP класс logical_and был определен в заголовочном файле <f unction h>. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона Т Тип аргументов logical_and. Является моделью Адаптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101)- Требования к типу • Т можно преобразовать к bool. Открытые базовые классы binary_function<T. Т, bool> Члены logical_and::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) logical_and::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.)
15.4. Логические операции 387 logical_and::result-type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool logical_and::operator()(const Т& х, const Т& у) const Оператор вызова функции. Возвращаемое значение: х && у. (Описан в Бинарном Предикате.) logical_and::logical_and() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.4.2. logical_or (логическое ИЛИ) logical.© г<Т> Класс logical_or<T> является Адаптируемым Бинарным Предикатом, то есть функци- ональным объектом, проверяющим истинность некоторого условия. Если f — объект класса logical_or<T>, а х и у — значения типа Т (при этом Тможно преобразовать к bool), то f(х, у) возвращает истинность того, что х или у истинно. Пример Ищется в строке первый символ “ ” (пробел) или “\п”. int main() { char str[] = "The first line\nThe second line": int len = strlen(str), const char* wptr = find_if(str. str + len, compose2(logical_or<bool>(), bind2nd(equal_to<char>(), '), bind2nd(equal_to<char>(), \n*))), assert(wptr == str + len || *wptr == ’ ’ || *wptr == ’\n’), } Где определено В реализации HP класс logical_or был определен в заголовочном файле <f unction. h>. В соответствии со стандартом языка C++ он объявлен в заголовочном файле functional:». Параметры шаблона Т Тип аргументов logical.or. Является моделью каптируемого Бинарного Предиката (с. 131), Конструируемого по Умолчанию (с. 101). Требования к типу • Т можно преобразовать к bool.
388 Глава 15. Классы функциональных объектов Открытые базовые классы binary_function<T, Т. bool> Члены logical_or::first_argument_type Тип первого аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) logical_or::second_argument_type Тип второго аргумента: Т (Описан в Адаптируемом Бинарном Предикате.) logical_or::result-type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool logical-Or::operator()(const Т& х, const Т& у) const Оператор вызова функции. Возвращаемое значение: х 11 у. (Описан в Бинарном Предикате.) logical_or::logical_or() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.4.3. logical_not (логическое отрицание) logical_not<T> Класс logical_not<T> является Адаптируемым Предикатом, то есть функциональным объектом, который получает единственный аргумент и проверяет истинность неко- торого условия. Если f — объект класса logical_not<T>, ах — значение типа Т (при этом Т можно преобразовать к bool), то f (х) возвращает истинность того, что х ложно Пример Битовый вектор преобразуется в его логическое дополнение. int main() { const int N = 1000 vector<bool> v 1, for (int 1=0, i < N, ++i) v1 push_back(rand() > (RAND_MAX /2)), vector<bool> v2, transform(v1 begin(), v1 end(), back_inserter(v2) logical_not<bool>()) tor (int i-O, i < N, ++i) assert(v1[i] == 1v2[i]),
15.5. Тождественность и проекция 389 Где определено рреализации HP класс logical_not был определен в заголовочном файле <f unction h>. j} соответствии co стандартом языка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона Т Тип аргументов logical_not. Является моделью Адаптируемого Предиката (с. 131), Конструируемого по Умолчанию (с. 101). Требования к типу • Т можно преобразовать к bool. Открытые базовые классы unary_function<T, bool> Члены ' logical_not::argument_type Тип аргумента: Т (Описан в Адаптируемом Предикате.) logical_not::result_type Тип результата: bool (Описан в Адаптируемом Предикате.) bool logical_not::operator()(const T& x) const Оператор вызова функции. Возвращаемое значение: 1 х. (Описан в Бинарном Предикате.) logical_not::logical_not() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.5. Тождественность и проекция Все функциональные объекты этого раздела в некотором смысле таковы, что возвра- |Чают свои аргументы без изменения. Основной тождественный функциональный °бъект — это identity, а остальные являются его обобщениями. Стандарт языка C++ не содержит операций тождественности и проекции, но они оычно доступны в виде дополнительных возможностей. 15.5.1. identity (тождественность) i<Jentity<T> ^асс identity — это Унарная Функция, которая является функцией тождественно- СтИ. Он получает единственный аргумент х и возвращает его без изменения.
390 Глава 15. Классы функциональных объектов Пример int main() { int х = 137, identity<int> id, assert(x == id(x)), Где определено В оригинальной версии HP STL identity был определен в заголовочном файле <projectn. h>, а в SGI STL он определен в заголовочном файле <functional>. В стан- дарт языка C++ он не входит. Параметры шаблона Т Тип аргумента функционального объекта и тип возвращае- мого значения. Является моделью Адаптируемой Унарной Функции (с. 127), Конструируемого по Умолчанию (с. 101). Открытые базовые классы unary_function<T, Т> Члены identity::argument_type Тип аргумента: Т (Описан в Адаптируемой Унарной Функции.) identity:: result_type Тип результата: Т (Описан в Адаптируемой Унарной Функции.) const Т& identity::operator()(const Т& х) const Оператор вызова функции. Возвращаемое значение: х. (Описан в Адаптируемой Унарной Функции.) identity::identity() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.5.2. projectlst project1st<Arg1, Arg2> Класс projectlst — это функциональный объект, который получает два аргумент и возвращает первый, при этом второй аргумент игнорируется. Он по существу яв лЯ ется обобщением identity для случая Бинарной Функции. f Весьма важно, что тины параметра и возвращаемого значения одинаковы. Обобщение на случай Р33^1^ ющихся типов не будет работать, потому что identity возвращает const-ссылку на свой аргумент, а не ко1 .. а гопо аргумента Если бы в identity преобразовывался тип, то возвращаемое значение было бы висящей ссЫ-’
15.5. Тождественность и проекция 391 Пример int main() { vector<int> v1(10, 137), vector<char*> v2(10. (char*) 0); vector<int> result(10), transform(v1 begin(), vl end(), v2 begin(), result begin(), project1st<int. char*>()), assert(equal(v1 begin(), v1.end(), result begin())), I [де определено Класс project 1 st отсутствует в оригинальной версии HP и не входит в стандарт язы- ка C++- В реализации SGI он определен в заголовочном файле <functional>. Параметры шаблона Arg 1 Тип первого аргумента р го j ect 1 st и тип его результата. Агд2 Тип второго аргумента project 1st. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101). Требования к типу • Argl — модель Присваиваемого. Открытые базовые классы binary_function<Arg1. Arg2, Arg1> Члены projectlst::first_argument_type Тип первого аргумента: A rg 1 (Описан в Адаптируемой Бинарной Функции.) projectlst::second_argument_type Тип второго аргумента: Arg2 (Описан в Адаптируемой Бинарной Функции.) projectlst:: resiilt_type Тип результата: A rg 1 (Описан в Адаптируемой Бинарной Функции.) Argl projectlst::operator()(const Arg1& x, const Arg2& y) const Оператор вызова функции. Возвращаемое значение: х. (Описан в Адаптируемой Бинарной Функции.) Projectlst::project1st() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.)
392 Глава 15. Классы функциональных объектов 15.5.3. project2nd project2nd<Arg1, Arg2> Класс project 2nd — это функциональный объект, который получает два аргуМрп и возвращает второй; первый аргумент игнорируется. Он по существу является обобщением identity для случая Бинарной Функции. Пример int main() { veclor<char*> v1(10, (char*) 0); vector<int> v2(10, 137), vector<int> result(10), transform(v1 begin(), v1 end(), v2 begin(), result.begin(), project2nd<char*. int>()), assert(equal(v2 begin(), v2 end(), result begin())), Где определено Класс p roj ect2nd отсутствует в оригинальной версии HP и не входит в стандарт язы- ка C++. В реализации SGI он объявлен в заголовочном файле <f unctional>. Параметры шаблона A rg 1 Тип первого аргумента project 2nd. Arg2 Тип второго аргумента project 2nd и тип его результата. Является моделью Адаптируемой Бинарной Функции (с. 128), Конструируемого по Умолчанию (с. 101). Требования к типу • A rg 2 — модель Присваиваемого. Открытые базовые классы Dinary_function<Arg1. Arg2. Arg2> Члены project2nd::first_argument_type Тип первого аргумента: A rg 1 (Описан в Адаптируемой Бинарной Функции.) project2nd::second_argument_type Тип второго аргумента: Arg2 (Описан в Адаптируемой Бинарной Функции.) project2nd::result_type Тип результата: А гg 2 (Описан в Адаптируемой Бинарной Функции.)
15.5. Тождественность и проекция 393 Arg2 project2nd::operator()(const Агд1& х, const Arg2& у) const Оператор вызова функции. Возвращаемое значение: у. (Описан в Адаптируемой Бинарной Функции.) project2nd::project2nd() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.5.4. selectlst select 1 st < Pai г> Класс select 1 st — это функциональный объект, который получает единственный ар- !умент, пару pai г (или по крайней мере класс с интерфейсом, как у pai г) и возвраща- ет первый элемент этой пары. Пример Элементами тар являются пары, поэтому можно использовать selectlst для извлече- ния и печати ключей всех элементов в тар. int main() { mapcint, double> М; М[1] =03, М[47] =08; М[33] =01, transform(M.begin(). М end(), ostream_iterator<int>(cout, ” ”), select1st<map<int, double> value_type>()), cout << endl, // Результат 1 33 47 } Где определено В оригинальной версии HP STL selectlst был определен в заголовочном файле «Projectn. h>, а в SGI STL - в заголовочном файле <functional>. В стандарт языка C++ он не входит. Параметры шаблона Ра* г Тип аргумента функционального объекта. Является моделью Адаптируемой Унарной Функции (с. 127), Конструируемого по Умолчанию (с. 101). требования к типу Pai г имеет открытое поле Pai г •: f i rst типа Pai г. • f i rst_type. ^Крытые базовые классы unary_function<Pair, typename Pair first_type>
394 Глава 15. Классы функциональных объектов Члены selectlst::argument-type Тип аргумента: Pai г (Описан в Адаптируемой Унарной Функции.) selectlst::result-type Тип результата: typename Pai г: • f i rst.type (Описан в Адаптируемой Унарной Функции.) const typename Pair::first-type& selectlst::operator()(const Pair& p) const Оператор вызова функции. Возвращаемое значение: р f i rst. (Описан в Адаптируемой Унарной Функции.) selectlst::select1st() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.5.5. select2nd select2nd<Pair> Класс select 2nd — это функциональный объект, который получает единственный ар- гумент pai г (или по крайней мере класс с интерфейсом, как у pai г) и возвращает вто- рой элемент этой пары. Пример Элементами тар являются пары, поэтому можно использовать select2nd для извлече- ния и печати всех значений в тар. int main() { mapdnt, double> M. M[1] =03, М[47] =08. М[33] =01, transform^ begin(),‘ М end(), ostream_iterator<double>(cout. ’’ ”), select2nd<map<int. double> value_type>()), cout << endl, // Результат 030108 } Где определено В оригинальной версии HP STL select2nd был определен в заголовочном файл6 <projectn. h>, а в SGI STL — в заголовочном файле <functional>. В стандарт языка C++ он не входит. Параметры шаблона Pai г Тип аргумента функционального объекта.
15.6. Специализированные функциональные объекты 395 0Вдяется моделью каптируемой Унарной Функции (с. 127), Конструируемого по Умолчанию (с. 101). Требования к типу • Pai г имеет открытое поле Pair:: second типа Pair:: second_type. Открытые базовые классы unary_function<Pair, typename Pair second_type> Члены select2nd:: argument-type Тип аргумента: Pai г (Описан в Адаптируемой Унарной Функции.) select2nd::result-type Тип результата: typename Pair: :second_type (Описан в Адаптируемой Унарной Функции.) const typename Pair::second-type& select2nd::operator()(const Pair& p) const Оператор вызова функции. Возвращаемое значение: р. second. (Описан в Адаптируемой Унарной Функции.) select2nd::select2nd() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) 15.6. Специализированные функциональные объекты 15.6.1. hash hash<T> Класс hash<T> — это Функция Хеширования. Он используется в качестве хеш-функции по Умолчанию всеми Хешированными Ассоциативными Контейнерами, реализованными в STL. Шаблон hash<T> определен только для параметров шаблона типа char*, const char*, string и встроенных целочисленных типов.е) Если вам нужна Функция Хеширования с Другим типом аргумента, то вы должны либо обеспечить свою собственную специ: ализацию шаблона, либо написать новый класс Функции Хеширования. Пример int main() { hash<const char*> H, cout « "too -> " << H(”foo") << endl, ,cout « "bar -> " « H("bar") << endl, ------------- & Фактически стандартный шаблон hash<T> — это пустой класс. Метод орегагог() определен только Различных специализациях.
396 Глава 15. Классы функциональных объектов Где определено Класс hash не входил в оригинальную версию ИР и не входит в стандарт языка В реализации SGI он объявлен в заголовочных файлах <hash_set> и <hash_map>. Параметры шаблона Т Тип аргумента, то есть тип значения, которое будет хецць ровано. Является моделью Функции Хеширования (с. 135). Требования к типу Т должен быть типом, для которого определена специализация хеша. В STL опреде- лены следующие специализации: • char* • const char* • string • char • signed char • unsigned char • short • unsigned snort • int • unsigned int • long • unsigned long Открытые базовые классы Отсутствуют. Члены size_t hash::operator()(const T& x) const Возвращает хеш-значение x. (Описан в Функции Хеширования.) 15.6.2. subtractive.rng subtractive_rng Класс subtractive_rng — это Генератор Случайных Чисел, который использует субтрак- тивный метод генерации псевдослучайных чисел.Это Унарная Функция. Она nonv- чает единственный аргумент N типа unsigned int и возвращает unsigned int, который меньше, чем N. Последовательные обращения к одному и тому же объекту subt ractive_rng создают псевдослучайную последовательность. } См у Кнута |Knu98a| раздел 3 6, где приведена реализация субтрактивного метода на Фор|ра11< н раздел 3 2 2. где анализирус1ся .пог класс алгоритмов
15.6. Специализированные функциональные объекты 397 Отметим, что последовательность, произведенная классом subtractive_rng, пол- ностью детерминирована и что последовательности, созданные двумя различными объектами subtractive_rng, независимы друг от друга. То есть если R1 — это sUt)tractive_rng, то значение, возвращаемое при вызове R1, зависит только от началь- ного значения R1 и от количества предыдущих вызовов R1. Обращения к другим объек- там subt ractive.rng не важны. В терминах реализации это вызвано отсутствием ста- тических полей в классе subt ractive_rng. Пример int main() { subtractive_rng R, for (int 1=0, i < 20, ++i) cout « R(5) « cout « endl. // Результат 32324311220344442100 } Где определено Класс subtractive_rng не входит в стандарт языка C++. В SGI STLoh определен в за- головочном файле <f unctional>. Параметры шаблона Отсутствуют. Является моделью Генератора Случайных Чисел (с. 134), Адаптируемой Унарной Функции (с. 127). Открытые базовые классы « unary_function<unsigned int, unsigned int> Члены Некоторые члены subt ractive_rng не определены в требованиях к Генератору Случай- ных Чисел или Адаптируемой Унарной Функции, а специфичны для subt ractive_rng; они помечены символом ♦. subtractive_rng:: argument-type Тип аргумента: unsigned int (Описан в Адаптируемой Унарной Функции.) subtractive_rng::result-type Тип результата: unsigned int (Описан в Адаптируемой Унарной Функции.) ♦ subtractive.rng::subtractive_rng(unsigned int seed) Конструктор. Создает subt ractive_rng, инициализируя его внутреннее состоя- ние значением seed.
398 Глава 15. Классы функциональных объектов ♦ subtractive_rng::subtractive_rng() Конструктор по умолчанию. Создает subt ractive_rng, инициализируя его внуь реннее состояние некоторым значением по умолчанию. unsigned int subtractive.rng::operator()(unsigned int n) Вызов функции. Возвращает псевдослучайное число из диапазона [О, N). ♦ void subtractive_гng::initialize(unsigned int seed) Инициализация заново генератора случайных чисел таким образом, что он имеет то же внутреннее состояние, как если бы он только что был создан при использо- вании значения seed. 15.7. Адаптеры функций-членов Адаптеры функций-членов — это семейство небольших классов, которые позволяют вызывать функции-члены как функциональные объекты. Каждый из них принимает аргумент типа X* или Х& и вызывает одну из функций-членов X с помощью этого аргу- мента. Если метод виртуальный, то будет вызвана полиморфная функция. Адаптеры методов, таким образом, связывают объектно-ориентированное программирование и обобщенное программирование. Количество адаптеров функций-членов может казаться устрашающим, но они об- разуют простую структуру с систематически организованными именами. Существу- ет 2' = 8 адаптеров как результат трех разных разграничений: 1. Принимает адаптер аргумент типа X* или типа Х&? В последнем случае к имени адаптера присоединяется суффикс “_ref”. 2. Инкапсулирует адаптер функцию-член без аргументов или же функцию-член с одним аргументом? Во втором случае к имени адаптера присоединяется суф- фикс “1”. 3. Инкапсулирует адаптер неконстантную функцию-член или константную? (Син- таксис объявления типов языка C++ не позволяет обрабатывать оба варианта в од- ном классе.) В последнем случае к имени адаптера присоединяется префикс const,. При нормальном использовании сложность неощутима. Обычный способ конструи- рования адаптера функционального объекта состоит в использовании вспомогатель- ной функции, а не в непосредственном вызове конструктора. Поскольку вспомога- тельная функция перегружена, то нужно запомнить только два имени: mein_tun и mem_fun_ref. 15.7.1. mem_fun_t mem_fun_t<R, Х> Класс mem_f un_t — адаптер функции-члена. Если X представляет собой некоторый класс с функцией-членом R X.:f() (то есть функцией-членом без аргументов, возвращают611 значение типа RШ)), то mem_f un_t<R, Х> — это адаптер функционального объекта, кото- рый позволяет вызвать f () как обычную функцию, а не функцию-член. > Тип R может быть void.
15.7. Адаптеры функций-членов 399 Конструктор получает указатель на одну из функций-членов X. Кроме того, как Л у всех функциональных объектов, у mem_fun_t есть operator^), который позволяет зазывать его, пользуясь обычным синтаксисом вызова функции. В таком случае operator () в mem_f un_t принимает аргумент типа X*. Если F представляет собой mem_fun_t, сконструированный для вызова функции- цдена X:: f, и если х — указательтипаХ*, то выражение F(х) эквивалентно выражению (). Разница между ними синтаксическая: F обеспечивает интерфейс Адаптируемой унарной Функции. Как и в случае многих других адаптеров, непосредственное использование конст- руктора mem_f un_t неудобно. Вместо него лучше использовать вспомогательную функ- цию mem_fun. Пример struct В { virtual void print() = О, Struct D1 public В { void print() { cout « "I’m a D1” « endl: } }; Struct D2 public В { void print() { cout « "I’m a D2" « endl, } }; int main() { vector<B*> V; V .push_back(new D1); V .push_back(new D2); V .push_back(new D2); V .push_back(new DI), for_each(V begin(), V end(), mem_fun(&B..print)); } Где определено Класс mem_fun_t не входил в реализацию HP. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает mem_f un_t. Является моделью Адаптируемой Унарной Функции (с. 127).
400 Глава 15. Классы функциональных объектов Требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна не-const функция-член аргументов, возвращающая значение типа R. Открытые базовые классы unary_function<X*, R> Члены Некоторые члены mem_f un_t не определены в требованиях к Адаптируемой Унарной Функции, а специфичны для mem_fun_t; они помечены символом ♦. mem_fun_t::argument-type Тип аргумента: X* (Описан в Адаптируемой Унарной Функции.) mem_fun_t::result-type Тип результата: R (Описан в Адаптируемой Унарной Функции.) R mem-fun_t::operator(X* х) const Оператор вызова функции. Вызывает x->f (), где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Унарной Функции.) ♦ explicit mem_fun-t: :mefn_fun_t(R (X::*f)()) Конструктор. Создает mem_f un_t для вызова функции-члена f. ♦ template <class R, class X> mem_fun_t<R, X> mem_fun(R (X::*f)()) Вспомогательная функция для создания mem_fun_t. Если f имеет тип R (X *), то mem_fun(f) идентичен mem_fun_t<R, Х>(f). 15.7.2. mem_fun_ref_t mem-fun_ref_t<R, Х> Класс mem_fun_ref_t — адаптер функций-членов. Если X представляет собой некото- рый класс с функцией-членом R X: f() (то есть функцией-членом без аргументов, возвращающей значение типа R*}), то mem_fun_ref_t<R, Х> — это адаптер функции" нального объекта, который позволяет вызвать f () как обычную функцию, а не функ- цию-член. Конструктор получает указатель на одну из функций-членов X. Кроме того, как и у всех функциональных объектов, у mem_fun_ref_t есть operator(), который вызы вается с помощью обычного синтаксиса вызова функции. В этом случае operator > принимает тип Х&. } Тин R можс! быть void
15.7. Адаптеры функций-членов 401 Если F — это meni_fun_ref_t, сконструированный для вызова функции-члена X f, 0ссли х — объект типа X, то выражение F(x) эквивалентно выражению х f (). Разни- ца между ними синтаксическая: F обеспечивает интерфейс Адаптируемой Унарной функции. Непосредственное применение конструктора mem_fun_ref_t, как и многих других адаптеров, неудобно. Вместо него лучше использовать вспомогательную функцию твгп_ f и п _r е f • Пример struct В { virtual void print() = 0. }• struct D1 public В { void pnnt() { cout « "I’m a D1” « endl, } }: struct D2 public В { void pnnt() { cout << "I’m a D2" « endl, } }. int main() { vector<D1> V, V push_back(D1()). V push_back(D1()), for_each(V begin(), V end(), mem_fun_ref(&B print)), } Где определено Класс mem_fun_ref_t не входил в реализацию HP. В соответствии со стандартом язы- ка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает mem_f un_ref_t. Является моделью каптируемой Унарной Функции (с. 127). Требования к типу • В является Присваиваемым или void. • X — это класс, имеющий по крайней мере одну не-const функцию-член без аргу- ментов, возвращающую значение типа R.
402 Глава 15. Классы функциональных объектов Открытые базовые классы unary_function<X, R> Члены Некоторые члены mem_f un_ref _t не определены в требованиях к Адаптируемой Унар_ ной Функции, а специфичны для mem_fun_ref_t, они помечены символом ♦. mem_fun_ref_t::argument-type Тип аргумента: X (Описан в Адаптируемой Унарной Функции.) mem_fun_ref_t:: result_type Тип результата: R (Описан в Адаптируемой Унарной Функции.) R mem_fun_ref_t::operateг(Х& х) const Оператор вызова функции. Вызывает х. f (), где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Унарной Функции.) ♦ explicit mem_fun_ref_t::mem_fun_ref_t(R (X::*f)O) Конструктор. Конструирует mem_fun_ref_t для вызова функции-члена f. ♦ template <class R, class X> mem_fun_ref_t<R, X> mem_fun_ref(R (X::*f)()) Вспомогательная функция для создания mem_fun_ref_t. Если f имеет тип R (X- *), то mem_fun_ref(f) идентичен mem_fun_ref_t<R, X>(f). 15.7.3. mem_fun1_t mem_fun1_t<R, X, A> Класс mem_fun1_t — адаптер функций-членов. Если X представляет собой некоторый класс с функцией-членом R X:: f (А) (то есть функцией-членом с единственным аргу- ментом типа А, возвращающей значение типа R ), то mem_f un1_t<R, X, А> — это адап- тер функционального объекта, который позволяет вызвать f как обычную функцию, а не функцию-член. Конструктор получает указатель на одну из функций-членов X. Кроме того, как и у всех функциональных объектов, у mem_fun1_t есть operator(), вызываемый с по- мощью обычного синтаксиса вызова функции. В таком случае operator() в mem_f un1_t принимает два аргумента. Первый — типа X*, а второй — типа А. Если F — это mem_fun1_t, сконструированный для вызова функции-члена X. . f, и ес- ли х — указатель типа X*, а а — значение типа А, то выражение F(x, а) эквивалентно выражению x->f (а). Разница между ними синтаксическая: F обеспечивает интерфейс Адаптируемой Бинарной Функции. Непосредственное применение конструктора mem_fun1_t, как и многих других адап- теров, неудобно. Вместо него лучше использовать вспомогательную функцию mem_f ип. * Тип R может быть void.
15.7. Адаптеры функций-членов 403 Пример struct Operation { virtual double eval(double) = 0; struct Square public Operation { double eval(double x) { return x * x; } }; struct Negate public Operation { double eval(double x) { return -x, } int main() { vector<Operation*> operations, vector<double> operands; operations.push_back(new Square), operations.push_back(new Square), operations.push_back(new Negate); operations push_back(new Negate), operations push_back(new Square), operands push_back(1), operands.push_oack(2); operands.push_oack(3); operands push_back(4); operands push_back(5), transform(operations begin(), operations end(), operands begin(), ostream_iterator<double>(cout, ”\n"), mem_fun(&Operation: eval)); } Где определено Класс mem_fun1_t не входил в реализацию HP. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает mem_fun1_t. А Тип аргумента функции-члена. Является моделью Адаптируемой Бинарной Функции (с. 128).
404 Глава 15 Классы функциональных объектов Требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна не-const функция-член с едИьь ственным аргументом типа А, возвращающая значение типа R. • А является Присваиваемым. Открытые базовые классы binary_funciion<X*. A. R> Члены [ 1екоторые члены mem_f uni t не определены в требованиях к Адаптируемой Бинарной функции, а специфичны для mem_f un1_t; они помечены символом ♦ . mem_fun1_t::first_argument_type Тип первого аргумента: X* (Описан в Адаптируемой Бинарной Функции.) mem_fun1_t::second_argument_type Тип второго аргумента: А (Описан в Адаптируемой Бинарной Функции.) mem_fun1_t::result-type Тип результата: R (Описан в Адаптируемой Бинарной Функции.) R mem_fun1_t::operator(X* х, A a) const Оператор вызова функции. Вызывает x->f (а), где f — это функция-член, кото- рая была передана конструктору. (Описан в Адаптируемой Бинарной Функции.) ♦ explicit mem_fun1_t::mem_fun1_t(R (X::*f)(A)) Конструктор. Создает mem_f un1_t для вызова функции-члена f. ♦ template <class R, class X, class A> mem_fun1_t<R, X, A> mem_fun(R (X::*f)(A)) Вспомогательная функция для создания mem_fun1_t. Если f имеет тип R (X 0(A), Tomem_fun(f) идентичен mem_fun1_t<R, X, A>(f). I5.7.4. mem_fun1_ref_t mem-fun1_ref_t<R, X, A> Класс mem f unl ref _t — адаптер функций-членов. Если X представляет собой некото- рый класс с функцией-членом R X f (А) (то есть функцией-членом с единственным аргументом типа А, возвращающей значение типа RФ)). то mem_f un1_ ref_t<R, X, А> это адаптер функционального объекта, который позволяет вызвать f как обычную функцию, а не функцию-член. 1 Тин R может бы и, void
15.7. Адаптеры функций-членов 405 Конструктор получает указатель на одну из функций-членов X. Кроме того, как у всех функциональных объектов, у mem_f un1_ref_t есть operator^), вызываемый спомощью обычного синтаксиса вызова функции. В этом случае operator^) в mem_f un1_ ref_t принимает два аргумента. Первый — типа Х&, а второй — типа А. Если F — это mem_fun1_ref_t, сконструированный для вызова функции-члена X f, несли х является объектом типах, аа — значение типа А, то выражение F(x. а) эквива- лентно выражению х f (а). Разница между ними синтаксическая: F обеспечивает ин- терфейс Адаптируемой Бинарной Функции. Непосредственное применение конструктора mem_f un1_ref_t, как и многих других адаптеров, неудобно. Вместо него лучше использовать вспомогательную функцию (neffuf un_ref. Пример Для данного вектора векторов извлекается по одному элементу из каждого вектора, ini main() { int А1[5] = {1, 2. 3, 4 5}, int А2[5] = {1. 1. 2, 3. 5}. mt А3[5] - {1. 4, 1, 5. 9}. vector<vector<int> > V, V push_back(vector<int>(A1, А1 + 5)), V.push_back(vector<int>(A2. А2 + 5)), V push_back(vector<int>(A3 АЗ + 5)), int indices[3] = {0 2. 4}, int& (vector<lnt>. *extract)(vector<int> size_type), extract - &vector<int> operator[], transform^ begin(), V end(), indices, ostream_iterator<int>(cout, ” '), mem_fun_ref(extract)), cout « endl, // Результат 1 2 9 } Где определено Класс mem_f un1_ref _t не входил в реализацию HP. В соответствии со стандартом язы- ка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона R Тип, возвращаемый функцией-членом. % Класс, функцию-член которого вызывает mem_f un1_ref_t. А Тип аргумента функции-члена. Является моделью Адаптируемой Бинарной Функции (с. 128).
406 Глава 15. Классы функциональных объектов Требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна не-const функция-член, по- лучающая единственный аргумент типа А и возвращающая значение типа R. • А является Присваиваемым. Открытые базовые классы binary_function<X, A, R> Члены Некоторые члены mem_fun1_ref_t не определены в требованиях к Адаптируемой Би- нарной Функции, а специфичны для mem_fun1_ref_t; они помечены символом ♦. mem_f un1_ref_t::first_argument_type Тип первого аргумента: X (Описан в Адаптируемой Бинарной Функции.) mem_fun1_ref_t::second_argument_type Тип второго аргумента: А (Описан в Адаптируемой Бинарной Функции.) mem_fun1_ref_t::result_type Тип результата: R (Описан в Адаптируемой Бинарной Функции.) R mem_fun1_ref_t:.operator(Х& х, A a) const Оператор вызова функции. Вызывает х. f (а), где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Бинарной Функции.) ♦ explicit mem_fun1_ref_t::mem_fun1_ref_t(R (X::*f)(A)) Конструктор. Создает mem_fun1_ref_t для вызова функции-члена f. ♦ template <class R, class X, class A> mem_fun1_ref_t<R, X, A> mem_fun_ref(R (X::*f)(A)) Вспомогательная функция для создания mem_f un1_ref_t. Если f — это значение типаН (X.. *)(А),то mem_fun_ref(f) идентичен mem_fun1_ref_t<R, X, A>(f). 15.7.5. const_mem_fun_t const_mem_fun_t<R, X> Класс const_mem_f un_t — адаптер функций-членов. Если X представляет собой некО' торый класс с функцией-членом R X: : f () const (то есть const-функцией-членом без аргументов, возвращающей значение типа R), то const_mem_f un_t<R, Х> — это адап- тер функционального объекта, который позволяет вызвать f () как обычную фунК' цию, а не функцию-член. J Тип R может бы п> void.
15.7. Адаптеры функций-членов 407 Конструктор получает указатель на одну из функций-членов X. Кроме того, как Л у всех функциональных объектов, у const_mem_f un_t есть operator^), вызываемый спомощыо обычного синтаксиса вызова функции. В таком случае operator() в const_mem_f un_t принимает аргумент типа const X*. Если F — это const_mem_f un_t, который был сконструирован для вызова функции- цлена X: . f, и если х — указатель типа const X*, то выражение F( х) эквивалентно выра- жению x->f (). Разница между ними синтаксическая: F обеспечивает интерфейс Адап- тируемой Унарной Функции. Непосредственное использование конструктора const_mem_fun_t, как и многих дру- гих адаптеров, неудобно. Вместо него лучше использовать вспомогательную функ- цию mem_fun. Пример Для данного вектора векторов печатается размер каждого элемента. int main() vector<vector<int>*> v; v.push_back(new vector<int>(5)); v.push_back(new vector<int>(3)); v.push_back(new vector<int>(4)); transform(v begin(), v end(), ostream_iterator<int>(cout, ” "), mem_fun(&vector<int>: size)); cout « endl; // Результат 534 } Где определено Класс const_mem_f un_t не входил в реализацию HP. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает const_mem_f un_t. Является моделью Адаптируемой Унарной Функции (с. 127). требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна const-функция-член без аргументов, возвращающая значение типа R. Открытые базовые классы unary_function<const X*, R>
408 Глава 15. Классы функциональных объектов Члены 11екоюрыс члены const_mem_fun_t не определены в требованиях к Адаптируемой Унар. ной Функции, а специфичны для const_mem_fun_t; они помечены символом ♦. const_mem_fun_t::argument-type Тип аргумента: X* (Описан в Адаптируемой Унарной Функции.) const_mem_fun_t::result-type Тип результата: R (Описан в Адаптируемой Унарной Функции.) R const_mem_fun_t::operator(const X* х) const Оператор вызова функции. Вызывает x->f (), где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Унарной Функции.) ♦ explicit const_mem-fun_t::const_mem-fun_t(R (X::*f)() const) Конструктор. Создает const_mem_f un_t, который вызывает член-функцию f. ♦ template <class R, class X> const_mem_fun_t<Rl X> mem_fun(R (X::*f)() const) Вспомогательная функция для создания const_mem_f un_t. Если f — это значение типа R (X--*) const,то mem_fun(f) идентичен const_mem_fun_t<R, X>(f). 15.7.6. const_mem_fun_ref_t const_mem_fun-ref-t<Rl X> Класс const_mem_fun_ref_t — адаптер функций-членов. Если X представляет собой класс, у которого есть функция-член R X •: f () const (то есть const-функция-член без аргументов, возвращающая значение типа R’}), то объект типа const jnem_fun_ref_t<R, Х> есть адаптер функционального объекта, позволяющий вызвать f () как обычную функ- цию, а не функцию-член. Конструктор const_mem_fun_ref_t принимает указатель на одну из функций-чле- нов X. Кроме того, как и у всех функциональных объектов, у адаптера const_mem_fun_ ref _t есть operatoг(), вызываемый с помощью обычного синтаксиса вызова функции. В та- ком случае operator() в const_mem_f un_ref_t принимает аргумент типа const Х&. Если F — const_mem_fun_ref_t, сконструированный для вызова функции-члена X t и если х — объект типа X, то выражение F(x) эквивалентно выражению х f (). Разница между ними синтаксическая: F обеспечивает интерфейс Адаптируемой Унарной Функции. Непосредственное применение конструктора const_mem_fun_ref_t, как и многих других адаптеров функциональных объектов, неудобно. Вместо него лучше исполь- зовать вспомогательную функцию mem f un_ref. Пример int rnain() { vector<vector<int> > v } Гии R м(»жс1 бы i ь void
15.7. Адаптеры функций-членов 409 v push_back(vector<int>(2)) v push_back(vector<int>(7)), v push_back(vector<int>(3)). transform(v begin(), v end(). ostream_iterator<int>(cout, ” ”), mem_fun_ref(&vector<int> size)), cout << endl, // Результат 273 } Где определено Класс const_mem_fun_ref_t не входил в реализацию HP. В соответствии со стандар- том языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает const jnem fun ref t Является моделью Адаптируемой Унарной Функции (с. 127). Требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна const-функция-член без аргументов, возвращающая значение типа R. Открытые базовые классы unary_function<X, R> Члены Некоторые члены const_mem_f un_ref _t не определены для Адаптируемой Унарной Фун- кции, а специфичны для const_mem_fun_ref_t; они помечены символом ♦. const_mem_fun_ref_t::argument-type Тип аргумента: X (Описан в Адаптируемой Унарной Функции.) const-meni-fun-ref-t:: result-type Тип результата: R (Описан в Адаптируемой Унарной Функции.) R const_mem_fun_ref_t::operator(const Х& х) const Оператор вызова функции. Вызывает х f (), где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Унарной Функции.) ♦explicit const_mem_fun_ref_t::const_mem-fun_ref_t(R (X::*f)() const) Конструктор. Создает const_mem_f un_ref_t для вызова функции-члена f.
41 о Глава 15. Классы функциональных объектов ♦ template <class R, class X> const_mem_fun_ref_t<R, X> mem_fun_ref(R (X::*f)() const) Вспомогательная функция для создания const jnem_fun_ref _t. Если f имеет ти R (X.:*) const,то mem_fun_ref(f) идентичен const_mem_fun_ref_t<R, X>(f). 1 15.7.7. const_mem_fun1_t const_mem_fiin1_t<R, X, A> Класс const_mem_ f un1_t — адаптер функций-членов. Если X представляет собой неко- торый класс с функцией-членом R X:: f(A) const (то есть const-функцией-членом принимающей единственный аргумент и возвращающей значение типа То const_mem_fun1_t<R, X, А> — адаптер функционального объекта, который позволяет вызвать f как обычную функцию, а не функцию-член. Конструктор принимает указатель на одну из функций-членов X. Кроме того, как и у всех функциональных объектов, у адаптера const_mem_f un1_t есть operator(), вы- зываемый с помощью обычного синтаксиса вызова функции. В таком случае operate г () в const_mem_fun1_t принимает два аргумента. Первый — типа const X*, а второй - типа А. Если F — это const_mem_fun1_t, созданный для вызова функции-члена X. • f, и если х — указатель типа const X*, а а — значение типа А, то выражение F(x, а) эквивалентно выражению x->f (а). Разница между ними синтаксическая: F обеспечивает интерфейс Адаптируемой Бинарной Функции. Непосредственное применение конструктора const_mem_fiin1_t, как и многих дру- гих адаптеров функциональных объектов, неудобно. Вместо него лучше использо- вать вспомогательную функцию mem_f ип. Пример struct В { virtual int f(int х) const = 0; struct D ' public В { int val, D(int x) val(x) {} int f(int x) const { return val + x, } int main() { vector<B*> v. v push_back(new D(3)), v pusti_back(new D(4)), v push_back(new D(5)), mt A[3] - {7, 8. 9}, transform(v begin(), v end(), A, ostream_iterator<int>(cout, " ”), mem_fun(&B f))« cout « endl, } Тип R может бы гь void.
15,7. Адаптеры функций-членов 411 // Результат 10 12 14 } Где определено £дасс const_mem_fun1_t не входил в реализацию HP. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает const jnem_fun1_t. А Тип аргумента функции-члена. Является моделью Адаптируемой Бинарной Функции (с. 128). Требования к типу • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна const-функция-член, прини- мающая единственный аргумент типа А и возвращающая значение типа R. • А является Присваиваемым. Открытые базовые классы binary_function<const X*. A, R> Члены Некоторые члены const_mem_fun1_t не определены в требованиях к Адаптируемой Бинарной Функции, а специфичны для const_mem_f un1_t; они помечены символом ♦. const_mem_fun1_t::first_argument_type Тип первого аргумента: const X* (Описан в Адаптируемой Бинарной Функции.) const_mem_fun1_t::second_argument_type Тип второго аргумента: А (Описан в Адаптируемой Бинарной Функции.) const_mem_fun1_t::result-type Тип результата: R (Описан в Адаптируемой Бинарной Функции.) R const_mem_fun1_t::operator(const X* х, A a) const Оператор вызова функции. Вызывает x->f (а), где f — это функция-член, кото- рая была передана конструктору. (Описан в Адаптируемой Бинарной Функции.) ♦ explicit const_mem_fun1_t::const_mem_fun1_t(R (X::*f)(A) const) Конструктор. Создает const_mem_fun1_t для вызова функции-члена f.
412 Глава 15. Классы функциональных объектов ♦ template <class R, class X, class A> const_mem_fun1_t<R, X, A> mem_fun(R (X::*f)(A) const) Вспомогательная функция для создания const_mem_funl_t. Если f имеет тцп R (X *)(А) const, то mem_fun(f) идентичен const_mem_fun1_t<R, X, A>(f). 15.7.8. const_mem_fun1_ref_t const_mem_fun1_ref_t<R, X, A> Класс const_mem_fun1_ref_t — адаптер для функций-членов. Если X представляет со- бой некоторый класс с функцией-членом R X.. f (A) const (то есть функцией-членом принимающей единственный аргумент типа А и возвращающей значение типа R ъу то const_mem_fun1_ref_t<R, X, А> — адаптер функционального объекта, позволяющий вызвать f () как обычную функцию, а не функцию-член. Конструктор принимает указатель на одну из функций-членов X. Кроме того, как и у всех функциональных объектов, у адаптера const_mem_fun1_ref_t есть operator(), вызываемый с помощью обычного синтаксиса вызова функции. В таком случае operate г () в const_mem_f un1_ref_t принимает два аргумента. Первый — типа const Х&, а второй — типа А. Если F — это const_mem_f un1_ref_t, сконструированный для вызова функции-чле- на X- f, и если х — объект типа X, а а — значение типа А, то выражение F(x, а) эквива- лентно выражению х. f (а). Разница между ними синтаксическая: F обеспечивает ин- терфейс Адаптируемой Бинарной Функции. Непосредственное применение конструктора const_mem_fun1_ref_t, как и многих других адаптеров функциональных объектов, неудобно. Вместо него лучше исполь- зовать вспомогательную функцию mem_f un_ref. Пример struct В { virtual int f(int x) const = 0, }. struct D public В { int val, D(int x) val(x) {} int f(int x) const { return val + x, } } int main() { vector<D> v v push_back(D(3)), v push_back(D(4)), v push_back(D(5)), int A[3] - {7. 8. 9}, transform(v begin(), v end(), A, ostream_iterator<int>(cout, ” "). mem_fun_ref(&B f)), ) Tun R может бы i i> void.
15.7. Адаптеры функций-членов 413 coot « endl, // Резулыат 10 12 14 } Где определено jQiacc const_mem_f un1_ref_t не входил в реализацию HP. В соответствии со стандар- том языка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона R Тип, возвращаемый функцией-членом. X Класс, функцию-член которого вызывает const_mem_fun1_ref _t. А Тип аргумента функции-члена. Является моделью Адаптируемой Бинарной Функции (с. 128). Требования к типу , • R является Присваиваемым или void. • X — это класс, у которого есть по крайней мере одна const-функция-член, прини- мающая единственный аргумент типа А и возвращающая значение типа R. • А является Присваиваемым. Открытые базовые классы binary_function<X, A, R> Члены Некоторые члены const_mem_f un1_ref_t не определены в требованиях к Адаптируемой Бинарной Функции, а специфичны для const_mem_f un1_ref_t; они помечены символом ♦. const_mem_fun1_ref_t:: first_argument_type Тип первого аргумента: X (Описан в Адаптируемой Бинарной Функции.) const_mem_fun1_ref_t::second_argument_type Тип второго аргумента: А (Описан в Адаптируемой Бинарной Функции.) const_mem_fun1_ref_t::result_type Тип результата: R (Описан в Адаптируемой Бинарной Функции.) R const_mem_fun1_ref_t::operator(const Х& х, A a) const Оператор вызова функции. Вызывает х f (а); где f — это функция-член, которая была передана конструктору. (Описан в Адаптируемой Бинарной Функции.) ♦explicit const_mem_fun1_ref_t::const_mem_fun1_ref_t(R (X::*f)(A) const) Конструктор. Создает const_mem_fun1_ref_t для вызова функции-члена f.
414 Глава 15. Классы функциональных объектов ♦ template <class R, class X, class A> const_mem_fun1_ref_t<Rl X, A> mem_fun_ref(R (X::*f)(A) const) Вспомогательная функция для создания const_mem_fun1_ref_t. Если f имеет тцп R (Х-.*)(А) const, то mem_fun_ref(f) идентичен const jnem_fun1_ref_t<R, X, A>(f) 15.8. Другие адаптеры 15.8.1. binderlst binder1st<BinaryFun> Класс binderlst — адаптер функционального объекта. Он используется для преобра- зования Адаптируемой Бинарной Функции в Адаптируемую Унарную Функцию. Если f - объект класса binder1st<BinaryFun>,Tof(x) возвращает F (с, х), где F — объект класса BinaryFun, а с — константа. Гис передаются в виде аргументов конструктора binderlst Эту операцию можно представить как “связывание” первого аргумента бинарной функции с константой, превращающее эту функцию в унарную. Простейший способ создания binderlst — это не прямой вызов конструктора, а ис- пользование вспомогательной функции bindlst. Пример Ищется первый ненулевой элемент в списке. int main() { list<int> L, for (int 1=0, i < 20, ++i) L push_back(rand() % 3), list<int>. iterator first_nonzero = find_if(L begin(), L end(), bind1st(not_equal_to<int>(), 0)); assert(first_nonzero == L end() || *first_nonzero l= 0), } Где определено В реализации HP binderlst был определен в заголовочном файле <function h>. В со- ответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctionaP- Параметры шаблона BinaryFun Тип бинарной функции, первый аргумент которой дол*сН быть связан с константой. Является моделью Адаптируемой Унарной Функции (с. 127). Требования к типу • - BinaryFun — модель Адаптируемой Бинарной Функции.
15.8. Другие адаптеры 415 Открытые базовые классы unary_function<typename BinaryFun second_argument_type. typename BinaryFun- resulr_type> Члены Некоторые члены binderlst не определены в требованиях к Адаптируемой Унарной функции, а специфичны для binderlst; они помечены символом ♦. binderlst:: argument-type Тип аргумента: BinaryFun: :second_argument_type (Описан в Адаптируемой Унарной Функции.) binderlst::result-type Тип результата: BinaryFun:: result_type (Описан в Адаптируемой Унарной Функции.) result-type binderlst::operator()(const argument_type& x) const Оператор вызова функции. Возвращает F(с, х), где F и с — аргументы конструк- Topabinderlst. (Описан в Адаптируемой Унарной Функции.) ♦ binderlst::binder1st(const BinaryFun& F, typename BinaryFun::first-argument-type c) Конструктор. Создает binderlst таким образом, что вызов его с аргументом х (где х имеет тип BinaryFun:: second_argumenr_type) соответствует вызову F(c, х). ♦ template <class BinaryFun, class T> binder1st<BinaryFun> bind1st(const BinaryFun& F, const T& c) Вспомогательная функция для создания объекта binderlst. Если F — это объект типа BinaryFun, то bindlst(F, с) эквивалентно binder1st<BinaryFun>(F, с), но удоб- нее. Тип Т преобразуется к типу первого аргумента BinaryFun. 15.8.2. binder2nd binde r2nd<Bina ryFun> Класс binde r2nd — адаптер функционального объекта. Он используется для преобразо- вания Адаптируемой Бинарной Функции в Адаптируемую Унарную Функцию. Если f — втообъект класса binder2nd<BinaryFun>, то f (х) возвращает F(x, с), где F — объект клас- са BinaryFun, а с — константа. F и с передаются в виде аргументов конструктора binde r2nd. Эту операцию можно представить как “связывание” второго аргумента бинарной Функции с константой, превращающее эту функцию в унарную. Простейший способ создания binder2nd — это не прямой вызов конструктора, а ис- пользование вспомогательной функции bind2nd. пример Ищется первое положительное число в списке. int main() { list<int> L, for (int 1=0. i < 20, ++i)
416 Глава 15. Классы функциональных объектов L push_back(rand() % 4 - 3). list<int> iterator first_positive = find_if(L begin(), L end(). bind2nd(greater<int>() 0)). assert(first-positive == L end() || *first_positive > 0). Где определено В реализации HP binder2nd был определен в заголовочном файле <function h>. в Со_ ответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctionab Параметры шаблона BinaryFun Тип бинарной функции, второй аргумент которой должен быть связан с константой. Является моделью Адаптируемой Унарной Функции (с. 127). Требования к типу BinaryFun — модель Адаптируемой Бинарной Функции. Открытые базовые классы unary_function<typename BinaryFun first_argument_type, typename BinaryFun result_type> Члены Некоторые члены binder2nd не определены в требованиях к Адаптируемой Унарной Функции, а специфичны для binder2nd; они помечены символом ♦. binder2nd::argument-type Тип аргумента: BinaryFun.. f i rst_argument_type (Описан в Адаптируемой Унарной Функции.) binder2nd::result-type Тип результата: BinaryFun: • result_type (Описан в Адаптируемой Унарной Функции.) result_type binder2nd::operator()(const argument_type& x) const Оператор вызова функции. Возвращает F(х, с), где Рис — аргументы конструк- тора binder2nd. (Описан в Адаптируемой Унарной Функции.) ♦ binder2nd:: binder2nd(const BinaryFun& F, typename BinaryFun::second-argument_type °) Конструктор. Создает такой binder2nd, что вызов его с аргументом х (где х имеет тип BinaryFun first_argument_type) соответствует вызову F(x, с). ♦ template <class BinaryFun, class T> binder2nd<BinaryFun> bind2nd(const BinaryFun& F. const T& c) Вспомогательная функция для создания объекта binder2nd. Если F — объект тШ1а BinaryFun, то bind2nd(F, с) эквивалентно binder2na<BinaryFun>(F, с), но удобнее Тип Т преобразуется к типу второго аргумента BinaryFun.
15.8 Другие адаптеры 417 |5.8.3. pointer_to_unary_function pointer-to-unary-furiction<Ar9« Result> jQiacc oointer_to_unary_function — адаптер функционального объекта, который по- зволяет использовать указатель па функцию Result (*f )(Arg) в качестве Адаптиру- Унарной Функции. Если F — pointer_to_unary_function<Arg, Result>, который был инициализирован указателем на функцию f типа Result (*)(Агд),то F(x) вызывает функцию f(x) Раз- iiHiia между f и F заключается в том, что pointer_to_unary_function — это Адаптиру- емая Унарная Функция, то есть в данном классе определены вложенные типы argument_type и result_type. Указатель на функцию типа Result (*) (Arg) сам по себе является отличной Унар- ной Функцией, и его можно передать любому алгоритму STL, который принимает Унар- ную Функцию в качестве аргумента. pointer_to_unary_function нужен,только если вы используете обычный указатель на функцию в контексте, требующем Адаптируемую Унарную Функцию (например, как аргумент адаптера функционального объекта). В большинстве случаев вам не придется непосредственно применять конструктор pointer_to_Linary_f unction. Проще использовать вспомогательную функцию ptr_fun. Пример Числа в диапазоне заменяются на их абсолютные значения с помощью стандартной библиотечной функции tabs. В данном случае нет необходимости использовать адап- тер pointer_to_unary_function, потому что можно просто передать tabs прямо в transform: transformsf]rst, last, first, tabs). В противоположность этому следующий фрагмент кода заменяет все числа диапа- зона на отрицательную величину их абсолютных значений. Мы объединяем tabs и negate, который требует, чтобы tabs была Адаптируемой Унарной Функцией, поэто- му нам нужно использовать pointer_to_unary_function transformers!, last, first, composel(negate<double>, ptr_fun(tabs))). Где определено В реализации HP адаптер pointe r_to_unary_f unction был определен в <f unction h>. В со- ответствии со стандартом языка C++ он объявлен в <f unctional>. Параметры шаблона Аг9 Тип аргумента функционального объекта. Result Тип результата функционального объекта Является моделью Адаптируемой Унарной Функц ии (с. 127). требования к типу • Arg является Присваиваемым. • Result является Присваиваемым.
418 Глава 15. Классы функциональных объектов Открытые базовые классы unary_function<Arg. Result> Члены Некоторые члены класса pointer_to_unary_function не определены в требованиях к Адаптируемой Унарной Функции, а специфичны для pointer_to_unary_f unction; оНи помечены символом ♦. pointer_to_unary_function::argument-type Тип аргумента: Arg (Описан в Адаптируемой Унарной Функции.) pointer_to_unary_function::result-type Тип результата: Result (Описан в Адаптируемой Унарной Функции.) Result pointer_tO-unary-function::operator()(Arg) const Оператор вызова функции. (Описан в Адаптируемой Унарной Функции.) ♦ pointer_to_unary_function::pointer-tO-Unary_function(Result (*f)(Arg)) Конструктор. Создает pointer_to_unary _f unction, базовым указателем на функ- цию которого служит f. pointer-to_unary-function::pointer_to_unary_function() Конструктор по умолчанию. Создает pointe r_to_unary_f unction, у которого ба- зовым указателем на функцию является нулевой указатель. ♦ template <class Arg, class Result> pointer_to_unary_function<Arg, Result> ptr_fun(Result (*x)(Arg)) Вспомогательная функция для создания pointer_to_unary_f unction. Если f име- ет тип Result (*)(Arg), то выражение ptr_fun(f) эквивалентно явному вызову конструктора pointer_to_unary_function<Arg, Result>(f), но удобнее. 15.8.4. pointer_to_binary_function pointer_to_binary_function<Arg1, Arg2, Result> Класс pointer_to_binary_function — адаптер функционального объекта, который по- зволяет использовать указатель на функцию Result (*F)(Arg1, Arg2) в качестве Адап- тируемой Бинарной Функции. Если F — объект класса pointer_to_binary_f unction<Arg1 • Arg2, Result> с базовым у казател ем на функцию f типа Result (* )(Arg1, Arg2),ToF(x. у) вызывает функцию f (х. у). Разница между f и F заключается в том, чТ0 pointe r_to_binary_f unction — Адаптируемая Бинарная Функция, то есть в данном классе определены вложенные типы first_argument_type, second_argument_type и results уРе, Указатель на функцию типа Result (*) (Arg 1, Arg2) сам по себе является отличной Бинарной Функцией, и его можно передать любому алгоритму STL, который прини мает Бинарную Функцию в качестве аргумента. pointer_to_binary_function нужен- только если вы используете обычный указатель на функцию в контексте, требуюшеМ Адаптируемую Бинарную Функцию (например, как аргумент адаптера функционал^ ного объекта).
15.8. Другие адаптеры 419 В большинстве случаев вам не придется непосредственно применять конструктор pointer_to_binary_f unction. Легче использовать вспомогательную функцию ptr_fun. Пример Следующий фрагмент кода находит первую строку в списке, равную строке ОК. Аргумен- том адаптера функционального объекта является стандартная библиотечная функция stгетр, поэтому нужно сначала использовать адаптер pointer_to_binary_f unction, чтобы у st гетр был интерфейс Адаптируемой Бинарной Функции. list<char*> iterator item = find_if(L begin(), L end(), not1(bind2nd(ptr_fun(strcmp), "OK’))), Где определено В реализации HP адаптер pointer_to_binary_f unction был определен в <f unction. h>. В со- ответствии co стандартом языка C++ он объявлен в <f unctional>. Параметры шаблона Arg 1 Тип первого аргумента функционального объекта. Arg2 Тип второго аргумента функционального объекта. Result Тип результата функционального объекта. Является моделью Адаптируемой Бинарной Функции (с. 128). Требования к типу • Arg 1 является Присваиваемым. • Arg2 является Присваиваемым. • Result является Присваиваемым. Открытые базовые классы binary_function<Arg1, Arg2. Result> Члены Некоторые члены класса pointer_to_biпаry_function не определены в требованиях к Адаптируемой Бинарной Функции, а специфичны для pointer_to_binary_f unction; они Помечены символом pointer_to_binary_function:: first_argument_type Тип первого аргумента: Arg1 (Описан в Адаптируемой Бинарной Функции.) pointer_to_binary_function::second_argument_type Тип второго аргумента: Arg2 (Описан в Адаптируемой Бинарной Функции.) pointer_to_binary_function::result-type Тип результата: Result (Описан в Адаптируемой Бинарной Функции.)
420 Глава 15. Классы функциональных объектов Result pointer_to_binary_function::operator()(Arg1, Arg2) const Оператор вызова функции. (Описан в Адаптируемой Бинарной Функции.) ♦ pointer_to_binary_function ::pointer_toJ)inary_function(Result (*f)(Arg1, Arg2)) Конструктор. Создает pointer_to_binary_f unction, у которого базовым указате- лем на функцию является f. pointer_to_binary_function::pointer_to_binary_function() Конструктор по умолчанию. Создает pointer_to_binary_function, у которого базовым указателем на функцию является нулевой указатель. ♦ template <class Arg1, class Arg2, class Result> pointer_to_blnary_function<Arg1, Arg2, Result> ptr_fun(Result (*x)(Arg1, Arg2)) Вспомогательная функция для создания объекта pointer_to_binary_f unction. Если f является указателем на функцию типа Result (*)(Arg1, Arg2), то ptr_fun(f) эквивалентно pointer_to_binary_function<Arg1, Arg2, Result>(f), но удобнее. 15.8.5. unary_negate unary_negate<Predicate> Класс una ry_negate — Адаптируемый Предикат, который представляет собой логичес- кое отрицание другого Адаптируемого Предиката. Если f — объект типа unary_negate<Predicate>, a pred — функциональный объект, на базе которого был сконструирован f, то f (х) возвращает ' рred(х). (Строго говоря, unary negate является лишним. Его можно сконструировать с помощью функциональ- ного объекта logical_not и адаптера unary_compose.) Конструктор unary_negate используется крайне редко. Проще использовать вспо- могательную функцию not1. Например, вместо unary_negate<Pred>(f) лучше писать not1(f). Пример Ищется в списке первый элемент, который не находится в диапазоне 1...10. lot main() i iist<inr> L generate_n(back_inserter(L), 10000, rand), list<int> iterator i = find.if(L begin(), L end(), not 1(compose?(logical_and<bool>(), bind2nd(greater_equal<int>(). 1), bind2nd(less_equal<int>(), 10)))), assert(i == L end() || ’(*i >= 1 && *i <= 10)).
15.8. Другие адаптеры 421 рде определено р реализации HP адаптер unary_negate был определен в заголовочном файле junction h>. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <functional>. Параметры шаблона predicate Тип функционального объекта, для которого unary_negate является логическим отрицанием. Является моделью Адаптируемого Предиката (с. 131) Требования к типу • Predicate — модель Адаптируемого Предиката. Открытые базовые классы unary_function<typename Predicate argument-type, bool> Члены Некоторые члены unary negate не определены в требованиях к Адаптируемому Пре- дикату, а специфичны для unary negate; они помечены символом ♦. unary_negate::argument-type Тип аргумента: Predicate argument-type (Описан в Адаптируемом Предикате.) unary_negate::result-type Тип результата: bool (Описан в Адаптируемом Предикате.) bool unary_negate::operator()(argument-type) const Оператор вызова функции. (Описан в Адаптируемом Предикате.) ♦ unary_negate::unary_negate(const Predicate&) Конструктор. Создает unary_negate<Predicate>, базовым предикатом которого является р. ♦ template <class Predicate> unary_negate<Predicate> not1(const Predicate& p) Вспомогательная функция для создания unary.negate. Если р имеет тип Predicate, то not1 (р) эквивалентно unary_negate<Predicate>(p), неудобнее. 15.8.6. binary_negate binary _negate<BinaryPredicate> ^Ласс oinary negate — Адаптируемый Бинарный Предикат, который представляет собой Логическое отрицание другого Адаптируемого Бинарного Предиката.
422 Глава 15. Классы функциональных объектов Если f - объект класса binary_negate<BinaryPredicate^ a pred — функциональна объект, на базе которого был сконструирован f, то f (х, у) возвращает ’pred(x.y) Конструктор binary_negate используется крайне редко. Легче использовать вСПо могательную функцию not2. Вместо binary_negate<Pred>(f) лучше писать not2(f) Пример Ищется в строке первый символ, который не является ни “ ” (пробелом), ни “\п”. char str[MAXLEN], const char* wptr = find_if(str. str + MAXLEN, compose2(not2(logical_or<bool>()), bind2nd(equal_to<char>(), ’), bind2nd(equal_to<char>(). ’\n’))), assert(wptr == str + MAXLEN || ’(*wptr == ’ ' || *wptr == ’\n’)). Где определено В реализации HP адаптер binary_negate был определен в заголовочном файле <f unction. h>. В соответствии со стандартом языка C++ он объявлен в заголовочном файле <f unctional>. Параметры шаблона BinaryPredicate Тип функционального объекта, для которого binary_negate является логическим отрицанием. Является моделью Адаптируемого Бинарного Предиката (с. 131). Требования к типу • BinaryPredicate — модель Адаптируемого Бинарного Предиката. Открытые базовые классы binary_function<typename BinaryPredicate first_argument_type, typename ВзnaryPredicate second_argument_type. bool> Члены Некоторые члены binary negate не определены в требованиях к Адаптируемому Би- нарному Предикату, а специфичны для binary_negate; они помечены символом binary_negate::first_argument_type Тип первого аргумента: BinaryPredicate: first_argument_type (Описан в Адаптируемом Бинарном Предикате.) binary_negate::second_argument_type Тип второго аргумента: Binarypredicate .second_argument_type (Описан в Адаптируемом Бинарном Предикате.)
15.8. Другие адаптеры 423 binary_negate::result_type Тип результата: bool (Описан в Адаптируемом Бинарном Предикате.) bool binary_negate::operator()(first_argument_type1 second_argument_type) const Оператор вызова функции. (Описан в Адаптируемом Бинарном Предикате.) Ф binary_negate::binary_negate(const BinaryPredicate&) Конструктор. Создает binary_negate<BinaryPredicate>, базовым предикатом которого является р. Ф template <class BinaryPredicate> binary_negate<BinaryPredicate> not2(const BinaryPredicate& p) Вспомогательная функция для создания binary_negate. Если р имеет тип BinaryPredicate, то выражение not2(p) эквивалентно явному вызову конструк- тора binary_negate<BinaryPredicate>(p), но удобнее. 15.8.7. unary_compose unary_compose<Function1,Function2> Класс и па ry_compose — адаптер функционального объекта. Если f и g — Адаптируемые Унарные Функции, а тип, возвращаемый д, преобразуем к типу аргумента f, то unary_compose можно использовать для создания такого функционального объекта h, что выражение h(x) будет аналогично f (g(x)). Эта операция называется композицией функций, отсюда имя unary_compose. Она обозначается в математике f ° Л, так что (/° g )(х) — это f ( g(x) ). Понятие компози- ции функций очень важно в алгебре. Она важна также как метод сборки компонент программного обеспечения из других компонент, потому что позволяет конструиро- вать сколь угодно сложные функциональные объекты из простых. Простейший способ создать unary_compose, как и другие адаптеры функциональных объектов, состоит в использовании вспомогательной функции composed Можно непос- редственно вызвать конструктор unary_compose, но обычно в этом нет необходимости. Пример Вычисляются со знаком минус синусы элементов в vector, где каждый элемент пред- ставляет собой угол, измеренный в градусах. Поскольку в языке Си аргумент биб- лиотечной функции sin измеряется в радианах, то эта операция является композици- ей трех операций: отрицание, вычисление синуса и перевод градусов в радианы. vector<double> angles, vector<double> sines, const double pi = 3 14159265358979323846; assert(sines sizc() >= angles size()), transform(angles begin(), angles end(), sines begin(), composel(negate<double>()t compose1(ptr_fun(sin), bind2nd(multiplies<double>(), pi / 180 )))),
424 Глава 15. Классы функциональных объектов Где определено В оригинальной версии HP STL unary_compose был определен в заголовочном файае <function. h>, а в SGI STL — в заголовочном файле <functional>. В стандарт языка C++ этот класс не входит, но обычно присутствует в виде расширения. Параметры шаблона Functionl Тип первого операнда функциональной композиции, то есть если композиция записана в виде f ° g, то Functionl — ТИп функционального объекта f. Functions Тип второго операнда функциональной композиции, то есть если композиция записана в виде/ ° g, то Functions — тип функционального объекта д. Является моделью Адаптируемой Унарной Функции (с. 127). Требования к типу • Functionl — модель Адаптируемой Унарной Функции. • Functions — модель Адаптируемой Унарной Функции. • Functions:: result_type можно преобразовать к типу Functional: :argument_type. Открытые базовые классы unary_function<typename Functions- argument-type, typename Functionl result_type> Члены Некоторые члены unary compose не определены в требованиях к Адаптируемой Унар- ной Функции, а специфичны для unary_compose; они помечены символом ♦. ипагу-Compose::argument-type Тип аргумента функционального объекта: Functions: : argument-type (Описан в Адаптируемой Унарной Функции.) unary_compose::result-type Тип возвращаемого значения функционального объекта: Functionl. • result_tyPe (Описан в Адаптируемой Унарной Функции.) ♦ unary_compose(const Function1& f, const Function2& g) Конструктор. Создает объект una ry_compose, который представляет собой функ- циональный объект f ° g. ♦ template <class Functionl, class Function2> unary_compose<Function1, Function2> composel(const Function1& op1, const Function2& op2) Вспомогательная функция для создания объекта unary_compose. Если f и9" функциональные объекты классов Functionl и Functions соответственно, т° composel (f. g) эквивалентно unary_compose<Function1, FunctionS>(f, g), но удобнее-
15.8. Другие адаптеры 425 |5,8.8. binary_compose ^пагУ-compose<BinaryFunction, UnaryFunctionl, UnaryFunction2> jQiacc binary_compose — адаптер функционального объекта. Если f — Адаптируемая унарная Функция, a gi и g2 — Адаптируемые Унарные Функции и если типы, возвра- щаемые д1 и д2, можно преобразовать к типам аргументов f, то bina ry compose можно использовать для создания такого функционального объекта h, что выражение h(x) эквивалентно f(g1(x), g2(x)). Пример Ищется в списке первый элемент, который находится в диапазоне 1... 10. l!St<int> L, list<int> iterator in_range - find_if(L begin(), L end(), compose2(logical_and<bool>(), bind2nd(greater_equal<int>(), 1), bind2nd(less_equal<int>(), 10))), assert(in_range == L end() || (*in_range >= 1-&& *in_range <= 10)), Вычисляется sin(x)/(x + DBL_MIN) для каждого элемента в диапазоне. transform(first, last, first. compose2(divides<double>(), ptr_fun(sin), bind2nd(plus<double>(), DBL__MIN))), Где определено Воригинальной версии HP STL bina ry_compose был определен в заголовочном файле function. h>, а в SGI STL — в заголовочном файле <functional>. В стандарт языка C++ он не входит, но обычно входит в расширение стандарта. Параметры шаблона BinaryFunction UnaryFunctionl UnaryFunction2 Тип “внешней” функции в операции функциональной ком- позиции, то есть если адаптер binary_compose создает такой функциональный объект h, что h(x) == f(g1(x), g2(x)), то BinaryFunction имеет тип f. Тип первой “внутренней” функции в операции функцио- нальной композиции, то есть если адаптер binary compose создает такой функциональный объект h, что h(x) = = f (gl(x), g2(x)), то UnaryFunctionl имеет тип g1. Тип второй “внутренней” функции в операции функцио- нальной композиции, то есть если адаптер binary compose создает такой функциональный объект h, что h(x) = = f (g1 (х), g2(x)), то UnaryFunction2 имеет тип g2. Является моделью Адаптируемой Бинарной Функции (с. 128).
426 Глава 15. Классы функциональных объектов Требования к типу • BinaryFunction — модель Адаптируемой Бинарной Функции. • UnaryFunctionl и UnaryFunction2 — модели Адаптируемой Унарной Функции. • Типы аргументов UnaryFunctionl и UnaryFunction2 можно преобразовать друг в друга. • Типы результата UnaryFunctionl и UnaryFunction2 можно преобразовать, соответ- ственно, к типам первого и второго аргументов BinaryFunction. Открытые базовые классы unary_function<UnaryFunction1 argument-type. BinaryFunction .result_type> Члены Некоторые члены binary_compose не определены в требованиях к Адаптируемой Би- нарной Функции, а специфичны для binary_compose; они помечены символом ♦. binary_compose::argument-type Тип аргумента функционального объекта: UnaryFunctionl •: argument-type (Описан в Адаптируемой Унарной Функции.) binary_compose::result-type Тип результата функционального объекта: BinaryFunction:: result_type (Описан в Адаптируемой Унарной Функции.) ♦ binary_compose(const BinaryFunction& f, const UnaryFunctionU gl, const UnaryFunctlon2& g2) Конструктор. Создает такой объект binary compose, что вызов этого объекта с ар- гументом х возвращает f(g1(x), g2(x)). ♦ template <class BinaryFunction, class UnaryFunctionl, class UnaryFunction2> binary_compose<BinaryFunction, UnaryFunctionl, UnaryFunction2> compose2(const BinaryFunction&, const UnaryFunction1&, const UnaryFunction2&) Вспомогательная функция для создания объекта binary_compose. Если f, g1 п g2 являются значениями типов BinaryFunction, UnaryFunctionl и UnaryFuncrion2 со- ответственно, то выражение compose2( f, g1, g2) эквивалентно явному вызову кон- структора binary_compose<BinaryFunction, UnaryFunctionl, UnaryFunction2>(f. g1. q2)> но удобнее.
16 Классы контейнеров Встроенные классы контейнеров STL являются моделями Последовательности или Ассоциативного Контейнера. Все они представляют собой шаблоны, экземпляры ко- торых создаются для хранения любых типов объектов. Можно воспользоваться vector<int> как обыкновенным Си-массивом, но класс vector избавит вас от необхо- димости ручного управления динамической памятью. Кроме последовательностей и ассоциативных контейнеров в STL определено три вида адаптеров контейнеров. Адаптеры контейнеров — это не контейнеры. Они не яв- ляются моделями концепции Контейнера и намеренно предоставляют лишь ограни- ченную функциональность. 16.1. Последовательности Стандарт C++ определяет три последовательности: vector (простая структура дан- ных, предоставляющая быстрый произвольный доступ к своим элементам), deque (более сложная структура данных, позволяющая эффективно вставлять и удалять элементы на обоих концах контейнера) и list (класс, реализующий двусвязный спи- сок). Ввиду того что vector — простейший класс контейнеров STL, он обычно являет- ся наилучшим выбором. В стандарте нет односвязного списка, но такие списки обычно входят в расшире- ние библиотеки. В эту главу включено описание siist — односвязного списка из реа- лизации SGI STL. 16.1.1. vector (вектор) vector<T, Allocator Шаблон vector — это Последовательность, которая обеспечивает произвольный до- ступ к элементам, вставку и удаление их в конце за константное время, а также в на- чале или середине за линейное время. Количество элементов в vector может динами- чески изменяться. Управление памятью осуществляется автоматически.
428 Глава 16. Классы контейнеров Шаблон vector — простейший среди шаблонов контейнеров STL, и во многих еду чаях он наиболее эффективен. Обычно его элементы располагаются в »епрерывНо| об части памяти, что позволяет реализовать итераторы в vector в виде указателей Существует важное различие между размером (size) vector (количеством элемец. юв, которое он содержит) и его емкостью (capacity) (количеством элементов, Г1о которое выделена память). Легче всего понять эту разницу в терминах типичной реа- лизации vector. Класс vector управляет блоком памяти и конструирует свои элемен- ты в начале этого блока. У него обычно есть три члена, которые являются указателя- ми: start, finish nend_of_storage. Все элементы vector находятся в диапазоне [sta-- finisn), тогда как диапазон [finish, end_of_storage) состоит из неинициализирован- ной памяти. Размер vector равен finish - start, а емкость равна eno_of_storage - star- Емкое! ь всегда больше или равна размеру вектора. Разница между размером и емкостью становится особенно важна, когда вы встав- ляете элементы в vector. Если размер vector равен его емкости (то есть вся памя1ь vector проинициализирована), то единственный способ вставки нового элемента - увеличение количества памяти, выделенной объектом vector.^ Это означает, что надо выделить новый блок памяти большего размера, скопировать содержимое старого бло- ка в новый и освободить старый блок. Реаллокация (переразмещение) может быть медленной операцией. Кроме того, она делает недопустимыми все итераторы, указывающие в vector. Можно управлять ре- аллокациями вручную при помощи специальной функции-члена reserve, которая уве- личивает емкость vector, не изменяя пи его размер, ни значения его элементов При реаллокации все итераторы vector становятся недопустимыми. Вставка и уда- ление элементов в середине vector также делает недопустимыми те итераторы, кото- рые указывали на элементы, следующие за местом вставки или удаления. Вы можете это предотвратить, если заранее воспользуетесь функцией-членом reserve для выде- ления памяти, достаточной для хранения всех элементов в vector, и если все опера- ции вставки и удаления будут происходить в конце vector. Пример Создание пустого vector и вставка в него одного элемента. int main() { vector<int> v, v insert(v begin(), 3) assert(v size() == 1 && v capacityO >= 1 && v[0] == 3), } Чтение чисел из стандартного ввода с использованием vector для временного хра' нения и печать среднего арифметического этих чисел (Размер vector будет увеличи- ваться по мере необходимости.) Пример полагается на важный аспект конструктора vector можно создать из диапазона Итераторов Ввода — в нашем случае из диапазона * Когда класс vector выполняй атома! ическую рсачлокацпю, он обычно увеличивает свою емк(Н |Ь вдвое Исключи ic.i.’iiaio важно, чтобы емкое н> vector возрастала пропорционально ickviucmv раьмгр4 а не па какую-1 о фиксированную iconcianiv В нервом случае. воавка нос тедова i елыюс i и )лемс||11)|’ в /ее ! О’ выно Н1ЯСГСЯ <а линейное время, а во втором — за квадрантное
16.1. Последовательности 429 |ггераторов типа istream_iterator. В примере также используется факт, что vector предоставляет Итераторы Произвольного Доступа. int main() { istream_iterator<double> first(cin), istream_iterator<double> end_of_file. vector<double> buf(first, end_of_file), nth_element(buf begin(), but begin() + but size() / 2. but end()), cout « "Median " « buf[buf size() / 2] « endl. } Для увеличения емкости vector можно воспользоваться функцией-членом reserve, но аналогичной функции для уменьшения емкости нет, поскольку емкость vecto г легко сокращается без каких-либо специальных функций-членов. Следующий пример де- монстрирует один из способов реализации этой задачи. template <class Т, class AllocatoO void shrink_to_fit(vector<T, Allocators v) { vectorcT, AllocatoO tmp(v begin(), v end()), tmp swap(v), } Где определено В реализации HP vector определен в заголовке <vector. h>. В соответствии со стан- дартом C++ он объявлен в заголовке <vector>. Параметры шаблона Т Тип значения vectoг: тип объекта, хранящегося в vector. Allocator Аллокатор vector, используемый для управления памятью. По умолчанию: allocator<T> Является моделью Контейнера Произвольного Доступа (с. 146), Последовательности с Концевой Встав- кой (с. 155). Требования к типам • Т — модель Присваиваемого. • Allocate г — модель Аллокатора, тип значения которого равен Т. Открытые базовые классы Отсутствуют. Члены Некоторые члены vector не определены в требованиях к Контейнеру Произвольного Доступа и Последовательности с Концевой Вставкой, а специфичны для vector; они Помечены символом ♦.
430 Глава 16. Классы контейнеров vector::value_type Тип объекта, хранящегося в vector, — Т. (Описан в Контейнере.) vector::pointer Указатель на Т. (Описан в Контейнере.) vector::const_pointer Указатель на const Т. (Описан в Контейнере.) vector::reference Ссылка на Т. (Описан в Контейнере.) vector::const_reference Ссылка на const Т. (Описан в Контейнере.) vector::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) vector::difference_type Целочисленный тип со знаком, обычно pt rdiff_t. (Описан в Контейнере.) vector::iterator Тип итератора vector, являющийся переменным Итератором Произвольного Доступа. (Описан в Контейнере.) vector::const_iterator Константный тип итератора vector — константный Итератор Произвольного Доступа. (Описан в Контейнере.) vector::reverse.iterator Итератор, используемый для перемещения по vector в обратном направлении, являющийся переменным Итератором Произвольного Доступа. (Описан в Реверсивном Контейнере.) vector::const_reverse_iterator Итератор, используемый для перемещения по vector в обратном направлении, являющийся константным Итератором Произвольного Доступа. (Описан в Реверсивном Контейнере.) vector::begin() Bo3Bpamaeriterator, указывающий на начало vector. (Описан в Контейнере.)
16.1. Последовательности 431 vector::end() Возвращает iterator, указывающий на конец vector. (Описан в Контейнере.) vector::begin() const Возвращает const-iterator, указывающий на начало vector. (Описан в Контейнере.) vector::end() const Возвращает const-iterator, указывающий на конец vector. (Описан в Контейнере.) vector:: rbeginQ Возвращает reverse_iterator, указывающий на начало реверсивного vector. (Описан в Реверсивном Контейнере.) vector::rend() Возвращает reverse_iterator, указывающий на конец реверсивного vector. (Описан в Реверсивном Контейнере.) vector::rbegin() const Возвращает const- reve rse_ite rato г, указывающий на начало реверсивного vecto г. (Описан в Реверсивном Контейнере.) vector::rend() const Возвращает const_reverse_iterator, указывающий на конец реверсивного vector. (Описан в Реверсивном Контейнере.) size_type vector::size() const Возвращает количество элементов в vector. (Описан в Контейнере.) size_type vector::max_size() const Возвращает максимально допустимый размер vector. (Описан в Контейнере.) ♦ size_type vector::capacity() const Возвращает емкость vector, то есть количество элементов, под которое была вы- делена память. Емкость всегда больше или равна размеру vector. (Дополнитель- ная память под vector выделяется автоматически, если вставляется элементов больше, чем capacity( )~size(). Реаллокация не увеличивает размер vector и не изменяет значений его элементов. Оно увеличивает capacity() и делает недопу- стимыми все итераторы, которые указывают в vector.) bool vector::empty() const Возвращает истинность того, что размер vector равен нулю. (Описан в Контейнере.) reference vector::operator[](size_type n) Возвращает n-й элемент. (Описан в Контейнере Произвольного Доступа.)
432 Глава 16. Классы контейнеров const_reference vector::operatorf](size_type n) const Возвращает n-й элемент. (Описан в Контейнере Произвольного Доступа.) explicit vector:: vector(const Allocators A = AllocatorO) Создает пустой vector, использующий заданный аллокатор. (Описан в Последовательности.) explicit vector::vector(size_type n, const TS x = T<), const Allocators A = AllocatorO) Создает vector, содержащий n копий x и использующий заданный! аллокатор (Описан в Последовательности.) vector::vector(const vectors v) Конструктор копирования (Описан в Контейнере.) template <class Inputlterator> vector:•vector(lnputlterator f, Inputiterator 1, const Allocators A = AllocatorO) Создает vector, содержащий копию диапазона [f, 1) и использующий заданный аллокатор. (Описан в Последовательности.) vector::-vector() Деструктор. (Описан в Контейнере.) vectors operator=(const vectors) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type vector::get_allocator() const Возвращает копию аллокатора, с которым был создан vector. void vector::swap(vectorS) Обменивает значения двух объектов типа vector. (Описан в Контейнере.) ♦ void vector::reserve(size_type n) Увеличивает емкость vector. Если п меньше или равно capacity(), то вызов не приводит ни к каким действиям, в противном случае — является запросом на выделение дополнительной памяти. Если запрос выполнился успешно, capacity!) увеличится до значения, большего или равного п. и все итераторы, указывающие в вектор, станут недопустимыми. Количество и значения элементов остаются неизменными. Главная причина использования reserve() состоит в повышении эффектив- ности. Если вам известна максимальная емкость, которой достигнет vector, наи- более эффективный путь — выделить всю требуемую память единовременно, а не полагаться на механизм автоматической реаллокации. Использование
16.1. Последовательности 433 г оьег ve() также позволяет контролировать, когда итераторы становятся недо- пустимыми. reference vector::front() Возвращает первый элемент. (Описан в Последовательности.) const_reference vector::front() const Возвращает первый элемент. (Описан в Последовательности.) reference vector::back() Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.) const_reference vector::back() const Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.) void vector::push_back(const Т& х) Добавляет х в vector. (Описан в Контейнере с Концевой Вставкой.) void vector::pop_back() Удаляет последний элемент. (Описан в Контейнере с Концевой Вставкой.) iterator vector::insert(iterator pos, const T& x) Вставляет x перед pos, что может привести к тому, что все итераторы, указыва- ющие в vector, станут недопустимыми. (Описан в Последовательности.) template <class Inputlterator> void vector::insert(iterator pos, Inputiterator f, Inputiterator 1) Вставляет диапазон [f, 1) перед pos, что может привести к тому, что все итерато- ры, указывающие в vector, станут недопустимыми. (Описан в Последовательности.) void vector:;insert(iterator pos, size_type n, const T& x) Вставляет n копий x перед pos, что может привести к тому, что все итераторы, указывающие в vector, станут недопустимыми. (Описан в Последовательности.) iterator vector::erase(iterator pos) Удаляет элемент, на который указывает pos, что может привести к тому, что все итераторы, указывающие за pos, станут недопустимыми. iterator vector::erase(iterator first, iterator last) Стирает элементы диапазона [first, last). Все итераторы, следующие за этим диапазоном, становятся недопустимыми. (Описан в Последовательности.)
434 Глава 16. Классы контейнеров void vector::clear() Удаляет все элементы vector. (Описан в Последовательности.) void vector:: resize(size_type n, const T& t = TO) Изменяет размер vector на n. (Описан в Последовательности.) ♦ template <class Inputlterator> void vector::assign(lnputlterator first, Inputiterator last) Эквивалентно стиранию всех элементов в *this и замене их элементами из диа- пазона [first, last). ♦ void vector::assign(size_type n, const T& x) Эквивалентно стиранию всех элементов в *this и замене их п копиями х. bool operator==(const vector&, const vector&) Операция сравнения на равенство. (Описана в Однонаправленном Контейнере.) bool operator<(const vector&, const vector&) Лексикографическое сравнение. (Описано в Однонаправленном Контейнере.) 16.1.2. list (список) list<T, AllocatoO Шаблон list представляет собой двусвязный список (doubly linked list), в котором у каждого элемента есть предыдущий (predecessor) и последующий (successor) эле- менты. Таким образом, это Последовательность, поддерживающая проходы в обоих направлениях, а также вставку и удаление элементов в конце, начале и середине за константное амортизированное время. Список обладает важным свойством: вставка и объединение не приводят к недопустимости итераторов в list, и даже операция удаления делает недопустимыми только те итераторы, которые непосредственно указывают на удаляемый элемент. Порядок итераторов может измениться, то есть в результате операции над списком у list<T>: iterator может оказаться не тот по- следующий или предыдущий элемент, который был раньше. Однако сами итераторы никогда не станут недопустимыми и не станут указывать на другие элементы, если только явно не была выполнена операция, приводящая к недопустимости итератора или изменению элемента. Полезно сравнить list с vector. Предположим, что i — это допустимый итератор типа vector<T>.: iterator. Если в позиции, предшествующей 1, вставить или удалить элемент, то i либо станет указывать на другой элемент, либо будет недопустимым. Наоборот, предположим, что i и j являются итераторами в vector и что существует такое целое п, что i == j + п. В этом случае, даже если элементы будут вставлены в vector, а 1 и j станут указывать на другие элементы, отношение между итераторами останется прежним. Список является полной противоположностью. Итераторы не станут недопустимыми и не станут указывать на другие элементы, но для итераторов list отношение “предыдущий/последующий” не является инвариантом.
16.1. Последовательности 435 , Обычно список реализуется в виде набора узлов, причем каждый из них содержит один элемент списка, а также указатели на последующий и предыдущий узлы. Иногда полезны односвязные списки (singly linked lists), поддерживающие прохо- ды только в одном направлении. Если вам не требуются проходы в обратном направ- лении, slist (с. 442) может оказаться эффективней, чем list. Пример Создаются два пустых списка, к ним добавляются элементы, каждый список сорти- руется, и оба сливаются в один, int main() { list<int> LI, L1 push-back(O), Li push_front(1), LI insert(++L1 begin(), 3), list<int> L2, L2 push_back(4), L2 push_front(2). L1 sort(). L2 sort(). L1 merge(L2). assert(L1 size() == 5); assert(L2 size() == 0), L1 reverse(), copy(L1 begin(), LI end(), ostream_iterator<int>(cout, " ”)), count « endl; // Результат 43210 } Где определено В реализации HP list определен в заголовке <list. h>. В соответствии со стандартом C++ он объявлен в заголовке <1 ist>. Параметры шаблона Т Тип значения list: тип объекта, хранящегося в list. Allocator Аллокатор list, используемый для управления памятью. По умолчанию: allocate г<Т> Является моделью Реверсивного Контейнера (с. 145), Последовательности с Начальной Вставкой (с. 153), Последовательности с Концевой Вставкой (с. 155). Требования к типам • Т — модель Присваиваемого. • Al 1 ocato г — модель Аллокатора, тип значения которого равен Т.
436 Глава 16. Классы контейнеров Открытые базовые классы Отсутствуют. Члены Некоторые члены list не определены в требованиях к Реверсивному Контейнеру Последовательности с Начальной Вставкой и Последовательности с Концевой Встав- кой, а специфичны для list; они помечены символом ♦. list::value.type Тип объекта, хранящегося в list — Т. (Описан в Контейнере.) list.:pointer Указатель на Т. (Описан в Контейнере.) list::const_pointer Указатель на const Т. (Описан в Контейнере.) list::reference Ссылка на Т. (Описан в Контейнере.) list::const_reference Ссылка на const Т. (Описан в Контейнере.) list::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) list::difference_type Целочисленный тип со знаком, обычно ptrdiff.t. (Описан в Контейнере.) list::iterator Tii.ii итератора list, являющийся переменным Двунаправленным Итератором (Описан в Контейнере.) list::const-iterator Константный тип итератора list, являющийся константным Двунаправленным Итератором. (Описан в Контейнере.) list::reverse_iterator Итератор, используемый для перемещения по list в обратном направлении* являющийся переменным Двунаправленным Итератором. (Описан в Реверсивном Контейнере.)
16.1. Последовательности 437 list::const_reverse_iterator Итератор, используемый для перемещения по list в обратном направлении, являющийся константным Двунаправленным Итератором (Описан в Реверсивном Контейнере.) list::begin() Возвращает iterator, указывающий на начало list. (Описан в Контейнере.) list::end() Возвращает iterator, указывающий наконец list. (Описан в Контейнере.) list::begin() const Возвращает const-iterator, указывающий на начало list. (Описан в Контейнере.) list::end() const Возвращает const-iterator, указывающий на конец list. (Описан в Контейнере.) list::rbegin() Возвращает reverse_iterator, указывающий на начало реверсивного list. (Описан в Реверсивном Контейнере.) list::rend() Возвращает reverse-iterator, указывающий на конец реверсивного list. (Описан в Реверсивном Контейнере.) list::rbegin() const Возвращает const_reverse_iterator, указывающий на начало реверсивного list (Описан в Реверсивном Контейнере.) list::rend() const Возвращает const_reverse_iteraror, указывающий на конец реверсивного list. (Описан в Реверсивном Контейнере.) size_type list::size() const Возвращает количество элементов в list. Обратите внимание, что от оценки вре- мени выполнения этой операции требуется порядок только О(№), а не 0(1). (Описан в Контейнере.) size_type list::max_size() const Возвращает максимально возможный размер list. (Описан в Контейнере.) bool list:: emptyO const Возвращает t rue тогда и только тогда, когда размер list равен нулю. (Описан в Контейнере.)
438 Глава 16. Классы контейнеров explicit list: :list(const Allocators A = Allocator!)) Создает пустой list, использующий заданный аллокатор. (Описан в Последовательности.) explicit list::list(size_type n, const TS x = TO. const Allocators A = Allocator!)) Создает list, содержащий n копий x и использующий заданный аллокатор. (Описан в Последовательности.) list::list(const lists 1) Конструктор копирования. (Описан в Контейнере.) template <class Inputlterator> list::list(lnputlterator f, Inputiterator 1, const Allocators A = AllocatorO) Создает list, содержащий копию диапазона [f, 1) и использующий заданный аллокатор. (Описан в Последовательности.) list::-list() Деструктор. (Описан в Контейнере.) list& operator=(const lists) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type list::get_allocator() const Возвращает копию аллокатора, с которым был создан list. void list::swap(listS) Обменивает значения двух объектов типа list. (Описан в Контейнере.) reference list::front() Возвращает первый элемент. (Описан в Последовательности.) const_reference list::front() const Возвращает первый элемент. (Описан в Последовательности.) reference list::back() Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.) const_reference list::back() const Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.)
16.1. Последовательности 439 void list::push_front(const T& t) Вставляет t в начало. (Описан в Контейнере с Начальной Вставкой.) void list::pop_front() Удаляет первый элемент. (Описан в Контейнере с Начальной Вставкой.) void list::push_back(const Т& t) Вставляет t в конец. (Описан в Контейнере с Начальной Вставкой.) void list::pop_back() Удаляет последний элемент. (Описан в Контейнере с Начальной Вставкой.) iterator list::insert(iterator pos, const T& t) Вставляет t перед pos. Ни один итератор не принимает недопустимого значе- ния. (Описан в Последовательности.) template <class Inputlterator> void list::insert(iterator pos, Inputiterator f, Inputiterator 1) Вставляет диапазон [f, 1) перед pos. Ни один итератор не принимает недопусти- мого значения. (Описан в Последовательности.) void list::insert(iterator pos, size_type n, const T& x) Вставляет n копий x перед pos. Ни один итератор не принимает недопустимого значения. (Описан в Последовательности.) iterator list::erase(iterator pos) Удаляет элемент, на который указывает pos. Ни один итератор, кроме pos, не при- нимает недопустимого значения. iterator list::erase(iterator first, iterator last) Удаляет все элементы в диапазоне [first, last). Ни один итератор, кроме тех, которые указывают на удаляемые элементы, не принимает недопустимого зна- чения. (Описан в Последовательности.) void list::clear() Удаляет все элементы list. (Описан в Последовательности.) void list:: resize(size^type n, const T& t = TO) Изменяет размер list на n. (Описан в Последовательности.)
140 Глава 16. Классы контейнеров ♦ template <class Inputlterator> void list::assign(lnputlterator first, Inputiterator last) Эквивалентно удалению всех элементов в *tnis и замене их элементами из диа- пазона [first. last). ♦ void list::assign(size_type n, const T& x) Эквивалентно удалению всех элементов в *this и замене их п копиями х. ♦ void list::splice(iterator pos, list& x) Удаляет все элементы x и вставляет их перед pos. Итератор pos должен быть допустимым итератором в *this, и х должен отличаться от *this. Все итераторы остаются допустимыми, включая те, которые указывают на элементы х. Оценка сложности: 0(1). ♦ void list::splice(iterator pos, list& x, iterator i) Перемещает элемент, на который указывает 1, из х в *this, вставляя его перед pos. Итератор pos должен быть допустимым итератором B*this,ai должен быть итератором, разыменуемым в х. (Не требуется, чтобы списки *this и х были раз- ными.) Все итераторы остаются допустимыми, включая те, которые указывают на элементы х. Если pos == i или pos == ++i, эта функция не выполняет никаких действий. Оценка сложности: 0(1). ♦ void list::splice(iterator pos, list& x, iterator f, iterator 1) Перемещает диапазон [f, 1) из x в *this, вставляя его непосредственно перед pos. Итератор pos должен быть допустимым итератором в *this, a [fi rst, last) дол- жен быть допустимым диапазоном в х. Не требуется, чтобы списки *this и х были разными, но pos не должен быть итератором в диапазоне [first, last). Все итера- торы остаются допустимыми, включая те, которые указывают на элементы х. Оценка сложности: 0(1). ♦ void list::remove(const T& val) Удаляет все элементы, равные val. Относительный порядок оставшихся элемен- тов не меняется, и итераторы, указывающие на нетронутые элементы, остаются допустимыми. Оценка сложности этой функции равна 0(N). Выполняется ров- но size() сравнений на равенство. ♦ template <class Predicate> void list::remove_if(Predicate p) Удаляет все такие элементы *i, что р( *1) имеет значение t rue. Относительный порядок нетронутых элементов остается неизменным, и итераторы, указываю' щие на оставшиеся элементы, остаются допустимыми. Оценка сложности этой функции равна 0(N). Выполняется ровно size() обращений к предикату р. ♦ void list::unique() Удаляет все элементы, кроме первого, в каждой последовательной группе рвв' пых элементов. Относительный порядок оставшихся элементов не меняется, и итераторы, указывающие на нетронутые элементы, остаются допустимыми Функция выполняется за линейное время. Требуется ровно size() - 1 сравне- ний на равенство.
16.1. Последовательности 441 + template cclass BinaryPredicate> void list::unique(BinaryPredicate p) Удаляет все элементы, кроме первого, в каждой последовательной группе рав- ных элементов. При этом элементы i и j считаются равными, если бинарный предикат р( *1, *j) возвращает значение true. Относительный порядок остав- шихся элементов не меняется, и итераторы, указывающие на нетронутые эле- менты, остаются допустимыми. Функция выполняется за линейное время. Тре- буется ровно size() - 1 сравнений на равенство. ♦ void list::merge(list& х) Объединяет два отсортированных списка, удаляя при этом все элементы х и вставляя их в *this. Списки *this и х, непременно различные, должны быть отсортированы при помощи operators Операция слияния является стабильной Если элемент *this равен какому-либо элементу х, то элемент из * this будет по- мещен перед элементом х. Все итераторы, указывающие на элементы в *this и х, остаются допустимыми. Оценка затрат на выполнение этой функции равна O(N). требуется не более size() + х. size() - 1 сравнений. ♦ template <class StrictWeakOrdering> void list::merge(list& x, StrictWeakOrdering Comp) Объединяет два отсортированных списка, удаляя при этом все элементы х и вставляя их в *this, используя отношение упорядочения Comp. Функция срав- нения Comp должна быть Строгим Слабым Упорядочением с типом аргумента Т, а списки *this и х отсортированы в соответствии с этим правилом упорядочива- ния. Два списка, х и *this, должны быть различными. Операция слияния явля- ется стабильной. Если элемент из *this равен элементу из х, то элемент из *this будет помещен перед элементом из х. Все итераторы, указывающие на элементы в *this и х, остаются допустимыми. Оценка затрат на выполнение этой функции равна O(N): требуется не более size() + х size() - 1 сравнений Comp. ♦ void list:: reverse() Меняет порядок элементов в списке на противоположный. Все итераторы оста- ются допустимыми и указывают нате же самые элементы. Функция выполняет- ся за линейное время. Обратите внимание, что существует глобальная функция reverse. Если L — это список, то и L. reverse() и reverse(L begin(), L end()) можно использовать для переворачивания списка L. Разница состоит в том, что L reverset) сохраня- ет значение каждого итератора, указывающего в L, но не сохраняет их отноше- ние “предыдущий/последующий”, тогда как reverse(L begin(), L end()) не со- храняет значения, на которые указывают итераторы, но сохраняет отношение “предыдущий/последующий”. Кроме того, алгоритм reverse использует опера- тор присваивания Т, а функция-член reverse — нет. ♦ void list::sort() Сортирует *this в соответствии с operator^ Сортировка является стабильной. Относительный порядок одинаковых элементов остается неизменным Все ите- раторы остаются допустимыми и продолжают указывать на те же элементы. Количество сравнений примерно равно TV log М где N — размер списка.
442 Глава 16. Классы контейнеров ♦ template «class StrictWeakOrdering> void list::sort(StrictWeakOrdering Comp) Сортирует *this в соответствии co Строгим Слабым Упорядочением Comp. СорТи. ровка является стабильной. Относительный порядок эквивалентных элементов сохраняется. Все итераторы остаются допустимыми и продолжают указывать на те же элементы. Количество сравнений примерно равно Mog N, где 2V — Дли на списка. bool operator==(const list&, const list&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const list&, const list&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.1.3. slist (односвязный список) slist<T, Allocator Шаблон slist — это односвязный список, в котором каждый элемент связан с после- дующим элементом, но не связан с предыдущим. Таким образом, slist представляет собой Последовательность, которая поддерживает проходы в прямом (но не в обрат- ном) направлении и обеспечивает константное амортизированное время вставки и удаления элементов. Односвязные списки знакомы нам по таким языкам програм- мирования, как Common LISP, Scheme и ML. В некоторых языках почти все структу- ры данных представлены в виде односвязных списков. Как и у list, у slist есть важное свойство: вставка и слияние не приводит к потере допустимости итераторов, указывающих на элементы списка. Даже функция erase (стирание) делает недопустимыми только те итераторы, которые указывают на уда- ляемые элементы. Относительный порядок итераторов может измениться (то есть после операции над списком у slist<T>: iterator может оказаться другой предше- ственник или преемник), но сами итераторы не станут недопустимыми и не начнут указывать на другие элементы, если только изменение значения итератора не было произведено явно. Основное различие slist и list состоит в том, что итераторы list являются Дву- направленными Итераторами, а итераторы slist — Однонаправленными Итераторами. Это означает, что slist менее гибок, чем list. Однако довольно часто дополнитель- ная функциональность Двунаправленных Итераторов оказывается невостребованной* В случае, когда вам не нужна дополнительная функциональность, следует пользо- ваться slist, потому что односвязные списки занимают меньше памяти и работают быстрее, чем двусвязные. Обратите внимание на одно серьезное предостережение. Как и у всех других По- следовательностей, у slist есть функции-члены insert и erase, бездумное использо- вание которых может привести к чрезвычайно медленной работе программ. Пробле- ма состоит в том, что первым аргументом insert (вставка) является итератор PoS> а вставка нового элемента осуществляется перед pos, а не после. Это означает, что сначала insert ищет итератор, предшествующий pos. Такая операция выполняется за константное время в случае list, потому что у него Двунаправленные Итераторы, но
16.1. Последовательности 443 з случае slist нужно пройтись по всему списку от начала. Другими словами, вставка удаление выполняются достаточно быстро только вблизи начала slist. Класс slist предоставляет функции-члены insert_after и erase_after, которые вы- полняются за константное время и которые, если это возможно, стоит предпочесть функциям insert и delete. Если же insert_after и erase_after вас не удовлетворяют И часто приходится обращаться к insert и erase, то, вероятно, следует воспользовать- ся шаблоном list. Пример Создается slist и в него вставляется несколько элементов. int main() sl]st<int> L, L push_front(0), L push_front(1), L insert_after(L begin(), 2), copy(L begin(), L end(), ostream_iterator<int>(cout, " ”)), cout << endl, } // Результат 1 2 0 Иногда удобно создавать список поэлементно, для чего в slist используется pusn_f ront. (Функции-члена push_back нет, потому что slist является моделью Пос- ледовательности с Начальной Вставкой, но не моделью Последовательности с Конце- вой Вставкой.) Таким образом, элементы в slist будут находиться в порядке, проти- воположном тому, в котором их туда добавляли. Если вы хотите, чтобы элементы стояли в списке в порядке их добавления, у вас есть две возможности. Во-первых, после того как добавлены все элементы можно вос- пользоваться функцией-членом reverse. Во-вторых, существует один трюк, часто при- меняемый в программах на LISP. Можно завести итератор, указывающий на послед- ний элемент. В следующем примере показано, как это делается. int main() { slist<double> L, double x = 1; L push_front(x), slist<double> iterator back - L.begin(), while (x < 1000000 ) back = L insert_after(back, x *=2), copy(L begin(), L end(), ostream_iterator<double>(cout, "\n”)), } // Результат числа, равные степени двойки, от 1 до 1048576 Где определено Шаблон slist не входит в реализацию HP и стандарт C++, но является их расшире- нием. В реализации SGI он определен в заголовке <slist>.
444 Глава 16. Классы контейнеров Параметры шаблона Т Тип значения siist: тип объекта, хранящегося в slist. Allocator Аллокатор slist, используемый для управления памятью По умолчанию: allocator<T> Является моделью Последовательности с Начальной Вставкой (с. 153) Требования к типам • Т — модель Присваиваемого. • Allocator — модель Аллокатора, тип значения которого равен Т. Открытые базовые классы Отсутствуют. Члены Некоторые члены slist не определены в требованиях к Последовательности с На- чальной Вставкой, а специфичны для slist; они помечены символом ♦ . slist::value_type Тип объекта, хранящегося в slist: Т. (Описан в Контейнере.) slist::pointer Указатель на Т. (Описан в Контейнере.) slist::const_pointer Указатель на const Т. (Описан в Контейнере.) slist::reference Ссылка на Т. (Описан в Контейнере.) slist::const_reference Ссылка на const Т. (Описан в Контейнере.) slist::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) slist::difference_type Целочисленный тип со знаком, обычно pt rd if f_t. (Описан в Контейнере.) slist::iterator Тип итератора slist, являющийся переменным Однонаправленным Итераторов (Описан в Контейнере.)
16.1. Последовательности 445 slist.:const-iterator Константный тип итератора slist, являющийся константным Однонаправлен- ным Итератором. (Описан в Контейнере.) slist::begin() Возвращает iterator, указывающий на начало slist. (Описан в Контейнере.) slist::end() Возвращает iterator, указывающий на конец slist. (Описан в Контейнере.) slist::begin() const Возвращает const-iterator, указывающий на начало slist. (Описан в Контейнере.) slist::end() const Возвращает const-iterator, указывающий на конец slist. (Описан в Контейнере.) size.type slist::size() const Возвращает количество элементов в slist. При этом требуется только, чтобы время выполнения size() было равно O(N), а не 0(1). (Описан в Контейнере.) size_type slist::maX-Size() const Возвращает максимально допустимый размер slist. (Описан в Контейнере.) bool slist::empty() const Возвращает true тогда и только тогда, когда размер slist равен нулю. (Описан в Контейнере.) explicit slist:: slist(const Allocators A = AllocatorO) Создает пустой slist, используя заданный аллокатор. (Описан в Последовательности.) explicit slist::slist(size-type n, const TS x = T(). const Allocator& A = AllocatorO) Создает slist, содержащий n копий x и использующий заданный! аллокатор (Описан в Последовательности.) slist::slist(const slist& 1) Конструктор копирования. (Описан в Контейнере.) template <class Inputlterator> slist::slist(lnputlterator f, Inputiterator 1, const Allocators A = AllocatorO) Создает slist, содержащий! копию диапазона f f, 1) и использующий заданный аллокатор (Описан в Последовательности.)
446 Глава 16. Классы контейнеров slist:: -slistO Деструктор. (Описан в Контейнере.) slist& operator=(const slist&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type slist::get_allocator() const Возвращает копию аллокатора, с которым был создан slist. void slist::swap(slist&) Обменивает значения двух объектов типа slist. (Описан в Контейнере.) reference slist::front() Возвращает первый элемент. (Описан в Последовательности.) const_reference slist::front() const Возвращает первый элемент. (Описан в Последовательности.) void slist::push.front(const T& t) Вставляет t в начало. (Описан в Последовательности с Начальной Вставкой.) void slist::pop_front() Удаляет первый элемент. (Описан в Последовательности с Начальной Вставкой.) ♦ iterator previous(iterateг pos) Возвращает итератор, предшествующий итератору pos. Аргумент pos должен быть допустимым итератором в *this, а возвращаемое значение является таким ите- ратором prev, что ++prev == pos. Оценка сложности линейна по количеству ите- раторов, предшествующих pos. Эта функция-член отражает существенную разницу между list и slist. Она выполняется за линейное, а не за константное время, поскольку slist предо- ставляет Однонаправленные Итераторы, а не Двунаправленные Итераторы. Функ- ции-члены типа insert и erase выполняются за линейное время, поскольку они должны вызывать функцию previous. ♦ const-iterator previous(const-iterator pos) Возвращает итератор, предшествующий итератору pos. Аргумент pos должен быть допустимым итератором в *this, а возвращаемое значение — таким итератором prev, что ++prev == pos. Оценка сложности линейна по количеству итераторов, предшествующих pos. iterator slist::insert(iterator pos, const T& t) Вставляет t перед pos. Ни один итератор не принимает недопустимого значения-
16.1 .Последовательности 447 (Описан в Последовательности.) template <class Inputlterator> void slist::insert(iterator pos, Inputiterator f, Inputiterator 1) Вставляет диапазон [f, 1.) перед pos. Ни один итератор не принимает недопусти- мого значения. (Описан в Последовательности.) void slist::insert(iterator pos, size_type n, const T& x) Вставляет n копий x перед pos. Ни один итератор не принимает недопустимого значения. (Описан в Последовательности.) iterator slist::erase(iterator pos) Удаляет элемент, на который указывает pos. Ни один итератор, кроме pos, не принимает недопустимого значения. (Описан в Последовательности.) iterator slist::erase(iterator first, iterator last) Удаляет все элементы в диапазоне [f i rst, last). Ни один итератор, кроме указы- вающих на удаленные элементы, не принимает недопустимого значения. (Описан в Последовательности.) void slist::clear() Стирает все элементы slist. (Описан в Последовательности.) void slist:: resize(size_type n, const T& t = TO) Изменяет размер slist на n. (Описан в Последовательности.) ♦ template <class Inputlterator> void slist::assign(lnputlterator first, Inputiterator last) Эквивалентно стиранию всех элементов в * this и замене их элементами из диа- пазона [first, last). ♦ void slist::assign(size_type n, const T& x) Эквивалентно стиранию всех элементов в *this и замене их п копиями х. ♦ iterator slist::insert_after(iterator pos, const T& t) Вставляет t после pos, который должен быть итератором, разыменуемым в *this. то есть pqs не должен равняться end(). Возвращаемое значение — итератор, ука- зывающий на новый элемент. Ни один итератор не принимает недопустимого значения. Оценка сложности: 0(1). ♦ template <class Inputlterator> void slist::insert_after(iterator pos, Inputiterator f, Inputiterator 1) Вставляет элементы из диапазона [f, 1) в *this сразу после pos. Ни один итера- тор не принимает недопустимого значения. Оценка сложности линейна по last - first.
448 Глава 16. Классы контейнеров ♦ void slist::insert_after(iterator pos, size_type n, const T& x) Вставляет n копий x сразу после pos. Ни один итератор не принимает недопуСТи мого значения. Оценка сложности линейна по п. ♦ iterator slist::erase_after(iterator pos) Удаляет элемент, на который указывает итератор, следующий за dos. Оценка слож- ности О( 1). ♦ iterator slist::erase_after(iterator before_first, iterator last) Удаляет все элементы из диапазона [before_first+1, last). Оценка сложности линейна по количеству элементов в этом диапазоне. ♦ void slist::splice(iterator pos, slist& x) Удаляет все элементы x и вставляет их перед pos. Итератор pos должен быть допустимым итератором в *this, а х должен отличаться от *this. Все итераторы остаются допустимыми, включая те, которые указывают на элементы х. Оценка сложности линейна по количеству элементов перед pos. ♦ void slist::splice(iterator pos, slist& x, iterator i) Перемещает элемент, на который указывает i из х в *this, при этом вставляя его перед pos. (Списки х и *this не обязаны быть различными списками.) Итератор pos должен быть допустимым итератором в *this, ai — итератором, разыменуе- мым в х. Все итераторы остаются допустимыми, включая те, что указывают на элементы х. Если pos == 1 или pos == ++1, эта функция не выполняет никаких действий. Оценка сложности пропорциональна q (pos - begin()) + c2(i - х begin О), где с1 и с2 — некоторые константы. ♦ void slist::splice(iterator pos, slist& x, iterator f, iterator 1) Перемещает элементы диапазона [f, 1) из x в *this, вставляя их непосредствен- но перед pos. Итератор pos должен быть допустимым оператором B*this.a[f,l) — допустимым диапазоном в х. Не требуется, чтобы *this и х различались, ио dos не должен быть итератором в [f, 1). Все итераторы остаются допустимыми, вклю- чая те, которые указывают на элементы х. Оценка сложности пропорциональна с} (pos - begin()) + с2(i - х begin()) + с3(1 - f ), где cv с2 и с3— некоторые константы ♦ void slist::splice_after(iterator pos, iterator prev) Перемещает элемент, следующий за prev в *this, вставляя его при этом непо- средственно после pos. Итератор pos должен быть разыменуемым B*this,aprev- разыменуемым либо в *this, либо в некотором другом объекте slist. (Посколь- ку требуется, чтобы оба итератора pos и prev были разыменуемыми, ни один из них не может равняться end().) Оценка сложности: 0(1). ♦ void slist::splice_after(iterator pos, iterator before_first, iterator before_last) Перемещает диапазон [before_fi rst+1, before_last+1) в *this, вставляя его не- посредственно после pos. Итератор pos должен быть разыменуемым в *tnis. a befcre_first и before_last — разыменуемыми либо в *this, либо в некотором другом объекте slist. Оценка сложности: 0(1).
16.1. Последовательности 449 ♦ void slist::remove(const T& val) Удаляет все элементы, равные val. Относительный порядок оставшихся элемен- тов не меняется; итераторы на оставшиеся элементы остаются допустимыми. Оценка сложности этой функции равна O(N). Выполняется ровно size() срав- нений на равенство. ♦ template <class Predicate> void slist::remove_if(Predicate p) Удаляет все элементы i, такие что р( *i) возвращает значение t rue. Относитель- ный порядок оставшихся элементов не меняется; итераторы на оставшиеся эле- менты остаются допустимыми. Оценка сложности этой функции равна O(N). Выполняется ровно size() обращений к предикату р. ♦void slist::unique() Удаляет все элементы, кроме первого, в каждой последовательной группе рав- ных элементов. Относительный порядок оставшихся элементов не меняется; ите- раторы на оставшиеся элементы остаются допустимыми. Оценка сложности этой функции равна O(N). Выполняется ровно size() - 1 сравнений на равенство. ♦ template <class BinaryPredicate> void slist::unique(BinaryPredicate p) Удаляет все элементы, кроме первого, в каждой последовательной группе рав- ных элементов, где два элемента 1 и j считаются равными, если бинарный пре- дикат р( * 1, *j) возвращает значение t rue. Относительный порядок оставшихся элементов не меняется; итераторы на оставшиеся элементы остаются допусти- мыми. Сложность этой функции равна O(N). Функция выполняет ровно size() - 1 сравнений на равенство. ♦ void slist::merge(slist& х) Сливает два отсортированных списка, удаляя при этом все элементы х и встав- ляя HXB*this. *this и х должны быть различными списками, отсортированными с помощью operator^ Операция слияния стабильна. Если элемент из *this ра- вен какому-либо элементу из х, то элемент из *this будет помещен перед эле- ментом из х. Все итераторы, указывающие на элементы *this и х, остаются до- пустимыми. Оценка сложности этой функции равна O(N): выполняется не более size() + х size() - 1 сравнений. ♦ template <class StrictWeakOrdering> void slist::merge(slist& x, StrictWeakOrdering Comp) Сливает два отсортированных списка, удаляя при этом все элементы х и встав- ляя их в *this, используя отношение упорядочения Comp. Функция сравнения Comp должна быть Строгим Слабым Упорядочением, аргумент которого имеет тип Т, а списки *this и х должны быть различными списками, отсортированными в соответствии с этой функцией. Операция слияния является стабильной. Если элемент из *this равен элементу из х, то элемент из *this будет помещен перед элементом из х. Все итераторы, указывающие на элементы *this и х, остаются допустимыми. Оценка сложности этой функции равна O(N): выполняется не более size() + х size() - 1 применений Comp.
450 Глава 16. Классы контейнеров ♦ void slist::reverse() Меняет порядок элементов в списке на противоположный. Все итераторы оста- ются допустимыми и указывают на те же самые элементы. Эта функция выпол- няется за время O(N). ♦ void slist::sort() Сортирует *this в соответствии с operators Операция сортировки стабильна. Относительный порядок одинаковых элементов остается неизменным. Все ите- раторы остаются допустимыми и продолжают указывать на те же элементы. Количество сравнений примерно равно N logM где N— размер списка. ♦ template <class StrictWeakOrdering> void slist::sort(StrictWeakOrdering Comp) Сортирует *this в соответствии co Строгим Слабым Упорядочением Comp. Опера- ция сортировки стабильна. Относительный порядок эквивалентных элементов остается неизменным. Все итераторы остаются допустимыми и продолжают ука- зывать на те же элементы. Количество сравнений примерно равно N logN, где У — размер списка. bool operator==(const slist&, const slist&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const slist&, const slist&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.1.4. deque (двусторонняя очередь) deque<T, AllocatoO Шаблон deque очень похож на vector (с. 427). Как и vector, deque является Последо- вательностью, которая обеспечивает произвольный доступ к своим элементам, кон- стантное время вставки и удаления элементов на концах и линейное время вставки и удаления элементов в начале или середине. Главное отличие deque от vector состоит в том, что deque обеспечивает константное время вставки и удаления элементов в начале последовательности. Вставка элемента в начале или конце deque занимает константное амортизированное время, а вставка элемента в середину выполняется за время, линейное по п, где п — минимальное из расстояний до начала и до конца. Еще одно отличие состоит в том, что у шаблона deque нет функций-членов, анало- гичных capacity() и reserve() шаблона vector, и он не предоставляет гарантий допу- стимости итераторов, связанных с этими функциями-членами. Наоборот, гарантиру- ется, что вставка элементов в начало или конец deque не приводит к копированию ни одного из существующих элементов. В deque нет операций, аналогичных реаллока- ции шаблона vector. ) Название deque произносится по-ain лийски “дек” и означает “double-ended queue” — “очередь с Д»У мя концами” Кну г утверждает, что название придумал Е Дж Швеппе (Е J Schweppe) Дополни Ю.’П» н\ю информацию можно найти у Кнута в разделе 2 2 1 |Knu97|.
16.1. Последовательности 451 Как правило, вставка (включая push_f ront и push_back) делает недопустимыми все итераторы, указывающие в deque, удаление в середине также приводит к недопусти- мости всех итераторов, а удаление в начале или конце (включая pop_f ront и pop_back) делает недопустимыми только те итераторы, которые указывают на удаляемые эле- менты. Обычно deque реализуется в виде динамически сегментированного массива и со- стоит из заголовка, указывающего на набор узлов, которые содержат фиксирован- ный набор хранящихся в непрерывной памяти элементов. В deque могут добавляться новые узлы. Внутренние детали deque не слишком интересны. Тем не менее очень важно по- нять, что deque представляет собой гораздо более сложную структуру данных, чем vector. Инкремент итератора deque не может быть простым инкрементом указателя; при этом должна быть выполнена по крайней мере одна операция сравнения на ра- венство. Поэтому, несмотря на то что и vector, и deque предоставляют Итераторы Про- извольного Доступа, следует ожидать, что любая операция с итератором deque будет намного медленнее, чем аналогичная операция с итератором vector. Если вам не требуются специфические возможности, предоставляемые только клас- сом deque (например, константное время вставки в начало или конец), лучше пользо- ваться шаблоном vector. Аналогично, сортировка в deque — отнюдь не блестящая мысль. Следует скопировать элементы deque в vector, там их отсортировать, а затем скопировать обратно — результат будет достигнут быстрее. Пример int main() { deque<int> Q. 0 push_back(3), 0 push_front(1). Q insert(Q begin() + 1, 2), 0[2] = 0. copy(Q begin(), 0 end(), ostream_iterator<int>(cout. ‘ ")), } // Результат 1 2 0 Где определено В реализации HP deque определен в заголовке <deque h>. В соответствии со стандар- том C++ он объявлен в заголовке <deque>. Параметры шаблона Т Тип значения deque: тип объекта, хранящегося в deque. Allocator Аллокатор deque, используемый для управления памятью. По умолчанию: allocateг<Т> Требования к типам • Т — модель Присваиваемого. • Allocate г — модель Аллокатора, тип значения которого равен Т.
452 Глава 16. Классы контейнеров Является моделью Контейнера Произвольного Доступа (с. 146), Последовательности с Начальной Встав- кой (с. 153), Последовательности с Концевой Вставкой (с. 155). Члены Одна функция-член deque не определена в требованиях к Контейнеру Произвольного Доступа, Последовательности с Начальной Вставкой и Последовательности с Конце- вой Вставкой, а специфична для deque; она помечена символом ♦. deque::value_type Тип объекта, хранящегося в deque: Т. (Описан в Контейнере.) deque::pointer Указатель на Т. (Описан в Контейнере.) deque::const_pointer Указатель на const Т. (Описан в Контейнере.) deque::reference Ссылка на Т. (Описан в Контейнере.) deque::const_reference Ссылка на const Т. (Описан в Контейнере.) deque::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) deque::difference_type Целочисленный тип со знаком, обычно pt rdiff_t. (Описан в Контейнере.) deque::iterator Тип итератора deque, являющийся переменным Итератором Произвольного Доступа. (Описан в Контейнере.) deque::const_iterator Константный тип итератора deque, являющийся константным Итератором Про- извольного Доступа. (Описан в Контейнере.) deque::reverse_iterator Итератор, используемый для перемещения по deque в обратном направлении, являющийся переменным Итератором Произвольного Доступа. (Описан в Реверсивном Контейнере.)
16.1. Последовательности 453 deque::const_reverse_iterator Итератор, используемый для перемещения по deque в обратном направлении, являющийся константным Итератором Произвольного Доступа. (Описан в Реверсивном Контейнере.) deque::begin() Возвращает iterator, указывающий на начало deque. (Описан в Контейнере.) deque::end() Возвращает iterator, указывающий на конец deque. (Описан в Контейнере.) deque::begin() const Возвращает const-iterator, указывающий на начало deque. (Описан в Контейнере.) deque::end() const Возвращает const-iterator, указывающий на конец deque. (Описан в Контейнере.) deque:: rbeginO Возвращает reverse_iterator, указывающий на начало реверсивного контейне- ра deque. (Описан в Реверсивном Контейнере.) deque::rend() Возвращает reverse_iterator, указывающий на конец реверсивного контейнера deque. (Описан в Реверсивном Контейнере.) deque:: rbeginO const Возвращает const_reverse_iterator, указывающий на начало реверсивного кон- тейнера deque. (Описан в Реверсивном Контейнере.) deque::rend() const Возвращает const_reverse_iterator, указывающий на конец реверсивного кон- тейнера deque. (Описан в Реверсивном Контейнере.) size_type deque::size() const Возвращает количество элементов в deque. (Описан в Контейнере.) size_type deque: :max__size() const Возвращает максимально допустимый размер deque. (Описан в Контейнере.)
454 Глава 16. Классы контейнеров bool deque::empty() const Возвращает истинность того, что размер deque равен нулю. (Описан в Контейнере.) reference deque::operator[](size_type n) Возвращает n-й элемент. (Описан в Контейнере Произвольного Доступа.) const_reference deque::operater[](size_type n) const Возвращает n-й элемент. (Описан в Контейнере Произвольного Доступа.) explicit deque: :deque(const Allocator& A = AllocatorO) Создает пустой deque, использующий заданный аллокатор. (Описан в Последовательности.) explicit deque::deque(size_tyре n, const T& x = T(), const Allocator& A = AllocatorO) Создает deque, содержащий n копий элемента x и использующий заданный алло- катор. (Описан в Последовательности.) deque::deque(const deque& d) Конструктор копирования. (Описан в Контейнере.) template <class Inputlterator> deque::deque(Inputiterator f, Inputiterator 1, const Allocator& A = AllocatorO) Создает deque, содержащий копию диапазона [f, 1) и использующий заданный аллокатор. (Описан в Последовательности.) deque::-deque() Деструктор. (Описан в Контейнере.) deque& operator=(const deque&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type deque::get_allocator() const Возвращает копию аллокатора, с которым был создан deque. void deque::swap(deque&) Обменивает значения двух объектов типа deque. (Описан в Контейнере.) reference deque::front() Возвращает первый элемент. (Описан в Последовательности.)
16.1. Последовательности 455 const_reference deque::front() const Возвращает первый элемент. (Описан в Последовательности.) reference deque::back() Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.) const_reference deque::back() const Возвращает последний элемент. (Описан в Контейнере с Концевой Вставкой.) void deque::push_front(const T& t) Вставляет элемент t в начало deque. (Описан в Контейнере с Начальной Вставкой.) void deque::pop_front() Удаляет первый элемент. (Описан в Контейнере с Начальной Вставкой.) void deque::push_back(const T& t) Добавляет t в конец deque. (Описан в Контейнере с Концевой Вставкой.) void deque::pop_back() Удаляет последний элемент. (Описан в Контейнере с Концевой Вставкой.) iterator deque::insert(iterator pos, const T& t) Вставляет t перед pos. (Описан в Последовательности.) template <class Inputlterator> void deque::insert(iterator pos, Inputiterator f, Inputiterator 1) Вставляет диапазон [f, 1) перед pos. (Описан в Последовательности.) void deque::insert(iterator pos, size.type n, const T& x) Вставляет n копий x перед pos. (Описан в Последовательности.) iterator deque::erase(iterator pos) Удаляет элемент, на который указывает pos. (Описан в Последовательности.) iterator deque::erase(iterator first, iterator last) Удаляет элементы диапазона [f i rst, last). (Описан в Последовательности.) void deque::clear() Удаляет все элементы deque. (Описан в Последовательности.)
456 Глава 16. Классы контейнеров void deque:: resize(size_type n, const T& t = TO) Изменяет размер deque на n. (Описан в Последовательности.) ♦ template <class Inputlterator> void deque::assign(lnputlterator first, Inputiterator last) Эквивалентно удалению всех элементов в *this и замене их элементами диапа- зона [first, last). ♦ void deque::assign(size_type n, const T& x) Эквивалентно удалению всех элементов в *this и замене их п копиями х. bool operator==(const deque&, const deque&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const deque&, const deque&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.2. Ассоциативные контейнеры В стандарте C++ имеется четыре Ассоциативных Контейнера: set, map, multiset и multimap. Все четыре класса являются моделями Сортированного Ассоциативного Контейнера. В стандарте отсутствует Хешированный Ассоциативный Контейнер, но в его расши- рение обычно входят хеш-таблицы. Одна из ранних версий хеш-таблиц STL была написана Хавьером Баррейро (Javier Barreiro) и Дэвидом Мюссером (David Musser), а другая — Бобом Фрэйли (Bob Fraley). Они описаны в издании Хеш-таблицы для Стандартной Библиотеки Шаблонов [BFM95]. В настоящее время наиболее широко используемой хеш-таблицей STL является версия, включенная в реализацию SGI STL. В этом разделе описываются четыре Сор- тированных Ассоциативных Контейнера, определенные в стандарте C++, и четыре Хе- шированных Ассоциативных Контейнера из реализации SGI STL. 16.2.1. set (множество) set<Key, Compare, Allocater> Шаблон set представляет собой Сортированный Ассоциативный Контейнер, который хранит объекты типа Key. Это Простой Ассоциативный Контейнер, и Key является как типом его значения, так и типом его ключа. Он также является Уникальным Ассоциа- тивным Контейнером, то есть в нем не бывает двух одинаковых элементов. Так как set — Сортированный Ассоциативный Контейнер, он содержит в себе эле- менты в порядке возрастания. Отношение упорядочения определяется параметром шаблона Compare. Шаблоны set и multiset особенно хорошо подходят для обобщенных алгоритмов над множествами (set_union, set_intersection и т. д.), поскольку, во-первых, их аргу-
16.2. Ассоциативные контейнеры 457 . - рентами должны быть отсортированные диапазоны, а требования к Сортированному дссоциативному Контейнеру гарантируют, что set и multiset удовлетворяютданному ограничению; во-вторых, результирующие диапазоны алгоритмов тоже всегда отсор- тированы, и вставка отсортированного диапазона в set или multiset — быстрая опе- рация. Требования к Сортированному Ассоциативному Контейнеру гарантируют, что если диапазон уже отсортирован, то для вставки диапазона необходимо лишь линей- ное время. Адаптер insert_iterator предоставляет особенно удобный способ вставки результата работы одного из этих алгоритмов в set. Как и у list (с. 434), у set есть важное свойство: вставка нового элемента не делает недопустимыми итераторы, указывающие на существующие элементы. Стирание эле- мента из set также не делает недопустимыми никакие итераторы, за исключением тех, которые указывают на сам удаляемый элемент. Пример struct Itstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0, } }. int main() { const int N = 6, const char* a[N] = {’’isomer", "ephemeral", "prosaic", "nugatory", "artichoke", "serif”}; const char* b[N] = {"flat", "this", "artichoke", "frigate" "prosaic”, "isomer"}, set<const char*, ltstr> A(a, a + N), set<const char*, ltstr> B(b, b + N), set<const char*, ltstr> C, cout « "Set A ", copy(A begin(), A end(), ostream_iterator<const char*>(cout, ” ")), cout « endl, cout « "Set В ", copy(B begin(), В end(), ostream_iterator<const char*>(cout, " ")), cout «endl, cout << "Union ", set_union(A begin(), A end(), В begin(), В end(), ostream_iterator<const char*>(cout, ’ ’’), ltstr()), cout << endl, cout « «Intersection
458 Глава 16. Классы контейнеров ъе1_иИегьес1юп(А beyin(), A end(), В begin(), В end(). ostream_iterator<const char*>(cout, " "). ItstrQ). cout << endl. set.difference(A begin(), A end(), В begin(). В end(), inserter(C, C.beginO), ltstr()), cout « »Set C (difference of A and B) copy(C begin(). C end(), ostream_iterator<const char*>(cout, ” ")), cout << endl, } Где определено В реализации HP set определен в заголовке <set h>. В соответствии со стандартом О+ он объявлен в заголовке <set>. Параметры шаблона Key Тип ключа и тип значения set. Этот тип также определен в виде вложенных типов set:: key_type и set: : value_type. Compa re Функция сравнения ключей в соответствии со Строгим Сла- бым Упорядочением (с. 132), типом аргумента которой явля- ется key_type. Она возвращает true, если ее первый аргумент меньше второго, и false в противном случае. Она также опре- делена как set: : кеу_сотрагеи set::value_compare. По умолчанию: less<Key> Allocator Аллокатор set, используемый для управления памятью. По умолчанию: allocator<Key> Является моделью Сортированного Ассоциативного Контейнера (с. 167), Простого Ассоциативного Кон- тейнера (с. 164), Уникального Ассоциативного Контейнера (с. 161). Требования к типам • Key — модель Присваиваемого. • Compa re — модель Строгого Слабого Упорядочения. • Типом значения Compare является Key. • Allocator — модель Аллокатора, тип значения которого равен Key. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов set не определена в требованиях к Сортированному Ассоци- ативному Контейнеру, Простому Ассоциативному Контейнеру и Уникальному Ассоциа- тивному Контейнеру, а специфична для set; она помечена символом ♦.
16.2. Ассоциативные контейнеры 459 set::value_type Тип объекта, хранящегося в set. Типом значения является Key. (Описан в Контейнере.) set::key_type Тип ключа, ассоциированного с объектом типа value_type. Типом ключа явля- ется Key. (Описан в Ассоциативном Контейнере.) set::key_compare Функциональный объект (Строгое Слабое Упорядочение), сравнивающий два ключа при упорядочивании. (Описан в Сортированном Ассоциативном Контейнере.) set::value_compare Функциональный объект, сравнивающий два значения при упорядочивании. (Тот же тип, что и key_compare.) (Описан в Сортированном Ассоциативном Контейнере.) set::pointer Указатель на Key. (Описан в Контейнере.) set::const_pointer Указатель на const Key. (Описан в Контейнере.) set::reference Ссылка на Key. (Описана в Контейнере.) set::const_reference Ссылка на const Key. (Описана в Контейнере.) set::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) set::difference_type Целочисленный тип со знаком, обычно pt rdi f f _t. (Описан в Контейнере.) set::iterator Тип итератора set: константный Двунаправленный Итератор. (У шаблона set нет изменяемого типа итератора. Элементы можно вставлять в set или удалять от- туда, но нельзя изменить непосредственно в месте их хранения ) (Описан в Контейнере.)
460 Глава 16. Классы контейнеров set::const-iterator Константный тип итератора set, являющийся константным Двунаправленным Итератором. (Тип, возможно, совпадает с iterator.) (Описан в Контейнере.) set::reverse_iterator Итератор, используемый для перемещения по set в обратном направлении и яв- ляющийся константным Двунаправленным Итератором. (Описан в Реверсивном Контейнере.) set::const_reverse_iterator Итератор, используемый для перемещения по set в обратном направлении и так- же являющийся константным Двунаправленным Итератором. (Описан в Реверсивном Контейнере.) iterator set::begin() const Возвращает iterator, указывающий на начало set. (Описан в Контейнере.) iterator set::end() const Возвращает iterator, указывающий на конец set. (Описан в Контейнере.) revers_iterator set::rbegin() const Возвращает reverse_iterator, указывающий на начало реверсивного set. (Описан в Реверсивном Контейнере.) revers_iterator set::rendО const Возвращает reverse_iterator, указывающий на конец реверсивного set. (Описан в Реверсивном Контейнере.) size_type set::size() const Возвращает количество элементов в set. (Описан в Контейнере.) size.type set::max_size() const Возвращает максимально допустимый размер set. (Описан в Контейнере.) bool set::empty() const Возвращает истинность того, что размер set равен нулю. (Описан в Контейнере.) key_compare set::key_comp() const Возвращает объект типа key_compa re (сравнение ключей), используемый объектом set. (Описан в Сортированном Ассоциативном Контейнере.) value_compare set::value_comp() const Возвращает объект типа value_compare (сравнение значений), используемый объектом set. (Описан в Сортированном Ассоциативном Контейнере.)
16.2. Ассоциативные контейнеры 461 explicit set::set(const key_compare& comp = key_compare(), const Allocater& A = AllocatorO) Создает пустой set, использующий заданный функциональный объект сравне- ния ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.) template <class Inputlterator> set::set(lnputlterator f, Inputiterator 1, const key_compare& comp = key_compare(), const Allocater& A = AllocatorO) Создает set, содержащий копию диапазона [f, 1) и использующий заданный функ- циональный объект сравнения ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.) set::set(const set&) Конструктор копирования. (Описан в Контейнере.) set::-set() Деструктор. (Описан в Контейнере.) set& set::operator=(const set&) Оператор присваивания. (Описан в Контейнере.) ♦ allocater_type set::get_allocator() const Возвращает копию аллокатора, с которым был создан set. void set::swap(set&) Обменивает значения двух объектов типа set. (Описан в Контейнере.) pairciterator, bool> set::insert(const value_type& х) Вставляет х в set. Вторая часть возвращаемого значения равна истинности того, что х действительно был вставлен в set (он может быть не вставлен, если такой элемент уже имеется). (Описан в Уникальном Ассоциативном Контейнере.) iterator set::insert(iterator pos, const value_type& x) Вставляет x, используя pos в качестве подсказки, куда поместить элемент. (Описан в Сортированном Ассоциативном Контейнере.) template <class Inputlterator> void set::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в set. (Описан в Уникальном Ассоциативном Контейнере.)
462 Глава 16. Классы контейнеров void set::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type set::erase(const key_type& k) Стирает элемент с ключом к (если таковой имеется). Возвращаемое значение равно количеству удаленных элементов — либо единица, либо нуль. (Описан в Ассоциативном Контейнере.) void set::erase(iterator f, iterator 1) Стирает все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator set::find(const key_type& k) const Ищет элемент с ключом k. (Описан в Ассоциативном Контейнере.) size_type set::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. Возвращаемое зна- чение равно либо нулю, либо единице. (Описан в Ассоциативном Контейнере.) iterator set::lower_bound(const key_type& к) const Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) iterator set::upper_bound(const key_type& к) const Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.) pairciterator, iterator> set;:equal_range(const key_type& к) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Сортированном Ассоциативном Контейнере.) bool operator==(const set&, const set&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const set&, const set&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.2.2. шар (отображение, ассоциативная последовательность) map<Key, Т, Compare, Allocateг> Шаблон тар представляет собой Сортированный Ассоциативный Контейнер, который сопоставляет объекты типа Key с объектами типа Т. Это Парный Ассоциативным Контейнер, а именно тип его значения — пара pair<const Key, Т>. Он также является Уникальным Ассоциативным Контейнером, то есть ни у каких двух элементов нет ОД" ного и того же ключа.
16.2. Ассоциативные контейнеры 463 Контейнеры тар и set очень похожи. Различие состоит лишь в том, что set являет- ся Простым Ассоциативным Контейнером (тип его значения совпадает с типом клю- ча), тогда как тар является Парным Ассоциативным Контейнером (тип его значения — это тип ключа и связанный с ним фрагмент данных, вследствие чего set, в отличие от тар, не различает iterator и const_iterator). Как и у list (с. 434), у тар есть важное свойство: вставка нового элемента не делает недопустимыми итераторы, указывающие на существующие элементы. Стирание эле- мента из тар тоже не делает итераторы недопустимыми, за исключением тех, которые указывают на стираемые элементы. Пример Поскольку тар сопоставляет объекты одного типа с объектами другого, можно ис- пользовать его в качестве ассоциативного массива. struct Itstr { bool operator()(const char* s1. const char* s2) const { return strcmp(s1, s2) < 0. } }. int main() { map<const char*, int, ltstr> days, days["january"] = 31, days[”february"] = 28, days["march"] = 31, days[”apnl”] = 30, days["may"] = 31. days["june"] = 30. days["july"] = 31, days["august"] = 31, days[”september"] = 30, daysf"October”] = 31. days[”november”] = 30, days["december"] = 31; cout << "june -> " « days["june"] « endl, map<const char*, int, ltstr> iterator cur = days find("june"), map<const char*, int, ltstr> iterator prev = cur, map<const char*, int, ltstr> iterator next = cur,' <-+next. --prev. cout « ’Previous (in alphabetical order) << (*prev) first « endl, cout « "Next (in alphabetical order) " « (*next) first « endl.
464 Глава 16. Классы контейнеров Когда вы применяете тар как ассоциативный массив, индексирование элементов при помощи operator[] выглядит естественно, поскольку напоминает индексир0Ва ние обыкновенного массива. Важно помнить, что орегатог[ ] — это просто сокращу ная форма записи. Любые возможности, предоставляемые operator!], могут быть реализованы при помощи find и insert. Обратное неверно: в отличие от find, operatorf ] не позволяет определить, производится замена уже существующего значения или вставляется новое. nit main() { map<string, int> M, M insert(make_pair("A” 17)) М insert(make_pair("B", 74)), if (M find( Z“) == M end()) cout « “Not found Z” << endl, // Вставить новый элемент в map pair<map<string, int> .iterator, bool> p = M insert(make_pair("C”, 4)), assert(p second), // Попытаемся вставить новый элемент Он не будет вставлен, // потому что в тар уже имеется элемент с ключом В р = М insert(make_pair(”B", 3)), assert(’p second); // Изменить значение, связанное с В cout « “Value associated with В " « p.first->second « endl, p first->second = 7, cout « "Value associated with B* ” « p first->second << endl, } Где определено В реализации HP map определен в заголовке <map. h>. В соответствии со стандартом C++ он объявлен в заголовке <тар>. Параметры шаблона Key Т Тип ключа тар. Он также определен как map: ' key_type. Тип отображаемого значения тар. Он также определен как mapped_type. Compare Функция сравнения ключей соответствует Строгому Слабо- му Упорядочению (с. 132), типом аргумента key_type. Она возвращает t rue, если ее первый аргумент меньше второго, и false в противном случае. Она также определена в виде вложенного типа тар • . key_compare. По умолчанию: less<Key> Allocator Аллокатор тар, используемый для управления памятью. По умолчанию: allocater<pair<const Key, Т> >
16.2. Ассоциативные контейнеры 465 Является моделью СОртиР°ванного Ассоциативного Контейнера (с. 167), Парного Ассоциативного Кон- тейнера (с. 166), Уникального Ассоциативного Контейнера (с. 161). Требования к типам • Key — модель Присваиваемого. • Т — модель Присваиваемого. • Compa ге — модель Строгого Слабого Упорядочения. • Типом значения Compa re является Key. • Allocate г — модель Аллокатора, тип значения которого совпадает с типом значе- ния тар. Открытые базовые классы Отсутствуют. Члены Некоторые функции-члены тар не определены в требованиях к Сортированному Ассоциативному Контейнеру, Парному Ассоциативному Контейнеру и Уникальному Ас- социативному Контейнеру, а специфичны для тар; они помечены символом ♦. тар::key_type Тип ключа тар — Key. (Описан в Ассоциативном Контейнере.) map::mapped_type Тип объекта, ассоциированного с ключом, — Т. (Описан в Парном Ассоциативном Контейнере.) map::value_type Тип объекта, хранящегося в тар. Этот тип — pai r<const Key, Т>. (Описан в Сортированном Ассоциативном Контейнере.) map::key_compare Функциональный объект (Строгое Слабое Упорядочение), сравнивающий два ключа при упорядочивании. (Описан в Сортированном Ассоциативном Контейнере.) map::value_compare Функциональный объект, сравнивающий два значения при упорядочивании. (Эквивалентно извлечению ключей этих значений и их сравнению.) (Описан в Сортированном Ассоциативном Контейнере.) map::pointer Указатель на тар. value_type. (Описан в Контейнере.) map::const_pointer Указатель на const map: value_type. (Описан в Контейнере.)
6 Глава 16. Классы контейнеров map::reference Ссылка на тар - :value_type. (Описана в Контейнере.) тар::const_reference Ссылка на const тар: :value_type. (Описана в Контейнере.) тар::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) map::difference-type Целочисленный тип со знаком, обычно ptrdiff_t. (Описан в Контейнере.) map::iterator Тип итератора тар, являющийся Двунаправленным Итератором. Обратите вни- мание, что map: iterator не переменный итератор, потому что map: :value_type не является Присваиваемым. (Если 1 имеет тип map: .iterator, нельзя писать *1 = х.) Однако же это не совсем константный итератор, потому что он может быть использован для модификации объекта, на который указывает. Можно на- писаты->зесопс1 = х. (Описан в Контейнере.) map::const-iterator Константный тип итератора тар, являющийся константным Двунаправленным Итератором. (Описан в Контейнере.) map::reverse_iterator Итератор, используемый для перемещения по тар в обратном направлении. (Описан в Реверсивном Контейнере.) map::const_reverse_iterator Константный итератор, используемый для перемещения по тар в обратном на- правлении. (Описан в Реверсивном Контейнере.) map::begin() Возвращает iterator, указывающий на начало тар. (Описан в Контейнере.) map::end() Возвращает iterator, указывающий на конец тар. (Описан в Контейнере.) map::begin() const Возвращает const_iterator, указывающий на начало тар. (Описан в Контейнере.)
16.2. Ассоциативные контейнеры 467 map::endO const Возвращает const_iterator, указывающий на конец тар. (Описан в Контейнере.) map::rbegin() Возвращает reverse_iterator, указывающий на начало реверсивного контейне- ра тар. (Описан в Реверсивном Контейнере.) map::rend() Возвращает reverse_iterator, указывающий на конец реверсивного контейне- ра тар. (Описан в Реверсивном Контейнере.) map::rbegin() const Возвращает const_reverse_iterator, указывающий на начало реверсивного кон- тейнера тар. (Описан в Реверсивном Контейнере.) map::rendО const Возвращает const_reverse_iterator, указывающий на конец реверсивного кон- тейнера тар. (Описан в Реверсивном Контейнере.) size_type map::size() const Возвращает количество элементов в тар. (Описан в Контейнере.) size_type map::max_size() const Возвращает максимально допустимый размер тар. (Описан в Контейнере.) bool map::empty() const Возвращает истинность того, что размер тар равен нулю. (Описан в Контейнере.) j key_compare map::key_comp() const Возвращает объект key_compare, используемый map. (Описан в Сортированном Ассоциативном Контейнере.) value_compare map::value_comp() const Возвращает объект value_compare, используемый map. (Описан в Сортированном Ассоциативном Контейнере.) explicit map::map(const key_compare& comp = key_compare(), const Allocator A = AllocatorO) Создает пустой map, использующий заданный функциональный объект сравне- ния ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.)
8 Глава 16. Классы контейнеров template <class Inputlterator> map::map(lnputlterator f, Inputiterator 1, const key_compare& comp = key_compare(), const Allocator A = AllocatorO) Создает map, содержащий копию диапазона [f, 1) и использующий заданный функциональный объект сравнения ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.) map::map(const тар&) Конструктор копирования. (Описан в Контейнере.) тар::~тар() Деструктор. (Описан в Контейнере.) map& тар::operator=(const тар&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type map::get_allocator() const Возвращает копию аллокатора, с которым тар был создан. void тар::swap(map&) Обменивает значения двух объектов типа тар. (Описан в Контейнере.) pair<iterator, bool> map::insert(const value_type& х) Вставляет х в тар. Вторая часть возвращаемого значения равна истинности того, что х был вставлен (элемент не будет вставлен, если пара с таким ключом уже присутствует). (Описан в Уникальном Ассоциативном Контейнере.) iterator map::insert(iterator pos, const value_type& x) Вставляет x, используя pos в качестве подсказки, куда поместить элемент. (Описан в Сортированном Ассоциативном Контейнере.) template <class Inputlterator> void map::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в map. (Описан в Уникальном Ассоциативном Контейнере.) void map::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type map::erase(const key_type& k) Стирает элемент с ключом к (если таковой имеется). Возвращаемое значение равно количеству стертых элементов — либо единица, либо нуль. (Описан в Ассоциативном Контейнере.)
16.2 Ассоциативные контейнеры 469 void map::erase(iterator f, iterator 1) Стирает все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator map::find(const key_type& k) Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) const-iterator map::find(const key_type& к) const Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) size_type map::count(const key_type& к) const Возвращает количество элементов, ключи которых равны к. Возвращаемое зна- чение равно либо нулю, либо единице. (Описан в Ассоциативном Контейнере.) iterator map::lowerj)ound(const key_type& к) Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) const-iterator map::lower_bound(const key_type& к) const Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) iterator map::upper_bound(const key_type& к) Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.) const-iterator map::upperj)ound(const key_type& к) const Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.) pair<iterator, iterator> map::equal_range(const key_type& к) Ищет диапазон, содержащий все элементы, ключи которых эквивалентны к. (Описан в Сортированном Ассоциативном Контейнере.) pair<const_iterator, const_iterator> map::equal-range(const key_type& к) Ищет диапазон, содержащий все элементы, ключи которых эквивалентны к. (Описан в Сортированном Ассоциативном Контейнере.) ♦ mapped-type& map::operator[](const key_type& к) Поскольку map является Уникальным Ассоциативным Контейнером, в нем содер- жится максимум один элемент с ключом к. Эта функция-член возвращает ссыл- ку на объект, соответствующий ключу к. Если тар еще не содержит такого объек- та, operate г[ ] вставляет значение объекта mapped_type() по умолчанию. (Обра- тите внимание, что возвращаемый тип — mapped_type&, а не value_type&. То есть operator[] возвращает не ссылку на один из элементов тар, а ссылку на часть элемента.)
470 Глава 16. Классы контейнеров Отсутствие const-версии operator! ] не является результатом недосмотра. Воп- рос состоит в том, что должен делать оператор т[ к], если т не содержит элемента с ключом к. Так как в этом случае operator[] вставляет новый элемент, то его нельзя определить как const. Строго говоря, operateг[ ] избыточен. Он не более чем просто сокращенная фор- мазаписи. Выражениеm[k]эквивалентноm.insert(value_type(k, mapped_type())) first->second bool operator==(const map&, const map&) Сравнивает два объекта map на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const map&, const map&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.2.3. multiset (мультимножество, множество с повторяющимися элементами) multiset<Key, Compare, Allocater> Шаблон multiset представляет собой Сортированный Ассоциативный Контейнер, хра- нящий объекты типа Key. Он является Простым Ассоциативным Контейнером, то есть тип его значения, как и тип ключа, равен Key. Он также является Множественным Ас- социативным Контейнером, то есть может хранить два и более идентичных элемента. (В этом состоит единственная разница между set и multiset.) Так как multiset является Сортированным Ассоциативным Контейнером, он содер- жит элементы в порядке возрастания. Отношение упорядочения задается парамет- ром шаблона Compare. Классы set и multiset особенно хорошо подходят для реализации обобщенных ал- горитмов над множествами (set_union, set_intersection и т. д.). На то есть две причи- ны. Во-первых, аргументы должны быть отсортированными диапазонами, а требова- ния к Сортированному Ассоциативному Контейнеру гарантируют, что set и multiset всегда удовлетворяют этому ограничению. Во-вторых, результирующие диапазоны этих алгоритмов тоже всегда отсортированы, а вставка отсортированного диапазона в set или multiset — быстрая операция. Требования к Сортированному Ассоциативно- му Контейнеру гарантируют, что если диапазон уже отсортирован, то для вставки ис- пользуется только линейное время. Адаптер insert_iterator обеспечивает особенно удобный способ вставки результата одного из этих алгоритмов в multiset. Так же как и у list (с. 434), у multiset есть важное свойство: вставка нового эле- мента не делает недопустимыми итераторы, указывающие на существующие элемен- ты Удаление элемента из multiset также не делает недопустимыми никакие итерато- ры, за исключением тех, которые указывают на сам удаляемый элемент. Примеры int main() { const int N = 10; int a[N] =• {4, 1, 1, 1, 1, 1, 0, 5, 1. 0},
16.2. Ассоциативные контейнеры 471 int b[N] = {4, 4, 2. 4, 2. 4. О, 1. 5. 5}, multiset<int> А(а, а + N). multiset<int> B(b b + N). multiset<int> C. cout « "Set A ”, copy(A begin(), A end(), ostream_iterator<int>(cout, " ”)), cout << endl, cout « "Set В ", copy(B begin(). В end(), ostream_iterator<int>(cout, ')), cout « endl, cout << "Union ", set_union(A begin(), A end(), В begin(), В end(), ostream_iterator<int>(cout, " ")), cout « endl, cout « "Intersection ", set_intersection(A begin(), A.end(), В begin(), В end(), ostream_iterator<int>(cout, " ")), cout << endl, set_difference(A begin(), A end(), В begin(), В end(), inserter(C, C begin())), cout « "Ser C (difference of A and B) copy(C begin(), C end(), ostream_iterator<int>(cout, " ")), cout << endl, } Где определено В реализации HP multiset определен в заголовке <multiset. h>. В соответствии со стандартом C++ он объявлен в заголовке <set>. Параметры шаблона Key Тип ключа и тип значения multiset. Этот тип также объявлен в виде вложенных типов multiset key_type и multiset’ value-type. Compa re Функция сравнения ключей соответствует Строгому Слабо- му Упорядочению (с. 132) с типом аргумента key_type. Она возвращает истинность того, что ее первый элемент меньше второго. Этот тип также объявлен в виде втоженных типов multiset: key_compareи multiset. value_compare. По умолчанию: less<Key> Allocator Аллокатор multiset, используемый для управления памятью По умолчанию: allocateг<Кеу>
472 Глава 16. Классы контейнеров Является моделью Сортированного Ассоциативного Контейнера (с. 167), Простого Ассоциативного Кон- тейнера (с. 164), Множественного Ассоциативного Контейнера (с. 163). Требования к типам • Key — модель Присваиваемого. • Compare — модель Строгого Слабого Упорядочения. • Типом значения Compare является Key. • Allocator — модель Аллокатора, тип значения которого равен Key. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов multiset не определена в требованиях к Сортированному Ассоциативному Контейнеру, Простому Ассоциативному Контейнеру и Множественному Ассоциативному Контейнеру, а специфична для multiset; она помечена символом ♦. multiset::value_type Тип объекта, хранящегося в multiset. Типом значения является Key. (Описан в Контейнере.) multiset::key_type Тип ключа, связанного с объектом типа value_type. Типом ключа является Key. (Описан в Ассоциативном Контейнере.) multiset::key_compare Функциональный объект (Строгое Слабое Упорядочение), сравнивающий два ключа при упорядочивании. (Описан в Сортированном Ассоциативном Контейнере.) multiset::value_compare Функциональный объект, сравнивающий два значения при упорядочивании. (Того же типа, что и key_compare.) (Описан в Сортированном Ассоциативном Контейнере.) multiset::pointer Указатель на Key. (Описан в Контейнере.) multiset::const_pointer Указатель на const Key. (Описан в Контейнере.) multiset::reference Ссылка на Key. (Описана в Контейнере.)
16.2. Ассоциативные контейнеры 473 multiset::const_reference Ссылка на const Key. (Описана в Контейнере.) multiset::size_type Целочисленный тип без знака, обычно size.t. (Описан в Контейнере.) multiset::difference_type Целочисленный тип со знаком, обычно pt rdiff_t. (Описан в Контейнере.) multiset::iterator Тип итератора multiset, являющийся константным Двунаправленным Итерато- ром. (У шаблона multiset нет изменяемого типа итератора. Элементы можно вставлять в multiset и удалять их оттуда, но нельзя изменить непосредственно в месте их хранения.) (Описан в Контейнере.) multiset::const_iterator Константный тип итератора multiset, являющийся константным Двунаправлен- ным Итератором. Возможно, совпадает с типом iterator. (Описан в Контейнере.) multiset:: reverse_iterator Итератор, используемый для перемещения по multiset в обратном направлении, являющийся константным Двунаправленным Итератором. (Описан в Реверсивном Контейнере.) multiset::const_reverse_iterator Итератор, используемый для перемещения по multiset в обратном направлении, тоже являющийся константным Двунаправленным Итератором. (Описан в Реверсивном Контейнере.) iterator multiset:: beginO const Возвращает iterator, указывающий на начало multiset. (Описан в Контейнере.) iterator multiset::end() const Возвращает iterator, указывающий на конец multiset. (Описан в Контейнере.) reverse_iterator multiset::rbegin() const Возвращает reverse_iterator, указывающий на начало реверсивного multiset. (Описан в Реверсивном Контейнере.) reverse_iterator multiset::rend() const Возвращает reverse_iterator, указывающий на конец реверсивного multiset. (Описан в Реверсивном Контейнере.)
474 Глава 16. Классы контейнеров size_type multiset::size() const Возвращает количество элементов в multiset. (Описан в Контейнере.) size_type multiset::max_size() const Возвращает максимально допустимый размер multiset. (Описан в Контейнере.) bool multiset:: emptyO const Возвращает истинность того, что размер multiset равен нулю. (Описан в Контейнере.) key_compare multiset::key_comp() const Возвращает объект key_compare, используемый в multiset. (Описан в Сортированном Ассоциативном Контейнере.) value_compare multiset::value_comp() const Возвращает объект value_compare, используемый multiset. (Описан в Сортированном Ассоциативном Контейнере.) explicit multiset::multiset(const key_compare& comp = key_compare(), const Allocators A = AllocatorO) Создает пустой multiset, использующий заданный функциональный объект срав- нения ключей и заданный аллокатор. (Описан в Последовательности.) template <class Inputlterator> multiset::multiset(lnputlterator f, Inputiterator 1, const key_compareS comp = key_compare(), const Allocators A = AllocatorO) Создает multiset, содержащий копию диапазона [f, 1), и использующий задан- ный функциональный объект сравнения ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.) multiset::multiset(const multisets) Конструктор копирования. (Описав в Контейнере.) multiset::-multiset() Деструктор. (Описан в Контейнере.) multisets multiset::operator=(const multisets) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type multiset::get_allocator() const Возвращает копию аллокатора, с которым был создан multiset.
16.2. Ассоциативные контейнеры 475 void multiset::swap(multiset&) Обменивает значения двух объектов типа multiset. (Описан в Контейнере.) iterator multiset::insert(iterator pos, const value_type& x) Вставляет x, используя pos в качестве подсказки, куда поместить элемент. (Описан в Сортированном Ассоциативном Контейнере.) iterator multiset::insert(const value_type& x) Вставляет x в multiset. (Описан в Множественном Ассоциативном Контейнере). template <class Inputlterator> void multiset::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в multiset. (Описан в Множественном Ассоциативном Контейнере.) void multiset::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type multiset::erase(const key_type& k) Стирает все элементы с ключом к. Возвращает количество стертых элементов. (Описан в Ассоциативном Контейнере.) void multiset::erase(iterator f, iterator 1) Стирает все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator multiset::find(const key_type& k) const Ищет элемент с ключом k. (Описан в Ассоциативном Контейнере.) size_type multiset::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. (Описан в Ассоциативном Контейнере.) iterator multiset::lower_bound(const key_type& к) const Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) iterator multiset::upper_bound(const key_type& к) const Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.) pair<iterator, iterator> multiset::equal_range(const key_type& к) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Сортированном Ассоциативном Контейнере.)
476 Глава 16. Классы контейнеров bool operator==(const multiset&, const multiset&) Сравнивает на равенство два объекта типа multiset. (Описан в Однонаправленном Контейнере.) bool operator<(const multiset&, const multiset&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.2.4. multimap (множественное отображение, ассоциативная последовательность с повторяющимися элементами) multimap<Key, Т, Compare, Allocator Шаблон multimap представляет собой Сортированный Ассоциативный Контейнер, ко- торый сопоставляет объекты типа Key с объектами типа Т. Это Парный Ассоциативный Контейнер, и тип его значения — пара pai rcconst Key, Т>. Он является также Множе- ственным Ассоциативным Контейнером, то есть может содержать два и более иден- тичных элемента. (В этом состоит единственное различие между тар и multimap.), то есть multimap отображает значение типа Key не на один объект типа Т, а на один или более объектов типа Т. Поскольку multimap является Сортированным Ассоциативным Контейнером, то он содержит элементы в порядке возрастания. Отношение упорядочения задается пара- метром шаблона Compare. Как и у list (с. 434), у multimap есть важное свойство: вставка нового элемента не делает недопустимыми итераторы, указывающие на существующие элементы. Сти- рание элемента из multimap тоже не делает недопустимыми никакие итераторы, за исключением тех, которые указывают на сам удаляемый элемент. Пример struct Itstr { bool operator()(const char* si, const char* s2) const { return strcmp(s1, s2) < 0, i i }. int main() { multimap<const char* int, ltstr> m, m insert(make_pair(’a’ 1)), m insert(make_pair("c”, 2)), m insert(make_pair(’b”, 3)), m insert(make_pair(‘b”, 4)), m insert(make_pair(”a", 5)), m insert(make_pair("b” 6)), cout << "Number of elements with key a ' « m count('a') << endl, cout << ’Number of elements with key b « m count(’b") « endl.
16.2. Ассоциативные контейнеры 477 cout << ’’Number of elements with key c ” « m count("c") « endl cout « 'Elements in m ” << endl, for (multimap<const char*, int, ltstr> iterator it - m begin(). it 1= m end(), ++it) cout << ” [’’ << (*it) first « ", " << (*it) second << ' ]" << endl, } Где определено В реализации HP multimap определен в заголовке <multimap h>. В соответствии со стандартом C++ он объявлен в заголовке <тар>. Параметры шаблона Key Тип ключа multimap. Этот тип также объявлен в виде вло- женного типа multi тар .key_type. Т Тип отображаемого значения multimap. Он также объявлен в виде вложенного типа multimap: mapped_type. Compare Функция сравнения ключей: Строгое Слабое Упорядочение (с. 132), типом аргумента которого является key_type. Она возвращает истинность того, что ее первый аргумент мень- ше второго. Этот тип также определен в виде вложенного типа multimap:: key_compare. По умолчанию: less<Key> Allocator Аллокатор multimap, используемый для управления памятью. По умолчанию: allocator<pair<const Key, Т> > Является моделью Сортированного Ассоциативного Контейнера (с. 167), Парного Ассоциативного Кон- тейнера (с. 166), Множественного Ассоциативного Контейнера (с. 163). Требования к типам • Key — модель Присваиваемого. • Т — модель Присваиваемого. • Compa ге — модель Строгого Слабого Упорядочения. • Типом значения Compa re является Key. • Allocator — модель Аллокатора, тип значения которого совпадает с типом значе- ния multimap. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов multimap не определена в требованиях к Сортированному Ассоциативному Контейнеру, Парному Ассоциативному Контейнеру и Множественному Ассоциативному Контейнеру, а специфична для multimap; она помечена символом ♦.
8 Глава 16 Классы контейнеров multimap::key_type Тип ключа multimap: Key. (Описан в Ассоциативном Контейнере.) multimap::mapped_type Тип объекта, связанного с ключом, — Т. (Описан в Парном Ассоциативном Контейнере.) multimap::value_type Тип объекта, хранящегося в multimap. Типом значения является pai r<const Key. Т>. (Описан в Парном Ассоциативном Контейнере.) multimap::key_compare Функциональный объект (Строгое Слабое Упорядочение), сравнивающий два ключа при упорядочении. (Описан в Сортированном Ассоциативном Контейнере.) multimap::value_compare Функциональный объект, сравнивающий два значения при упорядочивании. (Эквивалентно извлечению ключей этих значений и последующему сравнению ключей.) (Описан в Сортированном Ассоциативном Контейнере.) multimap::pointer Указатель на multimap- value_type. (Описан в Контейнере.) multimap::const-pointer Указательна const multimap: -value_type. (Описан в Контейнере.) multimap::reference Ссылка на multimap. : value_type. (Описана в Контейнере.) multimap::const_reference Ссылка на const multimap:-value_type. (Описана в Контейнере.) multimap::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) multimap::difference_type Целочисленный тип со знаком, обычно ptrdiff_t. (Описан в Контейнере.) multimap::iterator Тип итератора multimap, являющийся Двунаправленным Итератором Обратите внимание, что multimap iterator — неизменяемый итератор, потому что тип multimap value_type не является Присваиваемым. (Если i имеет тип
16.2. Ассоциативные контейнеры£79 multimap: • iterator, нельзя писать *i = х.) Но при этом он и не совсем констант- ный итератор, поскольку может быть использован для модификации объекта, на который он указывает. Можно написать так: i->second = х. (Описан в Контейнере.) multimap::const-iterator Константный тип итератора multimap, являющийся константным Двунаправлен- ным Итератором. (Описан в Контейнере.) multimap::reverse-iterator Итератор, используемый для перемещения по multimap в обратном направлении. (Описан в Реверсивном Контейнере.) multimap::const_reverse_iterator Константный итератор, используемый для перемещения по multimap в обратном направлении. (Описан в Реверсивном Контейнере.) multimap::begin() Возвращает iterator, указывающий на начало multimap. (Описан в Контейнере.) multimap::end() Возвращает iterator, указывающий на конец multimap. (Описан в Контейнере.) multimap::begin() const Возвращает const_iterator, указывающий на начало multimap. (Описан в Контейнере.) multimap::end() const Возвращает const_iterator, указывающий на конец multimap. (Описан в Контейнере.) multimap::rbegin() Возвращает reverse_iterator, указывающий на начало реверсивного multimap. (Описан в Реверсивном Контейнере.) multimap::rend() Возвращает reverse_iterator, указывающий на конец реверсивного multimap. (Описан в Реверсивном Контейнере.) multimap::rbegin() const Возвращает const_reverse_iterator, указывающий на начало реверсивного multimap. (Описан в Реверсивном Контейнере.) multimap::rend() const Возвращает const_reverse_iterator, указывающий на конец реверсивного multimap. (Описан в Реверсивном Контейнере.)
480 Глава 16 Классы контейнеров size_type multimap:: sizeO const Возвращает количество элементов в multimap. (Описан в Контейнере.) size_type multimap::max_size() const Возвращает максимально допустимый размер multimap. (Описан в Контейнере.) bool multimap::empty() const Возвращает истинность того, что размер multimap равен нулю. (Описан в Контейнере.) key_compare multimap::key_comp() const Возвращает объект key_compa re, используемый multimap. (Описан в Сортированном Ассоциативном Контейнере.) value_compare multimap::value_comp() const Возвращает объект value_compare, используемый multimap. (Описан в Сортированном Ассоциативном Контейнере.) explicit multimap::multimap(const key_compare& comp = key_compare(), const Allocator A = AllocatorO) Создает пустой multimap, использующий заданный функциональный объект срав- нения ключей и заданный аллокатор. (Описан в Сортированном Ассоциативном Контейнере.) template <class Inputlterator> multimap::multimap(lnputlterator f, Inputiterator 1, const key_compare& comp = key_compare(), const Allocator& A = AllocatorO) Создает multimap с копией диапазона [f, 1) и заданными функциональным объек- том сравнения и аллокатором. (Описан в Сортированном Ассоциативном Контейнере.) multimap::multimap(const multimap&) Конструктор копирования. (Описан в Контейнере.) multimap::-multimap() Деструктор. (Описан в Контейнере.) multimap& multimap::operator=(const multimap&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type multimap::get_allocator() const Возвращает копию аллокатора, с которым был создан multimap. void multimap::swap(multimap&) Обменивает значения двух объектов типа multimap. (Описан в Контейнере.)
16.2. Ассоциативные контейнеры 481 iterator multimap::insert(const value_type& x) Вставляет x в multimap. (Описан в Уникальном Ассоциативном Контейнере.) iterator multimap::insert(iterator pos, const value_type& x) Вставляет x, используя pos в качестве подсказки, куда поместить элемент. (Описан в Сортированном Ассоциативном Контейнере.) template <class Inputlterator> void multimap::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в multimap. (Описан в Множественном Ассоциативном Контейнере.) void multimap::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type multimap::erase(const key_type& k) Стирает все элементы с ключом к. Возвращаемое значение равно количеству стертых элементов. (Описан в Ассоциативном Контейнере.) void multimap::erase(iterator f, iterator 1) Стирает все элементы диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator multimap::find(const key_type& k) Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) const-iterator multimap::find(const key_type& k) const Ищет элемент с ключом k. (Описан в Ассоциативном Контейнере.) size_type multimap::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. (Описан в Ассоциативном Контейнере.) iterator multimap::lower_bound(const key_type& к) Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) const-iterator multimap::lower_bound(const key_type& к) const Ищет первый элемент, ключ которого не меньше к. (Описан в Сортированном Ассоциативном Контейнере.) iterator multimap::upper_bound(const key_type& к) Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.)
482 Глава 16. Классы контейнеров const-iterator multimap::upper_bound(const key_type& k) const Ищет первый элемент, ключ которого больше к. (Описан в Сортированном Ассоциативном Контейнере.) pair<iterator, iterator> multimap::equal_range(const key_type& k) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Сортированном Ассоциативном Контейнере.) pair<const_iterator, const_iterator> multimap::equal_range(const key_type& к) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Сортированном Ассоциативном Контейнере.) bool operator==(const multimap&, const multimap&) Операция сравнения multimap на равенство. (Описан в Однонаправленном Контейнере.) bool operator<(const multimapi, const multimap&) Лексикографическое сравнение. (Описан в Однонаправленном Контейнере.) 16.2.5. hash_set (хешированное множество) hash_set<Key, HashFun, EqualKey, Allocator Шаблон hash_set представляет собой Хешированный Ассоциативный Контейнер, хра- нящий объекты типа Key. Это Простой Ассоциативный Контейнер, то есть Key является как типом значения, так и типом его ключа. Он также является Уникальным Ассоциа- тивным Контейнером, следовательно, никакие два элемента не равны друг другу, если для сравнения используется Бинарный Предикат EqualKey. Шаблон hash_set полезен в приложениях, где нужен быстрый поиск элемента. Если важно, чтобы элементы были упорядочены, то более подходящим будет set (с. 456). Пример struct eqstr { boo] operator()(const char* si. const char* s2) const return strcmp(s1, s2) 0, } void lookup(const hash_set<const char*, hash<const char*>, eqstr>& Set, const char* word) { hash,set<const char* hashcconst char*>, eqstr> const-iterator it - Set find(word), cout « ’ ‘ « word « " << (it ’= Set end() ? "present" "not present")
16.2. Ассоциативные контейнеры 483 << endl, } int main() { hash_set<const char*, hash<const char*>, eqstr> Set, Set insert("kiwi’), Set insert(”plum"), Set insert("apple"). Set insert (’’mango”). Set insert(”apricot”), Set Insert("banana”), lookup(Set, ’’mango’’). lookup(Set, "apple”), lookup(Set, "durian"), } Результат: mango present apple present durian, not present Где определено Ни в стандарте C++, ни в оригинальной реализации HP hash_set не определен. В ре- ализации SGI он определен в заголовке <hash_set>. Параметры шаблона Key Тип ключа и тип значения hash_set. Этот тип также объяв- лен в виде вложенных типов hash_set:•key_type и hash_set:’value-type. HashFun Функция Хеширования, используемая hash_set. Она также определена в виде вложенного типа hash_set:: hasher. По умолчанию: hash<Key> EqualKey Функция сравнения ключей hash_set на равенство: Бинар- ный Предикат, используемый для определения идентично- сти двух ключей. Он также определен в виде вложенного типа hash_set: • key_equal. По умолчанию: equal_to<Key> Allocator Аллокатор hash_set, используемый для управления памятью. По умолчанию: allocateг<Кеу> Является моделью Хешированного Ассоциативного Контейнера (с. 171), Простого Ассоциативного Кон- тейнера (с. 164), Уникального Ассоциативного Контейнера (с. 161).
484 Глава 16. Классы контейнеров Требования к типам • Key — модель Присваиваемого. • HashFun — модель Функции Хеширования. • Типом значения HashFun является Key. • EqualKey — модель Бинарного Предиката. • EqualKey образует отношение эквивалентности. • Типом значения EqualKey является Key. • Allocator — модель Аллокатора, тип значения которого равен Key. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов hash_set не определена в требованиях к Хешированному Ассоциативному Контейнеру, Простому Ассоциативному Контейнеру и Уникальному Ассоциативному Контейнеру, а специфична для hash_set; она помечена символом ♦. hash_set::value_type Тип объекта, хранящегося в hash_set. Типом значения является Key. (Описан в Контейнере.) hash_set::key_type Тип ключа, связанного с объектом типа value_type. Типом ключа является Key. (Описан в Ассоциативном Контейнере.) hash_set::hasher Функция Хеширования hash_set. (Описана в Хешированном Ассоциативном Контейнере.) hash_set::key_equal Бинарный Предикат, сравнивающий ключи на равенство. (Описан в Хешированном Ассоциативном Контейнере.) hash_set::pointer Указатель на Key. (Описан в Контейнере.) hash_set::const_pointer Указатель на const Key. (Описан в Контейнере.) hash_set::reference Ссылка на Key. (Описана в Контейнере.) hash_set::const_reference Ссылка на const Key. (Описана в Контейнере.)
16.2. Ассоциативные контейнеры 485 hash_set::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) hash_set::difference_type Целочисленный тцп со знаком, обычно ptrdiff_t. (Описан в Контейнере.) hash_set::iterator Тип итератора hash_set, являющийся константным Однонаправленным Итера- тором. (hash_set нс имеет изменяемого типа итератора.) (Описан в Контейнере.) hash_set::const-iterator Константный тип итератора hash_set, являющийся константным Однонаправ- ленным Итератором. (Возможно, тот же самый тип. что и iterator.) (Описан в Контейнере.) iterator hash_set::begin() const Возвращает iterator, указывающий на начало hash_set. (Описан в Контейнере.) iterator hash_set:: end() const Возвращает iterator, указывающий на конец hash_set. (Описан в Контейнере.) size_type hash_set::size() const Возвращает количество элементов в hash_set. (Описан в Контейнере.) size.type hash_set::max_size() const Возвращает максимально допустимый размер hash_set. (Описан в Контейнере.) bool hash-set::empty() const Возвращает истинность того, что размер hash_set равен нулю. (Описан в Контейнере.) size_type hash_set::bucket-count() const Возвращает количество “ведер” в хеш-таблице. (Описан в Хешированном Ассоциативном Контейнере.) void hash_set::resize(size_type n) Увеличивает количество “ведер” в хеш-таблице до значения, большего или рав- ного п. (Описан в Хешированном Ассоциативном Контейнере.) hasher hash_funct() const Возвращает используемый объект типа hasher. (Описан в Хешированном Ассоциативном Контейнере.)
486 Глава 16. Классы контейнеров key.equal key_eq() const Возвращает используемый объект типа key_equal. (Описан в Хешированном Ассоциативном Контейнере.) explicit hash.set::hash_set(size_type n = О, const hasher& h = hasherO, const key_equal& k = key_equal(). const Allocator A = AllocatorO) Создает пустой hash_set с не менее чем n “ведрами”, используя заданные функ- цию хеширования, функцию сравнения ключей и аллокатор. (Описан в Хешированном Ассоциативном Контейнере.) template <class Inputlterator> hash_set::hash_set(lnputlterator f, Inputiterator 1, size_type n = 0, const hasher& h = hasherO, const key_equal& k = key_equal(), const Allocator^ A = AllocatorO) Создает hash_set, содержащий копию диапазона [f, 1), с не менее чем п “ведра- ми”, используя заданные функцию хеширования, функцию сравнения ключей и аллокатор. (Описан в Хешированном Ассоциативном Контейнере.) hash_set::hash_set(const hash_set&) Конструктор копирования. (Описан в Контейнере.) hash_set::-hash_set() Деструктор. (Описан в Контейнере.) hash_set& hash_set::operator=(const hash_set&) Оператор присваивания. (Описан в Контейнере.) ♦ allocatoretype hash_set::get_allocator() const Возвращает копию аллокатора, с которым hash_set был создан. void hash_set::swap(hash_set&) Обменивает значения двух объектов типа hash_set. (Описан в Контейнере.) pair<iterator, bool> hash_set::insert(const value.type* х) Вставляет х в hash_set. Вторая часть возвращаемого значения равна истинности того, что х действительно был вставлен (он может быть не вставлен, если такой элемент уже есть). (Описан в Уникальном Ассоциативном Контейнере.)
16.2. Ассоциативные контейнеры 487 template <class Inputlterator> void hash_set::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в hash_set. (Описан в Уникальном Ассоциативном Контейнере.) void hash.set::erase(iterator pos) Удаляет элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type hash_set::erase(const key_type& k) Удаляет элемент с ключом к (если таковой имеется). Возвращаемое значение равно количеству удаленных элементов — либо единица, либо нуль. (Описан в Ассоциативном Контейнере.) void hash_set::erase(iterator f, iterator 1) Удаляет все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator hash_set::find(const key_type& k) const Ищет элемент с ключом k. (Описан в Ассоциативном Контейнере.) size_type hash_set::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. Возвращаемое значе- ние равно либо нулю, либо единице. (Описан в Ассоциативном Контейнере.) pair<iterator, iterator> hash_set::equal_range(const key_type& k) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.) bool operator==(const hash_set&, const hash_set&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) 16.2.6. hash_map (хешированная ассоциативная последовательность) hash_map<Key, Т, HashFun, EqualKey, Allocator Шаблон hash_map — это Хешированный Ассоциативный Контейнер, который сопостав- ляет объекты типа Key с объектами типа Т. Он также является Парным Ассоциативным Контейнером, то есть типом его значения является pai rcconst Key, Т>. Кроме того, он представляет собой Уникальный Ассоциативный Контейнер, то есть в нем не может быть двух элементов, которые эквивалентны в смысле EqualKey. Поиск элемента по ключу в hash_map очень эффективен, поэтому hash_map полезен при создании “словарей” или ассоциативных массивов, где объект типа Key связан с объектом типа Т, а порядок элементов не важен. Если требуется расположение эле- ментов в каком-нибудь определенном порядке, то более подходящим шаблоном бу- дет тар (с. 462).
488 Глава 16. Классы контейнеров Пример struct eqstr bool operator()(const char* s1, const char* s2) const { return strcmp(s1. s2) == 0, } int main() { hash_map<const char*, int, hash<const char*>, eqstr> days, days[’’January’’] = 31; days[ february"] = 28. days[’march’ ] = 31, days["april"] = 30. days[”may’] = 31, days[']une”j = 30, oaysfjuly"] = 31, days["august"] = 31, days[’September’’] = 30, days["October ] = 31 days [’’november’’] = 30, days["december"] = 31, cout « "september -> " « days["september"] « endl. cout « "april -> ” « days["april”] « endl, cout « ’june -> " « daysfjune"] « endl, cout « "november -> ’’ « days["november"] « endl, } Где определено Ни в стандарте C++, ни в оригинальной реализации HP hash map не определен В ре- ализации SGI он определен в заголовке <hash_map>. Параметры шаблона Key Т Тип ключа hash_map. Он также объявлен как hash_map key_type. Тип, связанный с ключами hash_map. Он также объявлен как вложенный тип hash_map mapped_type. HashFun Функция Хеширования, используемая hash map. Она также объявлена в виде вложенного типа hash_map hasher. По умолчанию: hash<Key> EqualKey Функция сравнения ключей на равенство: Бинарный Пре- дикат, используемый для определения идентичности двух ключей. Он также определен в виде вложенного типа hash_map *key_equal. По умолчанию: equal_to<Key>
16.2. Ассоциативные контейнеры 489 Allocator Аллокатор hash_map, используемый для управления па- мятью. По умолчанию: allocator<pair<const Key. Т> > Является моделью Хешированного Ассоциативного Контейнера (с. 171), Парного Ассоциативного Контей- нера (с. 166), Уникального Ассоциативного Контейнера (с. 161). Требования к типам • Key — модель Присваиваемого. • Т — модель Присваиваемого. • Hash Fun — модель Функции Хеширования. • Типом значения HashFun является Key. • EqualKey — модель Бинарного Предиката. • EqualKey образует отношение эквивалентности. • Типом значения EqualKey является Key. • Allocator — модель Аллокатора. • Тип значения аллокатора совпадает с типом значения hash_map. Открытые базовые классы Отсутствуют. Члены Некоторые функции-члены hash_map не определены в требованиях к Хешированному Ассоциативному Контейнеру, Парному Ассоциативному Контейнеру и Уникальному Ассоциативному Контейнеру, а специфичны для hash_map; они помечены символом ♦. hash_map::key_type Тип ключа hash_map: Key. (Описан в Ассоциативном Контейнере.) hash_map::mapped_type Тип объекта, связанного с ключом: Т. (Описан в Парном Ассоциативном Контейнере.) hash_map::value_type Тип объекта, хранящегося в hash_map. Тип значения: pair<const Key, Т>. (Описан в Парном Ассоциативном Контейнере.) hash_map::hasher Функция Хеширования hash_map. (Описана в Хешированном Ассоциативном Контейнере.) hash_map::key_equal Бинарный Предикат сравнения ключей на равенство. (Описан в Хешированном Ассоциативном Контейнере.)
490 Глава 16. Классы контейнеров hashjnap::pointer Указатель на hashjnap * • value_type. (Описан в Контейнере.) hash_map::const.pointer Указатель на const hash_map::value_type. (Описан в Контейнере.) hash_map::reference Ссылка на hashjnap: :value_type. (Описана в Контейнере.) hash_map::const_reference Ссылка на const hashjnap: :value_type. (Описана в Контейнере.) hashjnap::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) hash_map::difference_type Целочисленный тип со знаком, обычно pt rd if f _t. (Описан в Контейнере.) hashjnap::iterator Тип итератора hash_map, являющийся Однонаправленным Итератором. Обрати- те внимание, что hash_map: : iterator не изменяемый итератор, потому что hash_map: : value_type не является Присваиваемым. (Если 1 имеет тип hashjnap::iterator, нельзя писать *i = х.) Однако это также и не совсем кон- стантный итератор, поскольку он может быть использован для модификации объекта, на который указывает. Вы можете написать i->second = х. (Описан в Контейнере.) hashjnap::const-iterator Константный тип итератора hash_map, являющийся константным Однонаправ- ленным Итератором. (Описан в Контейнере.) iterator hashjnap: :beginO Возвращает iterator, указывающий на начало hashjnap. (Описан в Контейнере.) iterator hashjnap: :endO Возвращает iterator, указывающий на конец hash_map. (Описан в Контейнере.) const-iterator hashjnap:: beginQ const Возвращает const-iterator, указывающий на начало hashjnap. (Описан в Контейнере.)
16.2. Ассоциативные контейнеры 491 const-iterator hash_map::end() const Возвращает const-iterator, указывающий на конец hash_map. (Описан в Контейнере.) size_type hash_map::size() const Возвращает количество элементов в hash_map. (Описан в Контейнере.) size_type hash_map: :max_sizeO const Возвращает максимально допустимый размер hash_map. (Описан в Контейнере.) bool hash_map::empty() const Возвращает истинность того, что размер hash_map равен нулю. (Описан в Контейнере.) size_type hash_map::bucket_count() const Возвращает количество “ведер” в хеш-таблице. (Описан в Хешированном Ассоциативном Контейнере.) void hash_map::resize(size_type n) Увеличивает количество “ведер” до значения, не меньшего п. (Описан в Хешированном Ассоциативном Контейнере.) hasher hash_funct() const Возвращает используемый объект типа hasher. (Описан в Хешированном Ассоциативном Контейнере.) key_equal key_eq() const Возвращает используемый объект типа key_equal. (Описан в Хешированном Ассоциативном Контейнере.) explicit hash-map::hash_map(size_type n = О, const hasher& h = hasher(), const key.equalS k = key_equal(), const Allocators A = AllocatorO) Создает пустой hash_map с не менее чем n “ведрами”, с заданными функцией хеширования, функцией сравнения ключей и аллокатором. (Описан в Хешированном Ассоциативном Контейнере.) template <class Inputlterator> hash.map::hash_map(lnputlterator f, Inputiterator 1, size.type n = 0, const hasherS h = hasherO, const key.equalS k = key-equalO, const Allocators A = AllocatorO) Создает hash_map, содержащий копию диапазона [f, 1), с не менее чем п “ведра- ми”, с заданными функцией хеширования, функцией равенства ключей и алло- катором. (Описан в Хешированном Ассоциативном Контейнере.)
2 Глава 16. Классы контейнеров hashjnap::hashjnap(const hashjnap&) Конструктор копирования. (Описан в Контейнере.) hashjnap::-hash jnap() Деструктор. (Описан в Контейнере.) hash_map& hash_map::operator=(const hashjnap&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type hashjnap::get_allocator() const Возвращает копию аллокатора, с которым был создан hash_map. void hash_map::swap(hashjnap&) Обменивает значения двух объектов типа hash_map. (Описан в Контейнере.) pairciterator, bool> hashjnap::insert(const value_type& х) Вставляет х в hashjnap. Вторая часть возвращаемого значения равна истинности того, что х действительно вставлен. (Описан в Уникальном Ассоциативном Контейнере.) template cclass Inputlterator> void hashjnap::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в hashjnap. (Описан в Уникальном Ассоциативном Контейнере.) void hashjnap::erase(iterator pos) Удаляет элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type hashjnap::erase(const key_type& k) Удаляет элемент с ключом к, если таковой имеется. Возвращаемое значение равно количеству удаленных элементов — либо единица, либо нуль. (Описан в Ассоциативном Контейнере.) void hashjnap::erase(iterator f, iterator 1) Удаляет все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator hashjnap::find(const key_type& k) Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) const-iterator hashjnap::find(const key_type& к) const Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.)
16.2. Ассоциативные контейнеры 493 size_type hash_map::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. Возвращаемое зна- чение равно либо нулю, либо единице. (Описан в Ассоциативном Контейнере.) pair<iterator, iterator> hashjnap::equal_range(const key_type& к) Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.) pair<const_iterator, const_iterator> hashjnap::equal_range(const key_type& к) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.) ♦ mapped_type& hashjnap::operator!](const key_type& к) Так как hash_map является Уникальным Ассоциативным Контейнером, в нем со- держится максимум один элемент с ключом к. Эта функция-член возвращает ссылку на объект, который связан с данным ключом. Если в hash_map нет такого объекта, то operate г [ ] вставляет значение объекта по умолчанию hashjnapped_type(). (Заметьте, что возвращаемым типом является hash_mapped_type&, а не value_type&, то есть operate г [ ] возвращает ссылку не на весь элемент hash, тар, а только на его часть.) Константной версии operator[ ] не существует, потому что требуется наличие возможности вставки нового элемента. Строго говоря, operator[] избыточен. Он не более чем просто сокращенная формазаписи. Выражение m[k] эквивалентно m insert(value_type(k, hash_mapped_type())) first->second bool operator==(const hash_map&, const hash_map&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) 16.2.7. hash_multiset (хешированное мультимножество) hash_multiset<Key, HashFun, EqualKey, Allocator Шаблон hashjnultiset представляет собой Хешированный Ассоциативный Контейнер, хранящий объекты типа Key. Он же Простой Ассоциативный Контейнер, то есть типом его значения, как и типом ключа, является Key. Будучи также Множественным Ассо- циативным Контейнером, он может содержать два и более идентичных элемента. (В этом и состоит единственное различие между hash_set и hash jnultiset.) Шаблон hash_multiset полезен в приложениях, где нужен быстрый поиск элемен- тов. Если важно, чтобы элементы были расположены в каком-либо порядке, то боль- ше подойдет multiset (с. 470). Пример struct eqstr bnoi operator()(const char* s1. const char* s2) const
494 Глава 16. Классы контейнеров { return strcmp(sl. s2) == О, typedef hash_multiset<const char*, hash<const char*>, eqstr> Hash_Multiset: void lookup(const Hash_Multiset& Set, const char* word) { int n_found = Set count(word); cout << ” " « word « « n_found « « (n_found == 1 ? "instance" "instances") « endl, } int main() { riash_Multiset Set, Set insert("mango"); Set insert("kiwi"); Set insert("apple"); Set insert("pear"); Set insert("kiwi"), Set.insert(”mango"), Set insert("pear"); Set insertC'mango”), Set.insert("apricot") Set Insert("banana"); Set insert("mango"), lookup(Set, "mango”), lookup(Set, "apple”), lookup(Set. "durian") } Результат: mango 4 instances apple- 1 instance durian 0 instances Где определено Шаблон hash_multiset не входит ни в стандарт C++, ни в реализацию HP. В реа ции SGI он определен в заголовке <hash_set. h>.
16.2. Ассоциативные контейнеры 495 Параметры шаблона Key Тип ключа и тип значения hash-multiset. Он также объяв- лен в виде вложенных типов hash_multiset: :key_type и hash_multiset:-value_type. HashFun функция Хеширования, используемая hash_map. Она также объявлена в виде вложенного типа hashjnultiset • • hashe г. По умолчанию: hash<Key> EqualKey Функция сравнения ключей на равенство: Бинарный Пре- дикат, используемый для определения идентичности двух ключей. Он также объявлен в виде вложенного типа hashjnultiset::key_equal. По умолчанию: equal_to<Key> Allocator Аллокатор hash_multiset, используемый для управления памятью. По умолчанию: allocator<Key> Является моделью Хешированного Ассоциативного Контейнера £с. 171), Простого Ассоциативного Кон- тейнера (с. 164), Множественного Ассоциативного Контейнера (с. 163). Требования к типам • Key — модель Присваиваемого. • HashFun — модель Функции Хеширования. • Типом значения HashFun является Key. • EqualKey — модель Бинарного Предиката. • EqualKey образует отношение эквивалентности. • Типом значения EqualKey является Key. • Allocator — модель Аллокатора, тип значения которого равен Key. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов hash_multiset не определена в требованиях к Хеширован- ному Ассоциативному Контейнеру, Простому Ассоциативному Контейнеру и Множест- венному Ассоциативному Контейнеру, а специфична для hash_multiset; она помечена символом ♦. hashjnultiset::value_type Тип объекта, хранящегося в hash_multiset. Типом значения является Key. (Описан в Контейнере.) hashjnultiset::key_type Тип ключа, связанного с объектом типа value_type. Типом ключа является Key. (Описан в Ассоциативном Контейнере.)
496 Глава 16. Классы контейнеров hashjnultiset::hasher Функция Хеширования hashjnultiset. (Описана в Хешированном Ассоциативном Контейнере.) hashjnultiset::key_equal Бинарный Предикат, сравнивающий значения ключей на равенство. (Описан в Хешированном Ассоциативном Контейнере.) hashjnultiset::pointer Указатель на Key. (Описан в Контейнере.) hashjnultiset::const_pointer Указатель на const Key. (Описан в Контейнере.) hashjnultiset:: reference Ссылка на Key. (Описана в Контейнере.) hashjnultiset::const_reference Ссылка на const Key. (Описана в Контейнере.) hash_multiset::size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) hash_multiset::difference_type Целочисленный тип со знаком, обычно pt rdif f _t. (Описан в Контейнере.) hashjnultiset::iterator Тип итератора hashjnultiset, являющийся константным Однонаправленным Ите- ратором. (Итератора переменного типа не существует.) (Описан в Контейнере.) hashjnultiset::const_iterator Константный тип итератора hashjnultiset, являющийся константным Однонап- равленным Итератором. (Возможно, совпадает с iterator.) (Описан в Контейнере.) iterator hashjnultiset:: beginO const Возвращает iterator, указывающий на начало hash_multiset. (Описан в Контейнере.) iterator hashjnultiset::endO const Возвращает iterator, указывающий на конец hashjnultiset. (Описан в Контейнере.)
16.2. Ассоциативные контейнеры 497 size_type hash_multiset::size() const Возвращает количество элементов в hash_multiset. (Описан в Контейнере.) size_type hash_multiset::max_size() const Возвращает максимально допустимый размер riasn_multiset. (Описан в Контейнере.) bool hash_multiset::empty() const Возвращает истинность того, что размер hash_multiset равен нулю. (Описан в Контейнере.) size_type hash_multiset::bucket_count() const Возвращает количество “ведер” в хеш-таблице. (Описан в Хешированном Ассоциативном Контейнере.) void hashjnultiset::resize(size_type n) Увеличивает количество “ведер” до значения, не меньшего л. (Описан в Хешированном Ассоциативном Контейнере.) hasher hash_funct() const Возвращает используемый функциональный объект типа hasher. (Описан в Хешированном Ассоциативном Контейнере.) key_equal key_eq() const Возвращает используемый функциональный объект типа key_equal. (Описан в Хешированном Ассоциативном Контейнере.) explicit hashjnultiset::hashjnultiset(size_type n = О, const hasher& h = hasher(), const key_equal& k = key_equal(), const Allocators A = AllocatorO) Создает пустой hash_multiset, имеющий не менее чем л “ведер” и использующий задан- ные функцию хеширования, функцию сравнения ключей на равенство и аллокатор. (Описан в Хешированном Ассоциативном Контейнере.) template <class Inputlterator> hashjnultiset::hash_multiset(Inputiterator f, Inputiterator 1, size_type n = 0, const hasher& h = hasher(), const key_equal& k = key_equal(), const Allocators A = AllocatorO) Создает hash_multiset, содержащий копию диапазона [f, 1), имеющий не менее чем п “ведер” и заданные функцию хеширования, функцию сравнения ключей на равенство и аллокатор. (Описан в Хешированном Ассоциативном Контейнере.) hashjnultiset::hashjnultiset(const hash_multiset&) Конструктор копирования. (Описан в Контейнере.)
498 Глава 16. Классы контейнеров hashjnultiset::-hash_multiset() Деструктор. (Описан в Контейнере.) hashjnultiset& hashjnultiset::operator=(const hashjnultiset&) Оператор присваивания. (Описан в Контейнере.) ♦ allocatorjtype hashjnultiset::get_allocator() const Возвращает копию аллокатора, с которым hashjnultiset был создан. void hashjnultiset::swap(hashjnuItiset&) Обменивает значения двух объектов типа hashjnultiset. (Описан в Контейнере.) iterator hashjnultiset::insert(const value_type& x) Вставляет x в hashjnultiset. (Описан в Множественном Ассоциативном Контейнере.) template <class Inputlterator> void hashjnultiset::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в hashjnultiset. (Описан в Множественном Ассоциативном Контейнере.) void hashjnultiset::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type hashjnultiset::erase(const key_type& k) Стирает все элементы с ключом к. Возвращаемое значение равно количеству стертых элементов. (Описан в Ассоциативном Контейнере.) void hashjnultiset::erase(iterator f, iterator 1) Стирает все элементы из диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator hashjnultiset::find(const key_type& k) const Ищет элемент с ключом k. (Описан в Ассоциативном Контейнере.) size_type hashjnultiset::count(const key_type& k) const Возвращает количество элементов, ключи которых равны к. (Описан в Ассоциативном Контейнере.) pairciterator, iterator> hashjnultiset::equal_range(const key_type& k) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.)
16.2. Ассоциативные контейнеры 499 bool operator==(const hash_multiset&, const hash_multiset&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.) 16.2.8. hash_multimap (хешированная ассоциативная последовательность с повторяющимися элементами) hash_multimap<Key, Т, HashFun, EqualKey, Allocator Шаблон hash_multimap представляет собой Хешированный Ассоциативный Контейнер, сопоставляющий объекты типа Key с объектами типа Т. Он Парный Ассоциативный Контейнер, то есть типом его значений является pair<const Key, Т>, а также Множе- ственный Ассоциативный Контейнер и может содержать два и более одинаковых эле- мента (в этом и состоит единственное различие между hash jnap и hash_multimap.), то есть hashjnultimap отображает значение типа Key не на один объект типа Т, а на один или более объектов типа Т. Поиск элемента по ключу в hash_multimap — быстрая операция, и поэтому hashjnultimap полезен при создании “словарей” или ассоциативных массивов, где объект типа Key связан с объектами типа Т, а порядок элементов не имеет значения. Если нужно, чтобы элементы были расположены в некотором порядке, то лучше вос- пользоваться шаблоном multimap (с. 476). Пример struct eqstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1. s2) == 0. } }. typedef hash_multimap<const char*, int, hash<const char*>. eqstr> map_type. void lookup(const map_type& Map. const char* str) { cout << ” « str « " pair<map_type const-iterator, map.type* const_iterator> p = Map equal_range(str), for (map_type const-iterator i = p first; i '= p.second, ++i) cout « (*i) second « ” cout « endl, } int main() { map_type M, M M M M M insert(map_type••value_type(”H", insert(map_type value_type(”H", insert (map_type value-type(”C’’. insert(map_type value_type(*’C”, 1nsert(map_type .value_type(”0”, D), 2)). 12)). 13)), 16)).
500 Глава 16. Классы контейнеров М iпчргг(map_type value_type(’0", 17)), М insert(map_1уре value_type(”0 . 18)), М insert(map_type value_type("I”, 127)), lookupfM ’I”). lookup(M, ”0"), lookup(M. ”Rn”), // Результат // I 127 // 0 16 18 17 /7 Rn Где определено hasn_map не входит ни в стандарт C++, ни в оригинальную реализацию HP. В реали- зации SGI он определен в заголовке <hash_map>. Параметры шаблона Key Тип ключа hash_multimap. Этот тип также объявлен в виде вложенного типа hashjnultimap. ‘ key_type. Т Тип ассоциированного элемента hashjnultimap. Этот тип также объявлен в виде вложенного типа hashjnultimap:.mapped_type. HashFun Функция Хеширования, используемая hashjnultimap. Этот тип также объявлен в виде вложенного типа hash jnultimap •: hashe г. По умолчанию: hash<Key> EqualKey Функция сравнения ключей на равенство: Бинарный Преди- кат, используемый для определения идентичности двух клю- чей. Этот тип также объявлен в виде вложенного типа hashjnultimap::key_equal. По умолчанию: equal jto<Key> Allocator Аллокатор hash_multimap, используемый для управления памятью. По умолчанию: allocator<pair<const Key, Т» Является моделью Хешированного Ассоциативного Контейнера (с. 171), Парного Ассоциативного Контей- нера (с. 166), Множественного Ассоциативного Контейнера (с. 163). Требования к типам • Key — модель Присваиваемого. • Т — модель Присваиваемого. • HashFun — модель Функции Хеширования. • Типом значения HashFun является Key. • EqualKey — модель Бинарного Предиката.
16.2. Ассоциативные контейнеры 501 • EqualKey образует отношение эквивалентности. • Типом значения EqualKey является Key. • Allocator — модель Аллокатора. • Тип значения аллокатора совпадает с типом значения hashjnultimap. Открытые базовые классы Отсутствуют. Члены Одна из функций-членов hash_multimap не определена в требованиях к Хеширован- ному Ассоциативному Контейнеру, Парному Ассоциативному Контейнеру и Множест- венному Ассоциативному Контейнеру, а специфична для hashjnultimap; она помечена символом ♦. hashjnultimap::key_type Тип ключа hash_multimap — Key. (Описан в Ассоциативном Контейнере.) hashjnultimap::mapped_type Тип объекта, связанного с ключом. Этот тип — I. (Описан в Парном Ассоциативном Контейнере.) hashjnultimap::value_type Тип объекта, хранящегося в hash_multimap. Этот тип — pai r<const Key, Т>. (Описан в Парном Ассоциативном Контейнере.) hash_multimap::hasher Функция Хеширования hashjnultimap. (Описана в Хешированном Ассоциативном Контейнере.) hashjnultimap::key_equal Бинарный Предикат сравнения ключей на равенство. (Описан в Хешированном Ассоциативном Контейнере.) hashjnultimap::pointer Указатель на hash_multimap- value_type. (Описан в Контейнере.) hashjnultimap::const_pointer Указатель на const hash_multimap value_type. (Описан в Контейнере.) hashjnultimap::reference Ссылка на hashjnultimap • value_type. (Описана в Контейнере.) hashjnultimap::const.reference Ссылка на const hashjnultimap• value_type. (Описана в Контейнере.)
502 Глава 16. Классы контейнеров hashjnultimap:: size_type Целочисленный тип без знака, обычно size_t. (Описан в Контейнере.) hashjnultimap::difference-type Целочисленный тип со знаком, обычно pt rdif f_t. (Описан в Контейнере.) hashjnultimap::iterator Тип итератора hashjnultimap, являющийся Однонаправленным Итератором. Об- ратите внимание, что hashjnultimap : iterator — неизменяемый итератор, пото- му что тип значения hashjnultimap:: value Jzype не является Присваиваемым. (Если 1 имеет тип hash_multimap:: iterator, нельзя писать *i = х.) Однако это также и не совсем константный итератор, поскольку он может быть использован для модификации объекта, на который указывает. Можно написать i->second = х. (Описан в Контейнере.) hashjnultimap::const-iterator Константный тип итератора hash_multimap, являющийся константным Однонап- равленным Итератором. (Описан в Контейнере.) iterator hashjnultimap::begin() Возвращает iterator, указывающий на начало hashjnultimap. (Описан в Контейнере.) iterator hashjnultimap::end() Возвращает iterator, указывающий на конец hashjnultimap. (Описан в Контейнере.) const_iterator hashjnultimap:: beginO const Возвращает const-iterator, указывающий на начало hashjnultimap. (Описан в Контейнере.) const-iterator hashjnultimap::end() const Возвращает const_iterator, указывающий на конец hashjnultimap. (Описан в Контейнере.) size_type hashjnultimap::size() const Возвращает количество элементов в hash_multimap. (Описан в Контейнере.) size-type hashjnultimap::max_size() const Возвращает максимально допустимый размер hash_multimap. (Описан в Контейнере.) bool hashjnultimap::empty() const Возвращает истинность того, что размер hash_multimap равен нулю. (Описан в Контейнере.)
16.2. Ассоциативные контейнеры 503 size_type hashjnultimap::bucket_count() const Возвращает количество “ведер” в хеш-таблице. (Описан в Хешированном Ассоциативном Контейнере.) void hash_multimap::resize(size_type n) Увеличивает количество “ведер” до значения, не меньшего п. (Описан в Хешированном Ассоциативном Контейнере.) hasher hash_funct() const Возвращает используемый объект типа hasher. (Описан в Хешированном Ассоциативном Контейнере.) key_equal key_eq() const Возвращает используемый объект типа key_equal. (Описан в Хешированном Ассоциативном Контейнере.) explicit hash_multlmap::hash_multimap(size_type n = 0, const hasher& h = hasher(), const key_equal& k = key_equal(), const Allocator^ A = AllocatorO) Создает пустой hash_multimap с не менее чем n “ведрами”, с заданными функци- ей хеширования, функцией сравнения ключей и аллокатором. (Описан в Хешированном Ассоциативном Контейнере.) template <class Inputlterator> hashjnultimap::hash_multimap(lnputlterator f, Inputiterator 1, size_type n = 0, const hasher& h = hasher(), const key_equal& k = key_equal()t const Allocator^ A = AllocatorO) Создает hashjnultimap, содержащий копию диапазона [ f, 1), с не менее чем п “вед- рами”, с заданными функцией хеширования, функцией сравнения ключей и ал- локатором. (Описан в Хешированном Ассоциативном Контейнере.) hashjnultimap::hash_multimap(const hashjnultimapA) Конструктор копирования. (Описан в Контейнере.) hashjnultimap:: -hashjnultimapO Деструктор. (Описан в Контейнере.) hash_multimap& hashjnultimap::operator=(const hash_multimap&) Оператор присваивания. (Описан в Контейнере.) ♦ allocator_type hashjnultimap::get_allocator() const Возвращает копию аллокатора, с которым был создан hash_multimap.
\ Глава 16. Классы контейнеров void hashjnultimap::swap(hash_multimap&) Обменивает значения двух объектов типа hashjnultimap. (Описан в Контейнере.) iterator hashjnultimap::insert(const value_type& x) Вставляет x в hash_multimap. (Описан в Множественном Ассоциативном Контейнере.) template <class Inputlterator> void hashjnultimap::insert(lnputlterator f, Inputiterator 1) Вставляет диапазон [f, 1) в hashjnultimap. (Описан в Множественном Ассоциативном Контейнере.) void hashjnultimap::erase(iterator pos) Стирает элемент, на который указывает pos. (Описан в Ассоциативном Контейнере.) size_type hashjnultimap::erase(const key_type& k) Стирает все элементы с ключом к. Возвращаемое значение равно количеству стертых элементов. (Описан в Ассоциативном Контейнере.) void hashjnultimap::erase(iterator f, iterator 1) Стирает все элементы диапазона [f, 1). (Описан в Ассоциативном Контейнере.) iterator hashjnultimap::find(const key_type& k) Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) const_iterator hashjnultimap::find(const key_type& к) const Ищет элемент с ключом к. (Описан в Ассоциативном Контейнере.) size_type hashjnultimap::count(const key_type& к) const Возвращает количество элементов, ключи которых равны к. (Описан в Ассоциативном Контейнере.) pair<iterator, iterator> hashjnultimap::equal_range(const key_type& к) Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.) pair<const_iterator, const_iterator> hashjnultimap::equal_range(const key_type& к) const Ищет диапазон, содержащий все элементы, ключи которых равны к. (Описан в Ассоциативном Контейнере.) ооо1 operator==(const hashjnultimap&, const hash_multimap&) Операция сравнения на равенство. (Описан в Однонаправленном Контейнере.)
16.3. Адаптеры контейнеров 505 16.3. Адаптеры контейнеров Шаблоны stack (стек), queue (очередь) и priority_queue (приоритетная очередь) — это скорее не контейнеры, а шаблоны, предоставляющие ограниченное подмноже- ство операций контейнера. Например, stack позволяет вставлять, удалять или обра- щаться к одному элементу на вершине стека. Данные шаблоны являются адаптера- ми, потому что они реализованы на основе базовых контейнеров. Стеки, очереди и приоритетные очереди — хорошо знакомые структуры данных, но один аспект интерфейса этих адаптеров контейнеров может показаться незнако- мым. У всех трех шаблонов есть функция-член pop, которая удаляет верхний элемент и не возвращает значение удаляемого элемента. Если бы pop возвращала этот элемент, ей пришлось бы возвращать его по значе- нию, а не по ссылке. (Поскольку элемент удаляется, то не на что ссылаться.) Однако возврат по значению был бы неэффективен, потому что приводит по крайней мере к одному дополнительному вызову конструктора копирования. Следовательно, не- возможно реализовать возврат значения функцией рор() эффективно и корректно, поэтому разумнее совсем не возвращать значения. Если вы захотите обратиться к эле- менту на вершине, можете вызвать другую функцию-член. 16.3.1. stack (стек) stack<T, Sequence> Шаблон stack представляет собой адаптер, предоставляющий ограниченное подмно- жество функциональности Контейнера. Он предоставляет вставку, удаление и обра- щение к элементу на вершине стека. Это структура данных типа “последний вошел — первый вышел” (last in — first out, LIFO). На вершине стека находится элемент, кото- рый был добавлен в него последним. Обратиться можно только к элементу стека, который находится на его вершине; к остальным элементам стека обратиться нельзя. Кроме того, stack не позволяет осу- ществлять итерацию по своим элементам. Это ограничение — единственная причина существования стека, так как любую Последовательность с Начальной Вставкой или Последовательность с Концевой Вставкой можно использовать как стек. Например, в случае vector операциями над стеком являются функции-члены back, push back и pop_back. Единственная причина применения адаптера контейнера stack, а не По- следовательности — необходимость использовать операции, присущие только стеку. Так как stack является адаптером контейнера, он реализован на базе какого-либо типа контейнера. По умолчанию этим типом является deque, но может быть явно за- дан и другой тип. Стеки являются стандартными типами данных и обсуждаются в книгах по алго- ритмам. Например, можно обратиться к разделу 2.2.1 [Кпи97]. Пример int main() { stack<int> S. S push(8), S push(7),
506 Глава 16. Классы контейнеров S push(4), assertfS size() == 3), assert(S top() == 4), S pop(), assert(S top() == 7), S.popO. assert(S top() == 8), S pop(). assert(S emptyO), } Где определено В реализации HP stack определен в заголовке <stack. h>. В соответствии со стандар- том C++ он объявлен в заголовке <stack>. Параметры шаблона Г Тип объекта, хранящегося в stack. Sequence Тип базового контейнера, используемого для реализации stack. По умолчанию: deque<T> Является моделью Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Кроме того, является моделью =Сравнимого (с. 102) в том и только том случае, если Т — тип, =Сравнимый, и моделью <Сравнимого (с. 103) в том и только том случае, если Т — тип <Сравнимый. Требования к типу • Т — модель Присваиваемого. • Sequence — модель Последовательности с Концевой Вставкой. • Типом значения Sequence является Т. • Если используется operator==, то Т является моделью Сравнимого. • Если используется operators то Т является моделью Сравнимого. Открытые базовые классы Отсутствуют. Члены Некоторые из функций-членов stack не определены в требованиях к Присваиваемо- му, Конструируемому по Умолчанию, Сравнимому и Сравнимому, а специфичны для stack; они помечены символом ♦. ♦ stack::value_type Тип объекта, хранящегося в stack. Он совпадает с типом Т и Sequence:: value_type-
16.3. Адаптеры контейнеров 507 ♦ stack::size_type Целочисленный тип без знака. Эквивалентен Sequence:: size.type. stack::stack() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) ♦ stack::stack(const Sequence& S) Создает новый объект типа stack, содержащий те же самые элементы, что и S. Этот конструктор инициализирует базовый объект Sequence элементами из S stack::stack(const stack&) Конструктор копирования. (Описан в Присваиваемом.) stack::-stack() Деструктор. Уничтожает все элементы stack. stack& stack::operator=(const stack&) Оператор присваивания. (Описан в Присваиваемом.) ♦ size_type stack::size() const Возвращает количество элементов, содержащихся в stack. ♦ bool stack:: emptyO const Возвращает истинность того, что stack не содержит ни одного элемента. S.empty() эквивалентно S. size() == 0. ♦ value_type& stack::top() Возвращает изменяемую ссылку на элемент на вершине. Предусловие: emptyO == false. ♦ const value_type& stack: :top() const Возвращает константную ссылку на элемент на вершине. Предусловие: emptyO == false. ♦ void stack::push(const value_type& x) Помещает x на вершину stack. Постусловие: size() инкрементируется на 1. a top() будет копией x. ♦ void stack::pop() Удаляет элемент с вершины. Предусловие: empty() == false. Постусловие: size() декрементируется на 1. bool operator==(const stack&, const stackA) Оператор сравнения на равенство. Два объекта stack равны, если они содержат одинаковое количество элементов и равны поэлементно. bool operator<(const stack&, const stack&) Лексикографическое (поэлементное) сравнение.
508 Глава 16. Классы контейнеров 16.3.2. queue (очередь) queue<T, Sequence> Шаблон queue представляет собой адаптер, предоставляющий ограниченное подмно- жество функциональности Контейнера. Это структура данных типа “первый вошел — первый вышел” (first in — first out, FIFO), то есть элементы добавляются в конец оче- реди, а удаляются с начала; 0 f ront() — элемент, добавленный в очередь раньше всех остальных. Можно обратиться только к элементам queue, находящимся в начале или конце; к остальным элементам обратиться невозможно: у queue нет итераторов. Данное огра- ничение — единственная причина существования queue, потому что любой контей- нер, являющийся одновременно Последовательностью с Начальной Вставкой и По- следовательностью с Концевой Вставкой, можно использовать как очередь. Напри- мер, у deque и list есть функции-члены front, back, push_froot, push_back, pop_frent и poo_back. Единственная причина использования адаптера queue вместо контейнера deque — намерение выполнять операции, присущие только очереди, и никакие другие. Так как queue является адаптером контейнера, он реализован на базе какого-либо типа контейнера. По умолчанию данным типом является deaue, но может быть явно задан и другой тип. Очереди — это стандартные типы данных, они обсуждаются в книгах по алгорит- мам. Для примера можно обратиться к разделу 2.2.1 [Кпи97]. Пример int main() { queue<int> 0. Q push(8), Q push(7), 0 push(6). 0 push(2). assert(0 size() -- 4), assert(Q back() == 2), assert(0 front() == 8), Q pop(), assert(Q front() == 7), Q pop(). assert(Q front() == 6), Q pop(). assert(Q front() == 2), Q pop(). assert(Q emptyO),
16.3. Адаптеры контейнеров 509 Где определено В реализации HP queue определен в заголовке <stack. h>. В соответствии со стандар- том C++ он объявлен в заголовке <queue>. Параметры шаблона Т Тип объекта, хранящегося в queue. Sequence Тип базового контейнера, используемого для реализации queue. По умолчанию: deque<T> Является моделью Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Кроме того, является моделью =Сравнимого (с. 102) в том и только том случае, если Т — тип =Сравнимый, и моделью <Сравнимого (с. 103) в том и только том случае, если Т — тип <Сравнимый. Требования к типу • Т — модель Присваиваемого. • Sequence — модель Последовательности с Начальной Вставкой. • Sequence — модель Последовательности с Концевой Вставкой. • Типом значения Sequence является Т. • Если используется operateг==, то Т является моделью ^Сравнимого. • Если используется operatorc, то Т является моделью <Сравнимого. Открытые базовые классы Отсутствуют. Члены Некоторые функции-члены queue не определены в требованиях к Присваиваемому, Конструируемому по Умолчанию, =Сравнимому и <Сравнимому, а специфичны для queue; они помечены символом ♦. ♦ queue::value_type Тип объекта, хранящегося в queue. Он совпадает с типом Т и Sequence value_type ♦ queue::size_type Целочисленный тип без знака. Эквивалентен Sequence. • size_type. queue::queue() Конструктор по умолчанию. (Описан в Конструируемом по Умолчанию.) ♦ queue::queue(const Sequence& S) Создает новый объект типа queue, содержащий те же самые элементы, что и S Этот конструктор инициализирует базовый объект Sequence элементами из S. queue::queue(const queue&) Конструктор копирования. (Описан в Присваиваемом.)
510 Глава 16. Классы контейнеров queue::~queue() Деструктор. Уничтожает все элементы queue. queue& queue::operator=(const queue&) Оператор присваивания. (Описан в Присваиваемом.) ♦ size_type queue::size() const Количество элементов, содержащихся в queue. ♦ bool queue:: emptyO const Возвращает истинность того, что queue не содержит ни одного элемента. 0 empty() эквивалентно 0 size() == 0. ♦ value_type& queue::front() Возвращает изменяемую ссылку на элемент в начале queue, то есть на тот, кото- рый был вставлен раньше всех. Предусловие: empty() == false. ♦ const value_type& queue::front() const Возвращает константную ссылку на элемент в начале queue, то есть на тот, кото- рый был вставлен раньше всех. Предусловие: empty() == false. ♦ value_type& queue::back() Возвращает изменяемую ссылку на элемент в конце queue, то есть на тот, кото- рый был вставлен последним. Предусловие: empty() == false. ♦ const value_type& queue::back() const Возвращает константную ссылку на элемент в конце queue, то есть на тот, кото- рый был вставлен последним. Предусловие: empty() == false. ♦ void queue::push(const queue::value_type& x) Добавляет x в конец queue. Постусловие: size() инкрементируется на 1, a back() будет копией x. ♦ void queue:: pop() Удаляет элемент в начале queue. Предусловие: empty() == false. Постусловие: size() декрементируется на 1. bool operator==(const queue&, const queue&) Оператор равенства. Два объекта queue равны, если они содержат одинаковое количество элементов и равны поэлементно. bool operator<(const queue&, const queue&) Лексикографическое (поэлементное) сравнение. 16.3.3. priority_queue (приоритетная очередь) priority_queue<T, Sequence, Compare> Шаблон priority_queue представляет собой адаптер, предоставляющий ограничен- ное подмножество функциональности Контейнера. Он обеспечивает вставку элемен-
16.3. Адаптеры контейнеров 511 тов, а также просмотр и удаление элемента на вершине. Не существует никаких спо- собов модификации элементов priority_queue, как и итерации по ним. Шаблон priority_queue организован таким образом, что элемент на вершине все- гда является наибольшим в смысле отношения, задаваемого функцибнальным объек- том Compare, именно поэтому priority_queue не предоставляет доступ к отдельным элементам. Тот факт, что наибольший элемент находится на вершине, является инва- риантом класса. Поскольку priority_queue — адаптер контейнера, он реализован на базе какого-либо контейнерного типа. По умолчанию таким типом является vector, но может быть явно задан и другой тип. Приоритетные очереди являются стандартными типами данных и могут быть реа- лизованы многими различными способами. Типичная реализация шаблона priority_queue использует кучи, которые поддерживаются с помощью алгоритмов make_heap, push_heap и pop_heap. Приоритетные очереди обсуждаются во всех книгах по алгоритмам. Для примера можно обратиться к разделу 5.2.3 [Кпи97]. Пример int main() { priority_queue<int> 0. Q push(1), Q push(4), Q push(2), Q push(8); Q push(5), Q push(7). assert(Q.size() == 6): while (’Q emptyO) { cout « Q top() « 0 pop(), } cout « endl, // Результат- 875421 В предыдущем примере на вершине pnority_queue всегда находился наибольший элемент. Поскольку упорядочивание может быть задано функциональным объектом, так же легко можно создать priority_queue с наименьшим элементом на вершине. int main() { priority_queue<int, vector<int>, greater<int> > Q. 0 push(1), Q push(4), Q push(2), Q push(8), 0 push(5), Q push(7),
512 Глава 16. Классы контейнеров assert(Q size() == 6) while ('0 emptyO) { сои I « О top() « ” ". О рор(). } cout « endl. } И Результат 124578 Где определено В реализации HP priority_queue определен в заголовке <stack.h>. В соответствии со стандартом С++ он объявлен в заголовке <queue>. Параметры шаблона Т Тип объекта, хранящегося в pnority_queue. Sequence Тип базового контейнера, используемого для реализации priority_queue. По умолчанию: vector<T> Compare Функция сравнения, используемая для определения мень- шего элемента. По умолчанию: less<T> Является моделью Присваиваемого (с. 100), Конструируемого по Умолчанию (с. 101). Требования к типу • Т — модель Присваиваемого. • Sequence — модель Последовательности. • Sequence — модель Контейнера Произвольного Доступа. • Типом значения Sequence является Т. • Compare — модель Строгого Слабого Упорядочения. • Типом значения Compa re является Т. Открытые базовые классы Отсутствуют. Члены Некоторые функции-члены prionty_queue не определены в требованиях к Присваи- ваемому и Конструируемому по Умолчанию, а специфичны для priority_queue; они помечены символом ♦. ♦ priority_queue::value_type Тип объекта, хранящегося в ргюrity_queue. Он совпадает с типом Т и Sequence*'value_type.
16.3. Адаптеры контейнеров 513 ♦ priority_quetie:: size_type Целочисленный тип без знака. Эквивалентен Sequence •: size_type. explicit priority_queue:: priority_queue(const Compare& c = CompareO) Создает пустую priority_queue, используя с в качестве объекта сравнения. (Кон- структор по умолчанию использует в качестве объекта сравнения функцию Compare().) (Описан в Конструируемом по Умолчанию.) ♦ template <class Inputlterator> priority_queue::priority_queue(lnputlterator f, Inputiterator 1, const Compare& c = CompareO) Создает priority_queue, проинициализированную элементами из диапазона [f, 1), и использующую с в качестве функции сравнения. ♦ priority_queue::priority_queue(const Compare& с, const Sequence& S) Создает p rio ri ty_queue, содержащую те же самые элементы, что и 8, и использу- ющую с в качестве функции сравнения. Этот конструктор инициализирует базо- вый объект Sequence элементами S, а затем переупорядочивает элементы таким образом, чтобы соответствовать инварианту priority_queue. (Описан в Конструируемом по Умолчанию.) ♦ template <class Inputlterator> priority_queue::priority_queue(Inputiterator f, Inputiterator 1, const Compare& c, const Sequence^ S) Создает pnority_queue, проинициализированную элементами из диапазона [f, 1) и элементами S. Этот конструктор инициализирует базовый объект Sequence эле- ментами S, добавляет элементы из [f, 1) и затем упорядочивает элементы. priority_queue::-priority_queue() Деструктор. Уничтожает все элементы priority_queue. priority_queue& priority_queue::operator=(const priority_queue&) Оператор присваивания. (Описан в Присваиваемом.) ♦ size_type priority_queue::size() const Количество элементов, содержащихся в priority_queue. ♦ bool priority_queue:: emptyO const Возвращает истинность того, что priority_queue не содержит ни одного элемен- та. О empty() эквивалентно О size() == 0. ♦ const value_type& priority_queue::top() const Возвращает константную ссылку на элемент на вершине priority_queue. Гаранти- руется, что элемент на вершине является наибольшим в смысле функции сравнения Comp. То есть для всех остальных элементов х из priority_queue Comp(Q top(). х) равно false. Предусловие: ’ empty().
514 Глава 16. Классы контейнеров ♦ void priority_queue::push(const value_type& x) Вставляет x в priority_queue. Постусловие: size() инкрементируется на 1. Об- ратите внимание, что в общем случае значение х не будет находиться на вершине* это произойдет, только если оно больше всех других элементов, уже находящих- ся в priority_queue. ♦void priority_queue::рор() Удаляет элемент с вершины, то есть наибольший элемент в pnority_queue. Пред- условие: 1 empty(). Постусловие: size()декрементируется на 1.
Приложение А Переносимость и стандартизация В настоящее время язык C++ переживает переходный период. На начальном этапе развития языка, когда его единственной реализацией был ком- пилятор AT&T Cfront, полным справочным руководством считалось первое издание книги Бьерна Страуструпа (Bjarne Stroustrup) Язык программирования C++ [Str86]. Позднее, с появлением компиляторов C++ от других производителей, фактическим стандартом стало Аннотированное справочное руководство Маргарет Эллис (Margaret Ellis) и Бьерна Страуструпа [ES90], известное приверженцам языка как ARM. Сегодня происходит становление C++ в качестве международного стандарта (Окон- чательный Черновик Международного Стандарта был одобрен 14 ноября 1997 года), и определение языка в настоящее время контролируется Объединенным комитетом Международной организации стандартов (ISO) и Американским национальным ин- ститутом стандартов (ANSI). Язык изменялся в процессе стандартизации. Различия между стандартом языка C++ [ISO98] и ARM столь же впечатляющи, как и различия языка Си в стандарте ANSI и в первом издании книги Язык программирования Си [KR78]. Были добавлены новые языковые возможности и дополнительно уточнены многие существующие. Такие бюрократические подробности, казалось бы, не затрагивают обычных про- граммистов, однако переносимость между разными реализациями C++ не появляет- ся автоматически. В настоящее время ни один из существующих компиляторов не реализует целиком стандарт языка C++. Более того, очень немногие компиляторы ограничены только возможностями из ARM. Почти все они реализуют язык, кото- рый находится где-то между C++ из ARM и стандартным C++. Похожая ситуация наблюдается и с STL. Оригинальная версия STL была пред- ставлена в техническом отчете, написанном Александром Степановым и Мэнг Ли [SL95J. Теперь, однако, STL входит в стандартную библиотеку языка C++. Члены Комитета по стандартизации языка внесли изменения в определение STL так же, как они вносили изменения в определение базового языка C++. Кроме того, сложность заключается в следующем: несмотря на то что оригинальная реализация HP STL была написана теми же людьми, которые разработали формальную спецификацию библиотеки, она не полностью следовала формальной Спецификации,
516 Приложение А. Переносимость и стандартизация поскольку не было способа добиться этого. Определение STL требовало таких возмож- ностей языка, которые еще не были доступны на момент его написания. Реализация ИР была компромиссом между формальным проектом библиотеки и ограничениями технологии компиляторов на 1994 год. Сейчас, в 1998 году, ни одна из существующих реализаций STL не соответствует полностью ни первоначальному определению Степанова-Ли, ни определению из стан- дарта языка C++. В частности, потому что с формальной точки зрения стандарт все еще является черновиком и до недавнего времени в него продолжали вносить изме- нения. Попытка отслеживать изменяющийся стандарт была похожа на состязание между Алисой и Черной Королевой: “Здесь, знаешь ли, приходится бежать со всех ног, чтобы только остаться на том же месте”. Большинство разработчиков библиоте- ки предпочли проявить осторожность и ничего не трогать, пока стандарт языка С++ официально не станет окончательным. Вторая проблема состоит в том, что даже сегодня ни один компилятор C++ не под- держивает все возможности языка, используемые в STL. (Поставщики компилято- ров тоже должны быть осторожны!) Если какая-то возможность языка недоступна, то некоторые возможности библиотеки должны быть либо отброшены, либо реализо- ваны нестандартным образом. Эти ограничения и обходные пути, к сожалению, зача- стую документированы плохо или не документированы вообще. Большая часть STL вполне устойчива в том смысле, что не зависит от ограничений компилятора и не была изменена Комитетом по стандартизации. Вы можете пола- гаться на ее идентичность в любой реализации. Оставшаяся часть, однако, гораздо менее устойчива и иногда различия между реализациями довольно существенны. К счастью, эта путаница в основном проявляется только на границах. Изменения в STL и трудности реализации не влияют на дизайн ядра библиотеки. Основные прин- ципы обобщенного программирования и большая часть кода STL остаются без изме- нений независимо от того, какой компилятор и какую реализацию библиотеки вы используете. Большая часть изменений сосредоточена в нескольких проблемных об- ластях. В приложении описаны проблемы переносимости, о которых следует иметь пред- ставление: компоненты, реализованные разными способами; места, где ограничения компилятора не позволили реализовать библиотеку в соответствии с формальным определением; а также компоненты, определения которых изменились, и изменения в языке, влияющие на использование STL. АЛ. Изменения в языке А. 1.1. Модель компиляции шаблонов С тех пор как шаблоны были впервые добавлены в язык C++, их раздельная компи- ляция стала частью определения языка в целях использования шаблонов функций и шаблонов классов так же, как обычных функций и классов. То есть вы можете объя- вить обычную функцию в одном исходном файле и использовать ее в другом исход- ном файле. Можно компилировать два файла независимо друг от друга при условии, что два объектных файла будут скомпонованы. ARM требует раздельной компиляции шаблонов, и хотя некоторые детали изме- нились, того же требует стандарт языка C++. Версия 3.0 компилятора AT&T Cfront
А.1. Изменения в языке 517 использовала раздельную компиляцию шаблонов с ограничениями, что поддержива- ют и многие другие компиляторы. К сожалению, существующие реализации раздельной компиляции ощутимо раз- личаются. Некоторые включают в исходный код специальные описания # pragma, другие требуют, чтобы имена исходных файлов соответствовали специальному со- глашению, а многие компиляторы вообще не поддерживают раздельную компиля- цию шаблонов. Ваш компилятор может поддерживать ту или иную схему раздельной компиля- ции, однако при переносе кода на другие компиляторы могут возникнуть проблемы. Вероятно, пройдет несколько лет, прежде чем все компиляторы станут поддерживать раздельную компиляцию в соответствии со стандартом языка C++. В настоящее время единственное переносимое решение таково: определение шаб- лона функции, а не только его прототип, должно быть непосредственно перед исполь- зующей его функцией. Практически это означает, что шаблоны функций и шаблоны классов должны быть определены в заголовочных файлах (*. h) и что для использо- вания шаблона нужно ввести соответствующий заголовочный файл. Именно поэто- му все существующие реализации STL состоят большей частью — или даже цели- ком — из заголовочных файлов. А. 1.2. Параметры шаблона по умолчанию У функций в языке C++ могут быть аргументы по умолчанию, параметры по умолча- нию могут быть и у шаблонов классов. Если вы объявляете шаблон класса как template <class A. class В = А> class X { }. то можете пропустить второй параметр шаблона X при его инстанцировании, кото- рый, если не указан явно, получает по умолчанию значение А. Таким образом, тип X<int> идентичен типу X<int, int>. Все контейнерные классы STL используют параметры шаблона по умолчанию. Например, у шаблона vector есть два параметра: тип объектов, хранящихся в vector, и “аллокатор”, параметризующий стратегию распределения памяти для vector. Вто- рой параметр шаблона является параметром по умолчанию, так как в большинстве случаев, когда вы используете vector, не нужно использовать нестандартную страте- гию распределения. В настоящее время не все компиляторы C++ полностью поддерживают параметры шаблона по умолчанию. Некоторые компиляторы поддерживают их ограниченно (на- пример, допускают использование значений по умолчанию, только если оно не зави- сит от других параметров шаблона), другие вообще не поддерживают. Поскольку STL полагается на подобные параметры шаблона, то ее нельзя реализовать для таких ком- пиляторов в точном соответствии со спецификацией. Если компилятор не поддерживает данную возможность языка, то возможны два принципиально разных способа реализации класса, у которого должен быть параметр шаблона по умолчанию: либо требовать, чтобы пользователь всегда указывал пара- метр, либо же вообще удалить параметр. Снова рассматривая в качестве примера vector, в первом случае нужно было бы всегда писать vectorcint, allocator<int> >,
51 g Приложение А. Переносимость и стандартизация даже если вы хотите использовать аллокатор по умолчанию. Во втором случае можно было бы написать просто vector<int>, но тогда невозможно воспользоваться альтер- нативным аллокатором. Ни один из этих компромиссов не может быть полностью удовлетворительным. Оригинальная версия HP STL, написанная, когда компиляторы не обеспечивали параметров шаблонов по умолчанию, использовала второй вариант. В последующих реализациях STL использовались оба варианта. А. 1.3. Шаблоны-члены Когда шаблоны впервые появились в языке C++, можно было использовать в виде шаблонов только две вещи: глобальные классы и глобальные функции, то есть объяв- ления шаблонов не могли появляться внутри классов. Это значит, что функции-чле- ны не могли быть шаблонами функций. Сейчас ограничение оказалось ненужным, и стандарт разрешает делать невирту- альные функции-члены*) шаблонами функций. Шаблоны функций-членов вызыва- ются так же, как и обычные шаблоны функций. Компилятор выводит параметры шаб- лона из типов аргументов, с которыми вызывается функция, и автоматически создает соответствующий экземпляр функции-члена. Конструктор класса представляет собой функцию-член, и поэтому, как и другие функции-члены, он может быть шаблоном. Данный метод использования шаблонов- членов является одним из самых важных. У вспомогательного класса pai г, например, есть ‘‘обобщенный конструктор копирования” template <class Т1. class Т2> template <class U1, class U2> pair<Tl, T2> pair(const pair<U1, U2>&), Это позволяет создать pai r<T 1, T2> из любой pai r<U1, U2>, если тип U1 можно преобра- зовать к Т1, a U2 — к T2. (Подобный синтаксис выглядит странновато, но он корректен. Ключевое слово template нужно использовать дважды, потому что требуются два списка параметров шаблона: один для самого шаблона pai г, а второй — для конструк- тора как шаблона-члена.) Контейнерные классы STL повсеместно используют шаблоны-члены. Кроме того, основные точки их использования находятся в конструкторах. Можно сконструиро- вать контейнер из диапазона итераторов, а шаблоны-члены позволяют использовать диапазоны итераторов любого типа, который является моделью Итератора Ввода. Вы можете, например, создать vector V, который содержит те же элементы, что и list L, написав: vector<T> V(L begin(), L end()), Это допустимо, если тип значения L можно преобразовать к Т. Аналогично, у кон- тейнеров функции-члены insert также получают диапазон итераторов и являются шаблонами-членами. Такая функциональность недоступна в тех компиляторах, которые не поддержи- вают шаблоны-члены. Если ваш компилятор именно таков, то вы не сможете напи- сать полностью обобщенный конструктор, позволяющий создавать vector из диапа- )По юхническим причинам, которые описаны в The Design and Evolution of C+ + [Str941, виртуальные функции-члены по-прежнему не могут быть шаблонами.
А.1. Изменения в языке 519 зона Итераторов Ввода. Можно только перегрузить конструктор vector для несколь- ких избранных типов итераторов — видимо, для тех, которые разработчик вашей биб- лиотеки посчитал самыми полезными. Если ваш компилятор не поддерживает шаб- лоны-члены, то нужно узнать, каковы эти избранные типы. В большинстве случаев возможно, по крайней мере, конструировать контейнер из диапазона указателей и из диапазона собственных итераторов контейнера. Конструктор-шаблон в pai г существует отчасти лишь для удобства. Например, стан- дартный контейнер тар — это Ассоциативный Контейнер, элементы которого имеют тип paircconst Key, Data>. Как и все остальные Ассоциативные Контейнеры, у тар есть функция-член, которая позволяет вставить один элемент в контейнер. Вы пишете: М insert(p) Здесь р — объект того же типа, что и элементы М, то есть р — объект типа pai rcconst Key, Data>. Вопрос заключается в том, как сконструировать р. Используя конструктор-шаблон, можно вставить элемент в тар, не выписывая кон- структор pai г полностью. Можно написать так: М insert(make_pair(k, d)) Таким образом создается объект типа pai r<Key, Data>, который преобразуется к типу paircconst Key, Data> и вставляется в тар. Без конструктора-шаблона это преобразо- вание невозможно. Для компилятора, у которого отсутствует поддержка шаблонов- членов, приходится использовать менее удобную форму: М insert(pair<const Key, Data>(k, d)) A. 1.4. Частичная специализация Сразу же после появления шаблонов в языке C++ стало возможным специализиро- вать шаблон класса. Если у вас есть класс Х<Т>, но по какой-то причине общее опреде- ление X не применимо к некоторому конкретному типу, то можно предоставить дру- гое определение X специально для этого типа. Например, можно было бы реализовать X<tnt> гораздо эффективнее, чем обобщенную версию. Класс X<int> не обязательно связан с обобщенным классом Х<Т> и может иметь совершенно другой набор функ- ций-членов. Специализация шаблонов классов сейчас расширена до частичной специализации. В примере демонстрируется, как дать шаблону другое определение не для одного типа, а для целой категории. template <class Т> class X < // Самая общая версия t template <class Т> class Х<Т*> { // Версия для общих указателей }. template class X<void*>
520 Приложение А. Переносимость и стандартизация // Версия для одного особого типа указателя } Полная специализация не всегда использует тот же синтаксис, что и частичная. Такое изменение было сделано в процессе стандартизации. Если вы используете компиля- тор, который соответствует стандарту языка C++, то нужно писать template <> class X<void*> как сказано выше, но если вы используете более старый компилятор, то вместо этого следует писать class X<void*> без ключевого слова template. Использование макроопределения — единственный способ написания полной специализации, которая соответствует как старым, так и новым правилам. Частичная специализация часто полезна как форма оптимизации. Например, иногда можно написать специальную версию класса, оптимизированную для указателей. Кроме того, частичная специализация допускает некоторые методы программирова- ния, которых невозможно добиться другим путем. STL полагается на эти методы. Во-первых, для эффективного использования памяти STL содержит специализи- рованную версию контейнерного класса vector: vector<bool>. Это пример частичной специализации: как и у всех последовательностей STL, у vector есть два параметра шаблона: тип значения и аллокатор. Класс vector<bool> является частичной специа- лизацией, потому что он задает специфический тип первого параметра шаблона, тог- да как аллокатор остается полностью обобщенным. Для компиляторов, не поддержи- вающих частичную специализацию, реализации STL обычно объявляют класс bit.vector, что является лишь временным обходным путем. Он не входит в стандарт языка C++ и, вероятно, исчезнет, как только частичная специализация будет реали- зована повсеместно. Гораздо важнее, что один из основных конструктивных блоков STL — iterator_t raits — целиком полагается на частичную специализацию. Класс iterator traits, описанный в разделе 3.1, является основным механизмом доступа к информации об ассоциированных типах итераторов. Для любого итератора типа I 1terator_traits<I> value_type — тип значения I, а 1terator_traits<I> difference_type — разностный тип I. Для этого обязательно нужна частичная специализация, потому что мы не можем определить iterator_traits для указателей так же, как делали это для итераторов, описанных как классы. Можно использовать iterator_t raits в ваших собственных программах. Данный шаблон применяется в различных частях STL. • Адаптер итератора reve rse_ite rato г получает единственный параметр шаблона, Iter, Двунаправленный Итератор. Адаптер использует механизм iterator_t raits
А.1. Изменения в языке 521 таким образом, что у reverse_.itе rator<Iter> оказываются те же разностный тип и тип значения, что и у Iter. • Алгоритмы distance (с. 190), count (с, 222) и count.if (с. 224) оперируют с диапазонами итераторов ввода, возвращая значение типа typename iterator_t'raits<lnputlter>- ‘difference_type. Оригинальная версия HP STL не полагалась на частичную специализацию, и в ней не было класса ite rator_t raits. Механизм доступа к информации о типе итераторов, три информационные функции distance_type, value_type и iterator_category — был довольно неудобным. Названные функции больше не входят в стандарт языка C++. Большинство реализаций STL еще поддерживают их, но они не рекомендуются к ис- пользованию и в конечном счете будут удалены. Если ваш компилятор не поддерживает частичную специализацию, то вы не смо- жете реализовать все возможности iterator_t raits. Вам, видимо, придется использо- вать более старый механизм из HP STL, а также старые версии count, count.if и reverse_iterator. “Частичная специализация” функций В стандарте языка C++ есть возможность, очень похожая на частичную специализа- цию: частичное упорядочение шаблонов функций. Шаблонные функции, как и другие функции, можно перегрузить. Тогда оказыва- ется возможным вызвать функцию так, что несколько перегруженных версий будут совпадать. Предположим, например, что вы объявили функции template <class Т> void f(T) и template <class U> void f(U*) Какую версию вы получите, если напишете f ((int *) 0)? Вызов будет соответствовать первой версии (I — это int*) или второй (U — это int). Когда шаблоны только появились, такой вызов функции был недопустим вслед- ствие более одного точного соответствия. Новое правило ослабляет это ограничение. Если вызов функции соответствует одному из нескольких перегруженных шаблонов функций, компилятор будет выбирать наиболее специализированный из них. В рас- сматриваемом случае, например, вторая версия f более специализирована, чем пер- вая. Первая соответствует любому типу, а вторая — только указателям. STL использует частичное упорядочение функций только в одном месте. Функция swap (с. 243) обменивает содержимое любых двух переменных. Кроме того, в STL объявлены более специализированные версии swap для каждого стандартного кон- тейнерного класса. Если вы пишете swap( v1, v2), где v1 и v2 — векторы, то будет выз- вана функция template <class Т class Allocator void swap(vector<T, Allocator>&. vector<T, Allocator>&) а не более общая функция template <class T> void swap(T& T&)
522 Приложение А. Переносимость и стандартизация Это важно, потому что общая версия swap должна использовать присваивание, а при- сваивание одного вектора другому — медленная операция, поскольку должна копировать все элементы вектора. Специализированная версия работает довольно быстро и напрямую с внутренним представлением vector. Для компиляторов, которые не поддерживают частичное упорядочение шаблонов функций, STL не определяет специализированную версию swap, что не повлияет на синтаксис вашего кода, потому что по-прежнему можно использовать swap для обме- на двух векторов, однако некоторые программы будут работать гораздо медленнее, чем следовало бы. А. 1.5. Новые ключевые слова В языке C++ с момента первой публикации ARM появилось больше десятка новых ключевых слов. Два из них особенно важны для STL и программ, использующих шаблоны: explicit и typename. Ключевое слово explicit Назначение explicit заключается в том, чтобы запретить некоторые виды автомати- ческого преобразования типов. Обычно если у класса X есть конструктор, принимаю- щий единственный аргумент типа Т, то компилятор C++ будет автоматически ис- пользовать такой конструктор для преобразования значений типа Т в тип X. Так, на- пример, если у вас есть функция f, принимающая аргумент типа X, и если t имеет тип Т, то можно написать f (t), и делать преобразование вручную нет необходимости. Иногда именно это и нужно. Однако в некоторых случаях автоматическое преоб- разование может привести к неожиданным результатам. Если f полагает, что ее аргу- ментом будет, например, vector<st ring>, то действительно ли вы хотите иметь воз- можность написать f (3)? В конце концов, vector<stnng>(3) — вполне разумный вы- зов конструктора. Описание конструктора с единственным аргументом с помощью explicit запре- щает такое автоматическое преобразование. Вы по-прежнему можете использовать конструктор, но должны писать его явно. В основном стандарт языка C++ объявляет для контейнеров конструкторы, принимающие единственный аргумент, с помощью ключевого слова explicit. Так как весь смысл explicit заключается в запрете автоматического действия, это означает, что некоторые программы, соответствующие старым правилам, перестанут быть корректными при использовании компилятора и библиотеки, соответствующих стандартам. Нельзя писать f (3) для вызова функции, типом аргумента которой явля- ется vector<string>. Если вы действительно собрались передать в качестве аргумента вектор из трех пустых строк, то придется сделать это явно: f (vector<string>(3)). То же относится к объявлению переменных. Вместо vector<string> v = 3, следует писать vector<string> v(3), или vector<string> v = vector<string>(3),
АЛ. Изменения в языке 523 Ключевое слово typename Ключевое слово explicit влияет лишь на некоторые довольно запутанные языковые конструкции, например запрещает те, в которых вообще не было необходимости. Наоборот, typename применяется во многих контекстах, весьма существенных для обобщенного программирования на языке C++. Если вы пишете нетривиальную про- грамму на языке C++ с помощью шаблонов, нужно понимать, как использовать typename. Основная проблема чисто техническая и довольно простая. Когда компилятор встречает выражение, использующее параметр шаблона, то должен ли он обрабаты- вать это выражение, как если бы оно ссылалось на имя типа либо на что-то еще (на- пример, на функции-члены или просто члены класса)? Например, в функции template <class Х> void f(X) { X Т(х), } должен ли компилятор интерпретировать Т как тип (при этом X. : Т является вызовом конструктора: он конструирует анонимный временный объект и сразу же отбрасыва- ет его) или как статическую функцию-член либо просто статический член класса? Аналогично, в функции template <class Х> void g(X) { X Т t. } нужно ли интерпретировать Т как тип (при этом функция будет использовать конст- руктор по умолчанию для создания объекта t) или же компилятору следует интер- претировать Г как статическую функцию-член либо просто член класса (при этом X Т t — синтаксическая ошибка)? Последний случай особенно опасен. После инстанцирования g компилятор может понять, является ли X:: Т классом, функцией-членом, просто членом класса или чем- то еще. Было бы, однако, обидно, если бы не было способа проверить шаблон на син- таксическую корректность без его инстанцирования: в таком случае раздельная транс- ляция шаблонов стала бы невозможна. Язык C++ был разработан так, чтобы можно было проверить синтаксис шаблона. Соответственно, в C++ есть очень простое правило выбора интерпретировать имя вроде X: . Т как имя типа или нет. Компилятор всегда предполагает, что имя не ссыла- ется на тип, если явно не указано обратное. Ключевое слово typename предоставляет способ указать компилятору, что он дол- жен интерпретировать конкретное имя как имя типа. Правильный способ написать функцию g таков: template <class Х> void g(X) { typename X T t, } Предваряя X * T ключевым словом typename, вы указываете компилятору, что он дол- жен интерпретировать X: :Т как имя типа. Отметим, что нужно использовать typename
524 Приложение А. Переносимость и стандартизация всякий раз, когда вы обращаетесь к этому типу, то есть следует писать: template <class Х> void gl(X) { typename X T t, typename X T* pt, } Второе слово typename столь же обязательно, как и первое. Основное правило заключается в следующем: вы должны использовать type name всякий раз, чтобы компилятор мог понять — нечто является именем типа, то есть вы должны использовать его для имени, которое: 1) является вложенным внутрь имени некоторого другого типа {qualified пате); 2) зависит от параметра шаблона, то есть изменяется в зависимости от парамет- ров инстанцирования шаблона. Например, в функции g вложенным именем будет X: :Т (префикс X. :), зависящее от шаблона X. Аналогично, выражение вроде Y<X>:: Т также является вложенным име- нем, которое зависит от параметра шаблона X. Что это значит для программ, которые используют STL? Во-первых, если вы сами пишете новые компоненты STL или используете существующие, написанные други- ми программистами, вам придется часто использовать typename — даже когда вы де- лаете что-то совершенно безобидное, например пишете шаблонную функцию, кото- рая работает с vectoг<Т> и должна обращаться к его итераторам. Например, template <class Т> void f(vector<T>& v) { typename vector<T> iterator i = v begin(), } Как это ни странно, но typename здесь действительно требуется. Несомненно, vector<T>:: iterator всегда означает вложенный тип итератора vector, но для компи- лятора это просто еще одно вложенное имя, зависящее от параметра шаблона Т. Если вы явно не укажете компилятору, что vectoг<Т> •: iterator — имя типа, он сам не пой- мет. Это применимо также к ite rato r_t raits. В разделе 3,1.5 мы видели, что нужно было использовать typename при написании класса iterator_t raits; аналогично, мы почти всегда должны применять typename, если используем iterator_traits. У алгоритма count (с. 222), например, такая сигнатура: template <class Inputiterator, class EqualityComparable> typename iterator_traits<lnputlterator> difference_type countdnputlterator first. Inputiterator last, const EqualityComparable& value) Стоит сказать, что typename создает проблему переносимости. Некоторые компиля- торы (те, что соответствуют стандарту языка C++) требуют его, а другие (написан- ные до того, как ключевое слово typename было добавлено в язык) запрещают его ис- пользование. На 1998 год оба вида компиляторов были одинаково распространены.
А.2. Изменения в библиотеке 525 Единственный способ написать программу, которую вы сможете использовать как с компилятором, требующим typename, так и с компилятором, запрещающим typename, — применить препроцессор. Можно было бы, например, определить макрос TYPENAME, который раскрывается в слово typename для одних компиляторов и в пустое место — для других. Ключевое слово typename представляет собой техническую деталь, введенную для специальной технической цели. К сожалению, эту деталь нельзя игнорировать. Если вы делаете ошибку в использовании typename, компилятор всегда укажет вам на нее, выдав соответствующее сообщение. Пропущенный по ошибке typename не превра- тит работающую программу в другую работающую программу, считающую что-то другое. А.2. Изменения в библиотеке А.2.1. Аллокаторы Как описано в разделе 9.4, все контейнеры STL параметризованы используемой ими схемой распределения памяти. Это свойство иногда полезно, но исторически оно оказалось одной из наименее устойчивых частей STL. В разное время в STL исполь- зовались четыре схемы аллокаторов: аллокаторы из HP STL, аллокаторы из SGI STL и две различные версии аллокаторов, описанных в разных версиях проекта стандарта языка C++. Переносимость проблематична по двум причинам. Во-первых, стандарт языка C++ предоставляет разработчикам библиотеки много свободы в использовании контей- неров-аллокаторов. Во-вторых, многие реализации STL в действительности не пре- доставляют ни одной из этих четырех схем. Версию аллокаторов из HP STL нельзя реализовать без довольно запутанной возможности языка, которая называется “шаб- лон в качестве параметра шаблона”Ж), а версии из проектов стандарта нельзя реализо- вать без шаблонов-членов. Если ваш компилятор не поддерживает шаблоны-члены, то вы не можете использовать аллокаторы, описанные в стандарте языка C++. В луч- шем случае придется использовать какую-нибудь специализированную компромисс- ную версию. Если вы заботитесь о переносимости, то надежнее всего будет вообще не использо- вать аллокаторы, за исключением аллокатора по умолчанию. Аллокатор всегда явля- ется параметром шаблона по умолчанию, поэтому никогда не придется обращаться к нему по имени напрямую. Если вам нужно написать свой аллокатор, то вниматель- но прочитайте документацию и выясните, какую схему аллокаторов использует реа- лизация вашей библиотеки. В частности, если ваш компилятор не поддерживает шаб- лоны-члены, убедитесь, что вы понимаете все необходимые обходные пути и ограни- чения. Придется также подготовиться к тому, что понадобятся некоторые изменения при переносе на другой компилятор или другую библиотеку. Самая очевидная новинка в области аллокаторов, определенных в стандарте язы- ка C++, — это так называемые экземпляры аллокаторов. Функции-члены allocate и deallocate связаны с конкретными экземплярами объектов-аллокаторов. Если память } В .пой книге указанная возможность нигде больше нс упоминается, потому что не используеия в других частях STL. См. Язык программирования C++ | Slr97 ], раздел В 13 3 (в оригинале раздел С 13 3)
526 Приложение А. Переносимость и стандартизация выделена с помощью объекта-аллокатора а, то освобождать ос нужно с помощью объекта-аллокатора, равного а. Разные аллокаторы могут ссылаться на различные области памяти. Недостаточно, чтобы тип аллокатора входил в определение типа контейнера. Каж- дый контейнерный объект должен содержать конкретный экземпляр аллокатора. Даже если у двух контейнерных объектов одинаковый тип, они не обязательно одинаково распределяют память. Нет смысла в изменении аллокатора контейнера после того, как контейнер был создан. Однако должен быть способ управления тем, какой экземпляр аллокатора будет использоваться контейнером. Если контейнер параметризирован аллокатором, то все конструкторы этого контейнера должны получать аллокатор в качестве аргу- мента. У всех контейнеров STL, определенных в стандарте языка C++ (vector, list, deque, set, map, multiset и multimap), именно такие конструкторы. У каждого из них есть аргумент типа Allocator. В каждом случае он является последним аргументом конструктора, и в каждом случае у него есть значение по умолчанию: Allocator (/Таким образом, можно избежать использования экземпляров аллокаторов, просто не упо- миная аллокаторы вообще. Так, например, запись vector<int> V(100, 0), эквивалентна записи vector<int, allocator<int> > V(100, 0. allocator<int>()). A.2.2. Адаптеры контейнера В STL описано три адаптера контейнеров: stack, queue и priority_queue. Они не явля- ются Контейнерами. Они спроектированы так, что обеспечивают не полный интер- фейс Контейнера, а только его ограниченное подмножество. Адаптер контейнера представляет собой только “обертку” для какого-либо базового контейнера, который задается параметром шаблона. При этом изменился способ задания параметра. В оригинальной версии HP STL адаптер stack получал единственный параметр шаблона: тип базового контейнера stack. Если требовалось создать стек значений типа int, используя deque в качестве базового контейнера, нужно было объявить тип таким образом: stack<deque<int> > Это недвусмысленная, но не совсем понятная запись. Кажется, будто значения в сте- ке имеют тип deque, а не int. Более того, вам приходится явно описывать базовый контейнер, который должен быть всего лишь деталью реализации. В стандарте языка C++ произошли изменения, и теперь stack получает два пара- метра шаблона. Первый из них — тип объекта, который размещается в стеке, а вто- рой — базовый контейнер. Параметр “базовый контейнер” имеет значение по умолча- нию. Для stack значением по умолчанию является deque. Стек из значений типа int, таким образом, записывается просто как stack<int>. Данное изменение приводит к двум проблемам, связанным с переносимостью. Во- первых, большинство реализаций STL изменили адаптеры контейнера, чтобы соот- ветствовать стандарту языка C++. В зависимости от того, с какой реализацией биб- лиотеки вы работаете, возможно, придется применить более раннюю версию адапте- ров контейнеров. Во-вторых, если ваш компилятор не поддерживает параметры
А.2. Изменения в библиотеке 527 шаблона по умолчанию, то вы не сможете использовать адаптеры контейнера так, как описано в стандарте языка C++. Вам придется прочитать документацию, чтобы вы- яснить, как разработчик вашей библиотеки обошел это ограничение. Олин из часто встречающихся в данном случае вариантов состоит в том, что вам придется явно ука- зывать оба параметра шаблона. Например, придется объявить стек значений типа int таким образом: stack<int, deque<int> > А.2.3. Второстепенные изменения библиотеки В процессе стандартизации интерфейс STL был изменен и расширен в нескольких второстепенных аспектах. Новые алгоритмы В стандарт языка C++ входят три алгоритма, которые не были определены в ориги- нальной версии HP STL: find_first_of, похожий на функцию strpbrk библиотеки языка Си; find_end, похожий на алгоритм search, и search_n. Кроме того, в стандарт языка C++ включены три обобщенных алгоритма: uninitialized_copy, uninitialized_fill и uninitialized_fill_n, а также две функции распределения временной памяти: get_temporary_buffer и return_temporary_buf fer. Они входили в реализацию HP, но не были документированы. Эти специализированные функции полезны, если вы пишете свои собственные контейнеры или адаптивные алгоритмы. Изменения в интерфейсе итератора В стандарте языка C++ сделано одно небольшое добавление к итераторам по сравне- нию с их определением в оригинальной версии HP STL. У всех итераторов (за ис- ключением Итераторов Вывода) теперь определена функция-член operator-^ Как и для указателей, 1->т означает то же самое, что и (*1). т. Это особенно удобно для тар и multimap, поскольку элементы этих контейнеров являются парами (pair). Если 1 — итератор, типом значения которого является pai r<const Т, Х>, то можно написать i->f i rst или i->second, как если бы i был указа- телем. Изменения в интерфейсе Последовательности У всех Последовательностей теперь есть три новые функции-члена, не входившие в оригинальную версию HP STL: resize, которая удаляет или добавляет элементы в конец, чтобы Последовательность стала заданного размера; assign, которая являет- ся обобщенной операцией присваивания; и clear, которая является сокращением для erase(begin(), end()). Кроме того, был изменен тип возвращаемого значения функ- ции-члена erase. В HP STL данная функция возвращала void, а в стандарте языка C++ —iterator. Возвращаемое значение erase представляет собой итератор, указы- вающий на место сразу за стертым элементом (элементами). Функциональный объект multiplies Наконец, в реализации HP был функциональный объект times, вычислявший про- изведение двух аргументов. В стандарте языка C++ его имя было изменено на multiplies.
528 Приложение А. Переносимость и стандартизация К сожалению, нет хорошего способа поддержки ни старого имени, ни нового. Имя было изменено, потому что times конфликтовал с именем одного из заголовочных файлов в системе UNIX, то есть сохранение обоих имен сделало бы само переимено- вание бессмысленным. Если вы переносите код между ранней реализацией STL и более новой, то вам, вероятно, придется внимательно следить за именем данного функцио- нального объекта. А.З. Именование и компоновка Что значит имя? Роза пахнет розой, Хоть розой назови ее, хоть нет Вопросы именования могут быть тривиальными, но они являются главным отличи- ем STL из стандарта языка C++ от оригинальной версии HP STL. В оригинальной версии, например, нижеследующая программа является полной и корректной (хотя и не очень интересной): //include <vector h> int main() { vector<int> v, } Однако с точки зрения стандарта можно указать две причины недопустимости этой программы. 1. В языке C++ теперь есть система пространств имен (namespace), и по стан- дарту vector определен не в глобальном пространстве имен, а внутри про- странства имен std. Проще говоря, в стандарте языка C++ нет класса, который называется vector. Вместо этого есть класс, который называется std * vector. 2. В соответствии со стандартом класс std:: vector объявлен не в заголовочном файле <vector. h>, а в заголовочном файле <vector>. Предлагается несколько способов модификации этой программы так, чтобы она со- ответствовала всем правилам стандарта языка C++. Вот один из них: //include <vector> int main() { std vector<int> v, } а вот другой: //include <vector> int main() { using std vector, vector<int> v,
А.З. Именование и компоновка 529 В первой версии мы явно обращаемся к std::vectoг по полному имени. Во второй версии строка using std vector, (которая называется объявление использования (using declaration)) означает, что ком- пилятор должен интерпретировать имя vector как сокращение для std: vector. (Есть также третий метод: вместо объявления using можно было бы написать ди- рективу using namespace std, которая импортирует все имена из пространства имен std. Использование этого ме- тода не рекомендуется.) За одним небольшим исключением все компоненты в стандартной библиотеке язы- ка C++ определены в пространстве имен std. Когда используется контейнер, алго- ритм или любое другое имя из стандартной библиотеки, вы должны либо явно опре- делять его с префиксом std •:, либо импортировать его с помощью объявления using Исключением из этого правила является набор шаблонов, которые определяют операторы 1 =, >, <= и >= в терминах операторов == и <. Они описаны не в самом про- странстве имен std, а во вложенном пространстве имен std:: rel_ops. С именами заголовочных файлов все немного сложнее. В стандарте все заголовоч- ные файлы HP STL были переименованы. У новых имен заголовочных файлов, на- пример <vecto г>, отсутствует расширение “. h”. Однако простого взаимно однознач- ного соответствия между старыми и новыми именами нет. Это не только переимено- вание, но и реорганизация. Даже заголовочный файл <vector> является примером такой реорганизации. В HP STL шаблон vectoг<> был объявлен в заголовочном фай- ле <vector h>, а класс bit_vector — эквивалент специализации vector<bool> — был объявлен в <bvector. h>. Заголовочный файл <vector> содержит как шаблон, так и спе- циализацию. Таблица А.1 описывает примерное соответствие между заголовочными файлами старого стиля и стандартными заголовочными файлами. Более детальная информа- ция находится в части IV данной книги. В документации к каждой функции и классу указано, в каком стандартном заголовочном файле и заголовочном файле старого сти- ля они объявлены. Таблица А. 1. Соответствие между именами заголовочных файлов в стандарте языка C + + и в HP STL Стандартный заголовочный файл <algorithm> <deque> <functional> <iterator> <list> <memory> Соответствующий заголовочный файл(ы) HP STL <algo h> (большая часть) <deque.h> (все) <function.h> (большая часть) <iterator.h> (все) <list.h> (все) <defalloc h> (все) <tempbuf.h> (все) <iterator.h> (часть) <algo.h> (часть)
530 Приложение А. Переносимость и стандартизация Продолжение табл. А.1 Стандартный заголовочный файл Соответствующий заголовочный файл(ы) HP STL <numenc> <algo.h> (часть) <queue> <stack.h> (часть) <utility> <pair.h> (все) <function.h> (часть) <stack> <stack.h> (часть) <vector> <vector.h> (все) cbvector.h> (все) <map> <map.h> (все) <multimap.h> (все) <set> <set.h> (все) <multiset.h> (все) Тот факт, что имена стандартных заголовочных файлов совершенно отличаются от имен старого стиля, на самом деле способствует переносимости, а не препятствует ей. Он предоставляет разработчикам добавочную “зацепку”, которую они могут ис- пользовать для обратной совместимости. В стандарте не упомянуты имена старого стиля вроде <algo. h> и <list h>. От раз- работчиков библиотеки не требуется поставлять такие заголовочные файлы, но и не запрещается делать это. Разработчики могут — и делают так! — предоставлять заго- ловочные файлы с именами старого стиля, которые объявляют свое содержимое в гло- бальном пространстве имен, а не в пространстве имен std. Обычно в этом случае заго- ловочный файл <vector> содержит настоящую реализацию: namespace std ( template <class T, class Allocator^ class vector { }. } а заголовочный файл <vector. h> должен состоять только из двух строк: //include <vector> using std vector, Применение подобной схемы не требуется стандартом языка C++, но она допусти- ма и довольно часто используется. Одной из причин переименования заголовочных файлов STL как раз было сделать данную схему возможной. Она позволяет совер- шить постепенный переход к новому стилю имен заголовочных файлов и к явному использованию пространств имен.
Список литературы [Amm97] Ammeraal L. STL for C++ Programmers.John Wiley, Chichester, UK, 1997. [Аммераль Л STL для программистов на C++. M.: ДМК, 1999. 240 с.] [Aus97] Austern М. Н. The SGI Standard Template Library. Dr. Dobb’s Journal, 22(8):18-27, Aug 1997. [BFM95] Barreiro J., Fraley R. and Musser D. R. Hash tables for the Standard Template Library Technical Report X3J16/94-0218 and WG21/N0605. International Standard Organi- zation. Feb 1995. [Boo94] Booch G. Object-Oriented Analysis and Design with Applications. Benjamin Cummings, Redwood City, CA, 2nd edition, 1994. [Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на C++. М.; СПб.: “Изд-во Бином”; Невский Диалект, 1998. 560 с.] [Bre98] Breymann U. Designing Components with the C+ + STL: A New Approach to Programming Addison Wesley Longman, Harlow, UK, 1998. [CLR90] Cormen T. H., Leiserson С. E. and Rivest R. L. Introduction to Algorithms. MIT Press, Cambrige, MA, 1990. [Кормен T., Лейзерсон Ч., Ривест P. Алгоритмы: Построение и анализ. М.: МЦНМО, 1999. 960 с.] [Dur64] Durstenfeld R. Algorithm 235, random permutation. Communications of the ACM, 7(7):420,Jul 1964. [ES90] Ellis M. A. and Stroustrup B. The Annotated C+ + Reference Manual Addison-Wesley, Reading, MA, 1990. [FMR62] Fan С. T., Muller M. E. and Rezucha I. Development of sampling plans by using sequential (item by item) selection techniques and digital computers. Journal of the American Statistical Association, 57:387-402,1962. [GHG93] Guttag J. V., Horning J. J., Garland S. J., Jones K. D., Modet A. and Wing J. M. Larch Languages and Tools for Formal Specification. Springer-Verlag, New York, NY, 1993 [GS96] Glass G. and Schuchert B. The STL Primer. Prentice-Hall, Englewood Cliffs, NJ, 1996 [Hoa62] Hoare C. A. R. Quicksort. Computer Journal, 5(l):10-15,1962. [ISO98] International Organization for Standardization (ISO), 1 rue de Vareme, Case postale 56, CH-1211 Geneve 20, Switzerland. ZSO/7EC Final Draft International Standard 14882: Programming Language C++, 1998. [KM92J Kapur D. and Musser D. R. Tecton: A framework for specifying and verifying generic system components. Computer Science Technical Report 92-20, Rensselaer Polytechnic Institute, Jul 1992. Available via http://www.cs.rpi.edu/~musser/Tection [KM97] Koenig A. and Moo B. Ruminations on C++: A Decade of Programming Insight and Experience. Addison-Wesley, Reading, MA, 1997. [KMS82] Kapur D., Musser D. R. and Stepanov A. A. Tecton: A language for manipulating generic objects. In J. Staunstrup (ed.). Program Specification: Proceedings of Workshop, Aarhus, Denmark, volume 134 of Lecture Notes in Computer Science. Springer-Verlag, Berlin, 1982 [KMS88] Kershenbaum A., Musser D. R. and Stepanov A. A. Higher-order imperative programming Computer Science Technical Report 88-10, Renssalaer Polytechnic Institute, Apr 1988. Available via http://www.cs.rpi.edu/~musser/genprog html [Knu97] Knuth D. E. Fundamental Algorithms, volume I of The Ait of Computer Programming. Addison-Wesley, Reading, MA, 3rd edition, 1997. [Кнут Д. Э. Искусство програм- мирования. T. 1. Основные алгоритмы. М.: Вильямс, 2000. 720 с.]
532 ____________________Список литературы_________________________________ [Knu98a] Knuth D. E. Seminumencal Algorithms, volume II of The Art of Computer Programming. Addison-Wesley, Reading, MA, 3rd edition, 1998. [Кнут Д. Э. Искусство програм- мирования. T 2. Получисленные алгоритмы. М.: Вильямс, 2000. 832 с.] [Knu98b] Knuth D. Е. Sorting and Searching, volume III of The Art of Computer Programming. Addison-Wesley, Reading, MA, 2nd edition, 1998. [Кнут Д. Э. Искусство програм- мирования T. 3. Сортировка и поиск. М.: Вильямс, 2000. 832 с.] | Кое89] Koenig А. С Traps and Pitfalls. Addison-Wesley, Reading, MA, 1989. [KR78] Kernighan B. W. and Ritchie D. M. The C Programming Language. Prentice-Hall, Englewood Cliffs, NJ, 1978. [Керниган Б., Ритчи Д. Язык программирования Си / Пер. с англ, под ред. Вс. С. Штаркмана. СПб.: Невский Диалект, 2001.352 с.] [Lip91 ] Lippman S. В. C+ + Primer. Addison-Wesley, Reading, MA, 2nd edition, 1991. [LL98] Lippman S. B. and Lajoie J. C++ Primer. Addison-Wesley, Reading, MA, 3rd edition, 1998. [Липпман С. Б., Лажойе Ж. Язык программирования C++: Вводный курс. СПб.; М.: Невский Диалект; ДМК Пресс, 2001. 1104 с.] [ Mev97] Meyer В. Object- Oriented Software Construction. Prentice-Hall, New York, 2nd edition, 1997. [ M063] Moses L. E. and Oakford R. V. Tables of Random Permutations. Stanford U niversity Press, Stanford, CA, 1963. [M S89a] Musser D. R. and Stepanov A. A. The Ada Generic Library: Linear List Processing Packages. Springer-Verlag, New York, NY, 1989. [MS89b] Musser D. R. and Stepanov A. A. Generic Programming. In P. Gianni (ed.). Symbolic and Algebraic Computation: International Symposium ISSAC 1988, volume 358 of Lecture Notes in Computer Science, pages 13-25. Springer-Verlag, Berlin, 1989. [MS96] Musser D. R. and Saini A. STL Tutorial and Reference Guide: C++ Programming with the Standard Template Library. Addison-Wesley, Reading, MA, 1996. [Mus97] Musser D. R. Introspective sorting and selection algorithms. Software Practice and Experience, 27(8):983-993,1997. [Nel95] Nelson M. C++ Programmer's Guide to the Standard Template Library. IDG Books Worldwide, Foster City, CA, 1995. [Rob98] Robson R. Using the STL. Springer-Verlag, New York, 1998. [Sin69] Singleton R. C. Algorithm 347, an efficient algorithm for sorting with minimal storage. Communications of the ACM, 12:185-187, Mar 1969. [SL95] Stepanov A. and Lee M. The Standard Template Library. Technical Report HPL-95-11 (R.l). Hewlett-Packard Laboratories, Nov 1995. Available by anonymous FTP from butler.hpl.hp.com [Str86] Stroustrup B. The C++ Programming Language. Addison-Wesley, Reading, MA, 1986. [Страуструп Б. Язык программирования C++. M.: Радио и связь, 1991.352 с.] [Str94] Stroustrup В. The Design and Evolution of C++. Addison-Wesley, Reading, MA, 1994. [Страуструп Б. Дизайн и эволюция C++. М.: ДМК, 2000.448 с.] [Str97] Stroustrup В. The C++ Programming Language. Addison-Wesley, Reading, MA, 3rd edi- tion, 1997. [Страуструп Б. Язык программирования C++. 3-е изд. СПб.; М.: Невский Диалект; “Изд-во Бином”, 1999.999 с.; Страуструп Б. Язык программирования C++: Спец. изд. М.; СПб.: “Изд-во Бином”; Невский Диалект, 2001. 1099 с.] [V1194] Vilot М. J. An introduction to the Standard Template Library. C++ Report, 6(8):22-29, Oct 1994. [Wil64] Williams J. W. J. Algorithm 232, heapsori. Communications of the ACM, 7:347-348,1964.
Алфавитный указатель А accumulate, алгоритм 284-286 adjacent-difference, алгоритм 289-291 adjacent-find, алгоритм 42, 69,211-213 advance, алюритм 55-58, 192-193 allocator, класс 196-198 assign, функция-член в deque 456 в Iist 440 в slist 447 в vector 434 в Последовательности 527 assignable_base, класс 47 В back, функция-член в queue 510 в Последовательности с Концевой Вставкой 93, 155 back_insert_iterator, класс 345-347 для Последовательности 93 как адаптер 63 oase, функция-член 363 begin, функция-член в Контейнере 141 для block 79 для контейнеров 86-88 bidirectioral_iterator_tag, класс 58, 189-190 binary_compose, класс 425-426 binary_function, класс 71-72,369-370 binary_negate, класс 421-423 binary_search, алгоритм 306-307 bindist, функция 414 bind2nd, функция 415-416 oinderlst, класс 75, 415 binder2nd, класс 75,415-416 block, класс 78-81 bool, тин и функциональные обьекты 71 C++, язык книги 515 новые ключевые слова 522-525 отсутствие поддержки концепций 33, 57 стапдар1изация 515 шаблоны 29, 516-522 capacity, член класса 431 char_type, член класса вistream_iterator 352 в istreambuf-iterator 357 вostream_itcrator 355 в ostreambuf-iterator 359 clear, функция 527 compose!, функция 423-424 compose2, функция 425-426 corst_iterator, тип для block 79-80 для кошейнеров 86-88 const_mem_fun_ref_t. класс 408-410 const_mem_fun_t, класс 406-408 const_mem_fun1_ref_t, класс 412-414 const_mem_fun1_t, класс 410-412 const_node_wrap, класс 62-63 const_pointer, тип для block 79-80 для Аллокатора 179 для Контейнера 90, 139 const_reference, тип для block 79-80 для Аллокатора 179 для Контейнера 90, 138 const_reverse_iterator, тип 87-88, 145 construct, алгоритм 198-199 const-тин и Присваиваемый 101 сору, алгоритм 21, 70,239-241 сору_backward, алгоритм 241-243 count, алгоритм 96, 159,222-224 count_if, алгоритм 224-226 D delete, оператор 199 deque, класс 450-456 destroy, алгоритм 181, 199-200 difference_type, тип для block 79-80 для Аллокатора 179 для Итератора Ввода 110 для итераторов 54 для Контейнера 90, 139 distance, алгоритм 190-192 distance.type, тип 60 divides, класс 75,374-375 empty, функция-член в priority_queue 513 в queue 510 в stack 507 в Контейнере 141 для block 83 end, функция-член в контейнере 141 для block 79 для контейнеров 86-88 end_of_storage, указатели 428 equal, алгоритм 228-230,358 equal_range. алгоритм 312-315 в Ассоциативном Контейнере 159 в Сортированном Ассоциативном Контейнере 169-170 equal_to. класс 75.378-379 erase, функция-член 259 в Ассоциативном Контейнере 95-96, 159-160 в Последовательности 91-92. 150-151 erase_after, функция-член 443, 448 even, класс 67-68, 71 explicit, ключевое слово 522
534 Алфавитный указатель F FIFO, структура данных 508 f 111, ал горитм 254-255 fill.", алгоритм 255 find, ал горит м 29, 208-209 ограничения 66 f j rd. функция-член в Ассоциативном Контейнере 158 firo.end, алгоритм 217-219, 527 Гj ncTfi rst.of, алгоритм 213-214, 527 f iN.if, алгоритм 67-68, 73-75,209-211 inish, указатели 428 <or_each. алгоритм 226-228 furward„iterator_tag, класс 58, 188-190 front, функция-член в queue 510 в Последовательности 151 в Последовательности с Начальной Вставкой 93, 154 rroi’t_irsert_iterator, класс 342-345 для Последовательности 93 как адаптер 63 G ger er ate, алгоритм 70, 256 generate.n, алгоритм 256-257 get.allocator, функция-член в deque 454 вhash.map 492 в hash.multimap 503 в hashjnultiset 498 в hash.set 486 в list 438 в map 468 Binultimap 480 в multiset 474 в set 461 в slist 446 в vector 432 get_temporary_buffer, алгоритм 206-207, 527 greater, класс 75,381-383 g r ea t e r _ eq и a I. кл acc 75,384-385 H hash, класс 395-396 hash map, класс 487-493 hashjnultimap, класс 499 -504 hash multi set, класс 493-499 hash set, класс 482-487 I identity, класс 389-390 includes, алгоритм 320-322 irner_product, алгоритм 286-287 irp]ace_merge, алгоритм 317-319 input_iterator_tag, класс 58, 188-190 insert, функция-член в Ассоциативном Контейнере 95-96 в Множественном Ассоциативном Контейнере 164 в Последовательности 91-92, 149-153 в Сортированном Ассоциативном Контейнере 170 в Уникальном Ассоциативном Контейнере 162 1 rsert.after, функция-член 443, 447-448 и sert.iterator, класс 39, 347-350 для Последовательности 93 int.node, структура 30-32 int.type, член класса 357 is.even, функция 67-68 is.heap, алгоритм 340-341 is.sorted, алгоритм 304-305 ist ream.iterator, класс 351-353 istream.type, член класса в istream.iterator 353 в istreambuf-iterator 357 istreambuf-iterator, класс 356-358 iter.swap, алгоритм 60, 244-245 iter.swap.impl, алгоритм 60 iterator, класс 59, 193-196 iterator, тин в Контейнере 139 в Простом Ассоциативном Контейнере 165 для block 79-80 для контейнеров 86-88 iterator.category, тин 58-60 в Итераторе Ввода 110 в Итераторе Вывода 112 iterator.traits, класс 52-53, 59-60, 186-188 typename 524 частичная специализация 520 L less, класс 380-381 less.equal, класс 75,383-384 lexicographical.compare, алгоритм 232-234 LIFO, структура данных 505 line.iterator, класс 21-22 list, класс 434-442 вставка элементов 92 logical.and, класс 386-387 logical.not, класс 388-389 logical.or, класс 387-388 lower.bound, алгоритм 169,307-ЗЮ lvalues 55 м make.heap, алгоритм 334-335 make.paiг, функция 186 тар, класс 462-470 max, алгоритм 235-236 max.element, алгоритм 237-238 max.size, функция-член 83 в Аллокаторе 180-181 в Контейнере 141 mem.fип, функция 399 mem.fun.ref.t, класс 400-402 mem.fun.t, класс 398-400 mem.fun1_ref_t, класс 404-406 mem.funl.t, класс 402-404 merge, алгоритм 315-317 merge, функция-член в list 441 в slist 449 miп, ал горитм 234 -235 min.element, алгоритм 236-237 minus, класс 75, 371-373 mismatch, алгоритм 230-232 modulus, класс 375-377 multimap, класс 476-482 multiplies, класс 75,373-374. 527 multiset, класс 470-476 N negate, класс 75, 377 -378
Алфавитный указатель 535 new. оператор 198 next_permutation, алгоритм 272-274 node_wrap, класс 31-32,51 как адаптер 63 как изменяемый итератор 61-63 node_wrap_base, класс 62-63 not_eqdal.ro, класс 75,379-380 nth_element, алгоритм 302-303 NULL, указатели; нулевые указатели 28 numeric_limits, класс 152 О operator(), перегрузка 67-68 operator[], член класса в block 79-80, 87 в hashjnap 493 в тар 469-470 в Контейнере Произвольного Доступа 147 operator< в <Сравнимом 103 в Строго Слабо Сравнимом 104 operators, член класса 527 ostream_iterator, класс 39-40,354-356 ostream_type, член класса вostream.iterator 355 в ostreambuf_iterator 359 ostreambuf.iterator, класс 358-360 output_iterator_tag, класс 58, 188-190 P pair, класс 184-186 partial.sort, алгоритм 298-300 partial_sort_copy, алгоритм 300-302 partial_sum, алгоритм 287-289 partition, алгоритм 276-277 plus, класс 75,370-371 pointer, тин в block 79-80 в Аллокаторе 179 в Итераторе Ввода 110 в Контейнере 90, 138 pointer_to_binary_funcnon, класс 418-420 pointer_ro_unary_function, класс 74-75, 417-418 pop, функция-член в priority.queue 514 в queue 510 в stack 505, 507 pop.back, функция-член в stack 505 в Последовательности с Концевой Вставкой 93, 156 pop_f ront, функция-член 93, 154-155 pop_heap. алгоритм 337-339 prev.permutation, алгоритм 274-276 previous, функция-член класса slist 446 priority.queue, класс 510-514 projectlst, класс 390-391 project2nd, класс 392-393 pt r_fun, функция 75,417-420 ptrdiff.t, тин 54 push, функция-член в priority_queue 514 в queue 510 в stack 507 push.back, функция-член в stack 505 в Последовательности с Концевой Вставкой 93, 155 push.front, функция-член Последовательности с Начальной Вставкой 93, 154 push_heap, алгоритм 335-337 Q queue, класс 508-510 quicksort, алгоритм быстрой сортировки 294 R random access_iterator_tag, класс 57-58, " 189-190 random.sample, алгоритм 280-282 random_sample_n, алгоритм 282-284 random_shuffie, алгоршм 279-280 raw_storage_iterator, класс 365-367 rbegin, функция-член 83 для Реверсивного Контейнера 146 reference, тин в Аллокаторе 179 в Итераторе Ввода 110 в Контейнере 90, 138 для block 79-80 rel_ops, пространство имен 529 remove, алгоритм 257-259 remove, функция-член в list 440 в slist 449 remove_copy, алгоритм 260-262 remove_copy_if, алгоритм 262-263 remove_if. алгоритм 259-260 remove.if, функция-член в list 440 в slist 449 rend, функция-член 83 для Реверсивного Контейнера 146 replace, алгоритм 41,249-250 replace_copy, алгоритм 251-252 replace_copy_if, алгоритм 253-254 replace.if, алгоритм 250-251 reserve, функция-член 429, 432 resize, функция-член в Последовательности 527 в Хешированном Ассоциативном Контейнере 175 return_temporary_buffer, алгоритм 207, 527 reverse, алгоритм 268-269 reverse, функция-член в list 441 в slist 450 reverse.copy, ал гори i м 44, 269-270 reverse.iterator, класс 73, 360-365 в Реверсивном Контейнере 145 для контейнеров 87-88 как адаптер 63-64 rotate, алгоритм 270-271 rotate.copy, алгоритм 271-272 S search, алгоршм 215-217 search_n, алгоритм 219-222, 527 select 1st, класс 393-394 select 2nd, класс 394-395 set, класс 95-96,320, 456-462 set_difference, алгоритм 328-331 set-intersection, алгоршм 325-328 set_symmetric_difference, алгоршм 331 -334 set_union, алгоритм 322-325
536 Алфавитный указатель ы/е. функция-член в priority_queue 513 в queue 510 в stack 507 в Контейнере 141 size_type, член класса в block 79-80 в pnority_queue 513 в queue 509 в stack 507 в Аллокаторе 179 в Контейнере 90, 139 slist, класс 442-450 snail_sort, алгоритм 274-275 sori. алгоритм 21, 293-295 sort, функция-член в List 442 в slist 450 sorr_heap, алгоритм 339-340 splice, функция-член в List 440 в siist 448 splice_after, функция-член 448 stable_partition, алгоритм 277-278 stable_sort, алгоритм 295-298 stack, класс 505-507 s t а г t, у каза i ели 428 std, пространство имен 528 st rchr, функция 26-27 streambuf_type. член класса в istreambuf-iterator 357 в ostreambuf-iterator 359 strtab_cmp, класс 23 strtab_print, класс 23 sublractive_rng, класс 396-398 swap, алгоритм 243-244 в Контейнере 142 для block 83-84 частичная специализация 521 swap,ranges, алгоритм 245-246 'system, функция 227 top, функция-член в pnority_queue 513 в stack 507 r raits_type, член класса в istream_iterator 352 в istreambuf-iterator 357 в ostream_iterator 355 в ostreambuf-iterator 359 t rarsform, алгоритм 70, 246-248 1 rivial-Container, класс 89-90 typerame, ключевое слово 523-525 и J'ary_compose, класс 423-424 л'агу-tunctior, класс 71-72,368-369 irary regate, класс 73-75,420-421 iniritialized-copy, алгоритм 200-202, 527 irinitialized_fill, алгоритм 202-203,527 n initiaiized-fill-r, алгоритм 204-205, 527 в tque, алгоритм 263-265 irique, функция-член в list 440 в slist 449 nique_copy. алгоритм 266-268 oper bound, алгоритм 169,310-312 51 ng, объявление использования 529 N value_type, тин 59 в priority_queue 512 в queue 509 в stack 506 в Аллокаторе 179 в Ассоциативном Контейнере 94. 157 в Итераторе Ввода 109 в Контейнере 90, 138 в Парном Ассоциативном Контейнере 166 для block 79-80 для итераторов 50-53 vector, класс 21, 427-434 в сравнении с deque 450 вставка элементов 92 А автоматическое преобразование типов, подавление 522 адаптеры итераторов 63-64 контейнеров 505-514 функций-членов см. адаптеры функций- членов функциональных объектов 73-75 адаптеры функций-членов 398 const_mem_fun_ref_t 408-410 const_mem_fun_t 406-408 const_mem_fun1_ref_t 412-414 const_mem_fun1_t 410-412 mem_fun_ref_t 400-402 mem_fun_t 398-400 mem_fun1_ref_t 404-406 mem_fun1_t 402-404 Адаптируемая Бинарная Функция, концепция 72-75, 128-129 и указатели на функции 418-420 композиция функций 425-426 преобразование в Адаптируемую Унарную Функцию 414-416 Адаптируемая Унарная Функция, концепция 72-75, 127-128 и указатели на функции 417-418 композиция функций 423-424 преобразование Адаптируемой Бинарной Функции 414-416 адаптируемые функциональные объекты 72, 122 Адаптируемая Бинарная Функция 128-129 Адаптируемая Унарная Функция 127-128 Адаптируемый Генератор 1215-127 Адаптируемый Бинарный Предикат, концепция 131-132,421-423 Адаптируемый Генератор, концепция 126-127 Адаптируемый Предикат, концепция 72-75, 131, 420-421 алгоритм “разделяй и властвуй” 319 алгоритм максимального значения max 235-236 max_element 237-238 алгоритм минимального значения min 234-235 min_element 236-237 алгоритм разбиения partition 276-277 stable_partition 277-279 алгоритмы 20 вычислительная сложность 45 и итераторы 35-45
Алфавитный указатель 537 ал гор итм ы (продолжение) изменяющие см. алгоритмы изменяющие концепции и модели концепций, их связь с алгоритмами 32-35 линейного поиска 25-32 пемодифицирующие см алгоритмы немодифицирующие новые 527 отвязывание от контейнеров 24 алгоритмы адаптивные 205,296 алгоритм ы выборки random_sample 280-282 random_sample_n 282-284 алгоритмы диспетчеризации 55-58 алгоритмы изменяющие 49 transform 246-248 замена элементов 249-254 заполнение диапазонов 254-257 копирование диапазонов 239-243 обмен элементов местами 243-245 обобщенные числовые алгоритмы 284-291 перемешивания и выборки 278-284 перестановки 268-276 разбиения 276-278 удаление элементов 257-268 алгоритмы многопроходные 44 алгоритмы немодифицирующие 49 for_each 226-228 линейный поиск 208-214 минимум и максимум 234-238 подсчет элементов 222-226 поиск подпоследовательности 215-222 сравнение диапазонов 228-234 алгоритмы новые 527 алгоритмы однопроходные 37 алгоритмы перемешивания и выборки random_sample 280-282 random_sample_n 282-284 random_shuffie 279-280 алгоритмы переставляющие 268 next_permutation 272-274 prev_permutation 274-276 reverse 268-269 reverse_copy 269-270 rotate 270-271 rotate_copy 271-272 алгоритмы iюиска нод1 юследователыюсти find_end 217-219 search 215-217 search_n 219-222 ал горитм ы сортиров к и introsort 294 быстрая сортировка quicksort 294 сортировка кучей heapsort 293 сортировка слиянием mergesort 319 алгоритмы сравнения диапазонов equal 228-230 lexicographical_compare 232-234 mismatch 230-232 алгоритмы числовые accumulate 284-286 adjacent-difference 289-291 inner_product 286-288 partial_sum 288-289 алгоритмы числовые обобщенные accumulate 284-286 adjacent-difference 289-291 inner_product 286-287 partial_sum 287-289 алгоритмы, отвязывание от контейнеров 24 Аллокатор, концепция 176-182 аллокаторы и выделение памяти 96 для vector 428 изменения в библиотеке 525-526 класс allocator 196-198 концепция Аллокатор 176-182 аллокаторы по умолчанию 525 Аннотированное справочное руководсню (Annotated C++ Reference Manual) (Эллис и Страуструп) 515 аргументы шаблона функции 206 арифметические операции 75 divides 374-375 minus 371-373 modulus 375-377 multiplies 373-374 negate 377-378 plus 370-371 times 373 асимметричные диапазоны 29 ассоциативные контейнеры 456 hash_map 487-493 hashjnultimap 499-504 hashjnultiset 493-499 hash_set 482-487 map 462-470 multimap 476-482 multiset 470-476 set 456-462 Ассоциативный Контейнер 156-161 Множественный Ассоциативный Контейнер 163-164 Парный Ассоциативный Контейнер 165-167 Простой Ассоциативный Контейнер 164-165 Сортированный Ассоциативный Контейнер 167-171 Уникальный Ассоциативный Контейнер 161-163 Хешированный Ассоциативный Контейнер 171-176 Ассоциативный Контейнер, концепция 94-96, 156-161,499 Б Баррейро, Хавьер 456 безопасность при исключительных ситуациях 201 Бинарная Функция, концепция 70- 71, 125-126 Бинарный Предикат, концепция 70- 71, 130-131 булевы операции см логические операции буферы временные 205-206 В ввод istream_iterator 351-353 istreambuf-iterator 356-358 “ведра” функций хеширования 173 Функция Хеширования 135-136 Хешированный Ассоциативный Контейнер 175 включение в моделирование и развитие 46 возможности доработки STL 24 временные буферы 205-206 get_temporary_buffer 206-207 return_temporary_buffer 207 время жизни элементов Контейнера 86, 138 вставка в Ассоциативные Контейнеры 94 в Последовательности 91-93, 151-153
538 Алфавитный указатель вывод ostream.iterator 354-356 ostrearnbu^iterator 358-360 выражения адресные в Аллокаторе 182 выражения в концепциях 34 выражения вставки диапазона 150 выражения выделения памяти 180-181 выражения вызова функции в Бинарной Функции 125-126 в Бинарном Предикате 130 в Генераторе 123 в Предикате 129 в Строгом Слабом Упорядочении 133 в Унарной Функции 124-125 выражения декремента 117-118 выражения дес i рук гора 140 выражения доступа в Итераторе Произвольного Доступа 120 в Контейнере Произвольного Доступа 147 выражения доступа к членам класса 108-109 выражения доступа к элементу в Итераторе Произвольного Доступа 120 в Контейнере Произвольного Доступа 147 выражения конструктора диапазона в Последовательности 148-149 в Сортированном Ассоциативном Контейнере 168 в Уникальном Ассоциативном Контейнере 161 162 в Хешированном Ассоциативном Контейнере 174-175 во Множественном Ассоциативном Контейнере 163-164 в ы раже ния обоб i це 11 и о го ко 11 стру ктора копирования 180 выражения освобождения памяти 181 выражения постдекрементные 117-118 выражения пост инкрементные в Итераторе Ввода 111-112 в Итераторе Вывода 114-115 в Однонаправленном Итераторе 116 выражения предекрементные 117 в ы ражен и я н ре и и креме н т 11 ы е в Итераторе Ввода 111 в Итераторе Вывода 114 в Однонаправленном Итераторе 116 выражения присваивания в Итераторе Вывода 114 в Итераторе Произвольного Доступа 120-121 в Контейнере 140 в Присваиваемом 101 в Тривиальном Итераторе 108 вы ра же н и я раз ы м с но ва н и я в Итераторе Ввода 111 в Итераторе Вывода 114 в Однонаправленном Итераторе 116 в Тривиальном Итераторе 108 выражения с kohciрукюром копирования в Аллокаторе 180 в Итераторе Вывода 113-114 в Контейнере 140 в Присваиваемом 100-101 выражения сравнения в Аллокаторе 180 выражения сравнения на равенство в =Сравнимом 102 в Однонаправленном Контейнере 143-144 выражения сдирания диапазона 150 иячислтельная счожность алгортмов 45 функций хеширования 172, 176 вычитание иiораторов 45, 119-121 г Генератор Случайных Чисел, концепция 134-135, 396-398 Генератор, концепция 70-71, 123-124 д двоичный поиск 305 binary_search 306-307 equal_range 312-315 lower_bound 307-310 upper_bound 310-312 Двунаправленный Итератор, концепция 44, 117-118 для контейнеров 88 развитие концепции 45-46 двусвязный список 434-442 деревья 334 диапазоны асимметричные 29 в линейном поиске 28-29 в Последовательности 148 для Итераторов Вывода 39 для массивов 77 заполнение 254-257 итераторов 111 копирование 239-243 обмен 245-246 создание кучи 334-335 сортированные см сортированные диапазоны сортировка 292-305 сравнение 228-234 Доджсон, Чарльз 516 допустимые выражения концепции 34 допустимые диапазоны 28-29 в итераторах 111 в Последовательности 148 допустимые итераторы 111, 148 допустимые программы как представление концепций 33 до11усти м ые указател и 28 достижимые диапазоны 28 достижимые итераторы 111 Дурстенфельд, Ричард 279 Е емкость векторов 428-429 3 заголовочные файлы, имена 529-530 заполнение fill 254-255 fill.n 255 generate 256 generates 256-257 uninitialized_fill 202-203 jninitialized_fill_n 204-205 в Последовательности 148 значение “конец потока” 351 И идентичность Однонаправленных Итераторов 42, 64 иерархия контейнеров 88-89, 97-98 изменения в библиотеке multiplies 527
Алфавитный указатель 535 изменения в библиотеке (продолжение) times 527-528 адаптеры контейнеров 526 аллокаторы 525-526 интерфейс итераторов 527 новые алгоритмы 527 Последовательность 527 изменения в языке модель компиляции шаблонов 516-517 новые ключевые слова 522-525 параметры шаблона по умолчанию 517-518 частичная специализация 519-521 шаблоны-члены класса 518-519 изменяемые итераторы 43, 61-62 Однонаправленный Итератор 115 Тривиальный Итератор 108 имена lypename для них 523-525 переносимость и стандартизация 528-530 и 11 вариа i it анти си м метри и в <Сравнимом 104 в Строгом Слабом Упорядочении 133 инвариант непрерывного размещения в памяти 160 инвариант нерефлексивности в <Сравнимом 104 в Строгом Слабом Упорядочении 133 инвариант полноты 142 инвариант упорядочения по возрастанию 171 инварианты диапазона в Контейнере 142 в Реверсивном Контейнере 146 инвариант ы донус!имого диапазона в Контейнере 142 в Реверсивном Контейнере 146 инварианты достижимости 121 инварианты неизменности в Ассоциативном Контейнере 160 в Простом Ассоциативном Контейнере 165 инварианты равномерности в Генераторе Случайных Чисел 135 инварианты симметрии в =Сравнимом 103 в Двунаправленном Итераторе 118 в Итераторе Произвольного Доступа 121 в Последовательности с Концевой Вставкой 156 в Последовательности с Начальной Вставкой 154-155 и и ва р и а н ты тождестве н н ости в =Сравнимом 102 в Тривиальном Итераторе 109 и11вариа11ты транзитвиости в <Сравнимом 104 в =Сравнимом 103 в Строго Слабо Сравнимом 105 в Строгом Слабом Упорядочении 133 инвариаты уникальности 162-163 инвариан i ы частичного упорядочения 104 инвариант ы эквивалентности в Реверсивном Контейнере 146 в Строгом Слабом Упорядочении 133 индексация элемента в Итераторе Произвольного Доступа 45 инициализация массивов 78 инкремент и гераторов 192-193 Итератор Ввода 36-37, 111 Итератор Вывода 38, 114 Однонаправленный Итератор 116 инкрементируемые и i ораторы 110 Искусство программирования (Кнут) 66 Итератор Ввода, концепция 32-33,36-38, 88, 109-112 Итератор Вывода, концепция 38-41, 112-115 Итератор Произвольного Доступа, концепция 44- 45, 118-121 для контейнеров 88 разноси।ые ти 11 ы 53-54 итераторы 21,25,32,35 см также итераторы, классы; итераторы, концепции advance 192-193 distance 190-192 адаптеры 63-64 алгоритмы диспетчеризации и геги 55-58 в Последовательности 148 ввода 36-38 вывода 38-41 двунаправленные 44 для block 79-81 для контейнеров 86-88 для массивов 78 константные и изменяемые 43, 61-62 обмен 244-245 однонаправленные 41-43 определение 63-65 примитивы 186-196 произвольного доступа 44-45 разностные типы 53-54 ссылочные типы и типы указателей 55, 62 тины значений 50-53 функции запроса 60 итераторы вставки back_insert_iterator 345-347 front_insert_iterator 342-345 insert_iterator 347-350 итераторы за последний элемент 36, 110,351,356 итераторы, классы back_insert_iterator 345-347 front_insert_iterator 342-345 insert-iterator 347-350 istream_iterator 351-353 istreambuf-iterator 356-358 iterator 59, 193-196 iterator_traits 186-188 ostream_iterator 354-356 ostreambuf-iterator 358-360 raw_storage_iterator 365-367 reverse_iterator 360-365 изменения в библиотеке 527 итераторы вставки 342-350 потоковые 351-360 итераторы, концепции Двунаправленный Итератор 117-118 Итератор Ввода 109-112 Итератор Вывода 112-115 Итератор Произвольного Доступа 118-121 Однонаправленный Итератор 115-117 Тривиальный Итератор 107-109 К Кёниг, Эндрю 29, 78 классы контейнерные см конiеннерные классы наследование 46-48 обертки 31-32 эквивалентности см классы эквиваленiносги классы функциональных объектов binary_compose 425-426 binary_function 369-370
Алфавитный указатель 540 классы функциональных объекте (продолжение) binary_negate 421-423 binderlst 414-415 binder2nd 415-416 goirter_to_birary_function 418-420 pointer_to_unary_function 417-418 jrary_compose 423-424 jnary_function 368-369 unary_negate 420-421 адаптеры функций-членов 398-414 арифметические операции 370-378 логические операции 386-389 сравнения 378-385 тождественность и проекция 389-395 классы эквивалент пости 105, 133 ключевые слова, новые 522-525 ключи в find_if 209 в Ассоциативных Контейнерах 94, 157 в Парном Ассоциативном Контейнере 166 в Простом Ассоциативном Контейнере 165 при поиске 66 Кнут,Дональд 66 коллизии в Функции Хеширования 135 минимизация коллизий 135 композиция функций bi nary_compose 425-426 unary_compose 423-424 компоновка, переносимость и стандартизация в ней 528-530 констан i ные и гераторы 43, 61 в Контейнере 139 Однонаправленные Итераторы 115 Тривиальные Итераторы 108 Конструируемый по Умолчанию, концепция 34-35, 101-102 ко нет рукторы в Аллокаторе 181 но умолчанию 101-102 шаблоны-члены 518 конструкторы по умолчанию 101-102 в Аллокаторе 180 в Ассоциативном Контейнере 158 в Последовательности 148 в Сортированном Ассоциативном Контейнере 168 в Хешированном Ассоциативном Контейнере 173-174 Контейнер Произвольного Доступа, концепция 89, 146-147 Контейнер, концепция 88-89, 137-143 кошейнерныс классы 77-98, 427-514 deque 450-456 hash_map 487-493 bash_multimap 499 -504 hash_multiset 493-499 hash.set 482-487 list 434-442 map 462-470 multimap 476-482 multiset 470-476 priority_Queue 510-514 queue 508-510 set 456-462 slist 442-450 stack 505-507 vector 427-434 адаптеры 505-514 ассоциативные 456-504 изменения в библиотеке 526 ко 1 it ей нор!। ые классы (продолжение) носледователыюсти 427-456 шаблоны 518 контейнеры переменного размера 86, 90-91 аллокаторы 96 Ассоциативный Контейнер 94-96 Контейнер 140 Последовательность 91-93 контейнеры 21,137 см также контейнерные классы, Контейнер, концепция Аллокатор 176-182 Ассоциативный Контейнер 156-161 выбор 98 иерархия 88-89, 97-98 итераторы 86-88 Контейнер 137-143 Контейнер Произвольного Доступа 146 -147 массивы 77-85 Множественный Ассоциативный Контейнер 163-164 Однонаправленный Контейнер 143-145 определение 97-98 отвязывание от алгоритмов 24 Парный Ассоциативный Контейнер 166-167 переменного размера 90-96 Последовательность 147-153 Последовательность с Концевой Вставкой 155-156 Последовательность с Начальной Вставкой 153-155 Простой Ассоциативный Контейнер 164-165 Реверсивный Контейнер 145-146 Сортированный Ассоциативный Контейнер 167-171 тривиальные 89-90 Уникальный Ассоциативный Контейнер 161-163 Хешированный Ассоциативный Контейнер 171-176 элементы 86 концепции адаптируемые функциональные объекты 126-129 ассоциативные контейнеры 156-176 базовые 100-106 и модели 32-33 и наследование 46-48 итераторов 107-121 как абстракция 24 кон гейнер общего вида 137-147 определение 33 основные функциональные объекты 123- 12в перегрузка 57 носледовател ыюсти 147-156 11 реди ка гы 129 -134 развитие 45-46 специализированные 134-136 требования 32-35 копирование диапазонов 239-243 Итераторов Ввода 36 Итераторов Вывода 38 массивов 78 с помощью сору 239-241 с помощью copy_backward 241-243 с помощью partial_sort_copy 300-302 с помощью random_sample_n 282-284 с помощью remove_copy 260-262 с помощью remove_copy_if 262-263 с помощью replace_copy 251-252
Алфавитный указатель 54 копирование (продп чж епие) с помощью replace_copy_if 253-254 с помощью reverse_copy 269-270 с помощью rotate_copy 271-272 с помощью uninitialized_copy 200-202 с помощью unique_copy 266-268 коэффициент загрузки для функций хеширования 173, 175 куча, операции с пей is_heap 340-341 make_heap 334-335 pop_heap 337-339 push_heap 335-337 sort_heap 339-340 Л Ли, Менг 515-516 линейный поиск 25 adjacent.find 211-213 find 208-209 find_first_of 213-214 find_if 209-211 в языке C++ 29-32 в языке Си 26-28 диапазоны в нем 28-29 обобщение 66-69 операция 30-32 описание 66 логические операции И 386-387 ИЛИ 387-388 НЕ 388-389 м массивы 77-78 класс 78-85 указатели за последний элемент 28 множества, операции с ними 319-320 includes 320-322 set_difference 328-331 set-intersection 325-328 set_symmetric.difference 331-334 set_union 322-325 Множественный Ассоциативный Контейнер, концепция 95-96, 163-164 hashjnultimap, модель 499 hash_multiset, модель 493 multimap, модель 476 multiset, модель 470 моделирование 32-35, 46-48 модель компиляции шаблонов 516-517 Мозес, Линкольн 279 Мюссер, Дэвид 456 н накопление accumulate 284-286 partial_sum 287-289 наследование в STL 24 по сравнению с моделированием и развитием 46-48 тегов итераторов 58. 190 недопустимое! ь разыменуемых итераторов ' 91-92, 108 недопустимые указатели 28 не типовые параметры шаблона 81 новые ключевые слова 522 explicit 522 typename 523-525 О обертывающие классы 31-32 области значений в Бинарной Функции 125 в Генераторе 123 в Генераторе Случайных Чисел 135 в Унарной Функции 124 области определения в Бинарной Функции 125 в Генераторе Случайных Чисел 135 в Унарной Функции 124 область памяти Контейнера 140 обмен элементов местами iter_swap 244-245 swap 243-244 swap_ranges 245-246 обход двусвязных списков 434 общие концепции контейнеров Контейнер 137-143 Контейнер Произвольного Доступа 146 147 Однонаправленный Контейнер 143-145 Реверсивный Контейнер 145-146 объединение множеств 322-325 Однонаправленный Итератор, концепция 41-43, 115-117 для контейнеров 88 развитие 45-46 Однонаправленный Контейнер, концепция 89. 143-145 односвязпые списки slist для них 442-450 поиск в них 30-32 Окфорд, Роберт 279 Омар Хайям 39 операторы, перегрузка 31 определение итераторов 64 контейнеров 97-98 шаблонов 517 оптимизация, частичная специализация 520 основные функциональные объекты Бинарная Функция 125-126 Генератор 123-124 Унарная Функция 124-125 относительный порядок в стабильных сортировках 295-296 отношение “является” 46 отношения эквивалентности 102-103. 105 отрицание Адаптируемого Бинарного Предиката 421-423 Адаптируемого Предиката 420-421 п намять 96 construct 198-199 destroy 199-200 raw_storage_iterator 365-367 uninitialized_copy 200-202 uninitialized-fill 202-203 uninitialized_fill_n 204-205 vector 428 Аллокатор, концепция 176-182 временная 205-206 изменения в библиотеке 525-526
Алфавитный указатель 542 _____________________ намни» (продол м. ение) класс allocator 196-198 Однонаправленный Итератор 42 караморы шаблона цетиновые 81 явная спецификация 206 караморы шаблона по умолчанию 517-519 Парный Ассоциативный Контейнер, концепция 94-96, 166-167 hashjnap. модель 487 hashjnil timap модель 499 шар, модель 462 гни! 11 map, модель 476 ’первый вошел — первый вышел”, структура да иных см FI FO, с гру к гура ла н н ы х nepei рузка операторов 51 функции 57-58 шаблонных функций 521 переносимость 515-516 адат еров контейнеров 526 аллокаторов 177, 525-526 изменения в библиотеке 525-528 изменения в языке 516-528 именование и компоновка 528-530 с помощью istream_iterator 351 с помощью ostream_iterator 354 пересечение множеств 325-328 перестановки г ext_permutation 272-274 prev_perniutation 274-276 повторяющиеся элементы unique 263-265 umque.copy 266-268 подмножества, includes 320-322 поиск в Ассоциативных Контейнерах 96 двоичный см. двоичный поиск линейный см. линейный поиск поиск значения,операция 94 полиморфизм в сравнении с моделированием и развитием 47-48 полиморфная функция, вызовы 398 полная специализация шаблонов 52 полное упорядочение объектов 105-106 ‘последний вошел - первый вышел”, структура данных см LIFO, структура данных 11 (»с. 1едов ател ь 11 ост 11 deque 450- 456 list 434 - 442 slist 442-450 vector 427-434 Последовательность 147-153 Последовательность с Концевой Вставкой 155 -156 Последовательность с Начальной Вставкой 153- 155 Последовательность с Концевой Вставкой, концепция 93, 155-156 Последовательность с Начальной Вставкой, концепция 93. 153-155 Последовательность, концепция 91-93, 147-153. 527 последующие элсметы списка 434 потоковые и i ера юры 1st г сат_гterator 351-353 lit reambj*_iterator 356-358 Obtieam_iterator 354-356 ostreambjfjiterator 358-360 Предикат, концепция 70-71, 129-130 предикаты как функциональные объекты Адаптируемый Бинарный Предикат 131-132 Адаптируемый Предикат 131 Бинарный Предикат 130-131 Предикат 129-130 Строгое Слабое Упорядочение 132 -134 предыдущий элемент списка в list 434 в slist 442 Присваиваемый, концепция 34-35, 100-101 Простой Ассоциативный Контейнер, концепция 94-96, 164-165 hashjnultiset, модель 493 hash_set, модель 482 multiset, модель 470 set, модель 456 пустой диапазон 29 Р равенство Итераторов Ввода 36 Однонаправленных Итераторов 43, 64 указателей 42 равномерность Функций Хеширования 135 развитие концепций 45-46 размер векторов 428 контейнеров 86, 140 массивов 78 разность множеств setjlifference 328-331 set.symmetric_difference 331-334 разность соседних элементов 289-291 разыменуемые итераторы 36 Итераторы Ввода 36 Итераторы Вывода 113 Тривиальные Итераторы 108 расширяемость STL 24, 61 Реверсивный Контейнер, концепция 89, 145-146 рефлексивность в =Сравнимом 102 в моделировании и развитии 46 с связные списки list 434-442 slist 442-450 поиск в них 30-32 семантика завершения или отката 201 симметрическая разность множеств 331-334 сингулярные итераторы 36 Итератор Вывода 113 Тривиальный Итератор 108 сингулярные указатели 28 система пространств имен 528 слияние сортированных диапазонов с помощью inplace_merge 317-319 с помощью merge 315-317 словари hashjnap 487 hashjnultimap 499 словарный порядок сравнения 232-234 сложение в Итераторе Произвольного Доступа 45, 119, 121 сортирова н н ы е ди аи азо н ы бинарный поиск в них 305-315 операции над множествами 319-334 слияние 315-319 создание из кучи 339-340
Алфавитный указатель Сортированный Ассоциативный Контейнер, концепция 95-96, 167-171 тар, модель 462 multimap, модель 476 multiset, модель 470 set, модель 456 сортировка 20-24, 292-293 is_sorted 304-305 nth_element 302-303 partial_sort 298-300 partial_sort_copy 300-302 sort 293-295 stable_sort 295-298 Сортированные Ассоциативные Контейнеры 95-96, 167 функция-член sort 442, 450 специализация частичная типов 52, 186 шаблонов классов 519-521 шаблонов функций 521 специализированные функциональные объекты hash 395-396 subtractive_rng 396-398 списки свободной памяти в Аллокаторе 177 сравнение, операции 75 equal.to 378-379 greater 381-383 greater_equal 384-385 less 380-381 less_equal 383-384 not_equal_to 379-380 <Сравнимый, концепция 34-35, 82-83, 103-104 =Сравнимый, концепция 34-35, 82-83, 102-103 стабильность sort 293 stable_sort 295-296 разбиений 276-278 стандартизация 515-516 и изменения в библиотеке 525-528 и изменения в языке 516-528 именования и компоновки 528-530 Степанов, Александр 515-516 Страуструп, Бьорн 55, 78,515 Строго Слабо Сравнимый, концепция 104-106 сгрогое слабое упорядочение 104-105 Строгое Слабое Упорядочение, концепция 75, 132-134 теги итераторов 55-58, 188-190 ।ин аргумента в Адаптируемой Унарной Функции 127 в Генераторе Случайных Чисел 134 в Унарной Функции 124 тин второго аргумен га в Адаптируемой Бинарной Функции 128 в Бинарной Функции 125 тин отображаемый 166 тип первого аргумента в Адаптируемой Бинарной Функции 128 в Бинарной Функции 125 |ип результата в Адаптируемой Бинарной Функции 128 в Адаптируемой Унарной Функции 127 в Адаптируемом Генераторе 126 в Бинарной Функции 125 в Генераторе 123 в Генераторе Случайных Чисел 134 в Унарной Функции 124 54: тин ссылочный для итераторов 55, 62 тин указателя для итераторов 55, 62 тины для функциональных обьектов 71-72 и концепции 34-35 подавление преобразований 522 типы ассоциированные 34 для итераторов 50-58 для Контейнера 138-139 для функциональных объектов 71-72 типы значений в Тривиальном Итераторе 107 для итераторов, механизм логического выведения типа 50-51 тины регулярные 35, 100 тождественность и проекция, классы identity 389-390 projectlst 390-391 project2nd 392-393 selectlst 393-394 select2nd 394-395 “только для записи”, вид итераторов 39-40 транзитивность моделирования и развития 46 требования в концепциях 32-35 Тривиальный Итератор, концепция 107 109 У удаление элементов pop_heap 337-339 remove 257-259 remove_copy 260-262 remove_copy_if 262-263 remove_if 259-260 unique 263-265 unique_copy 266-268 в Ассоциативном Контейнере 94 в Последовательности 91-92 узлы в list 178, 435 в деревьях 334 указатели за последний элемент 28 указатели на функции с Адаптируемой Бинарной функцией 418-420 с Адаптируемой Унарной Функцией 417-418 указатели см. также итераторы в Итераторе Произвольного Доступа 45-46 в контейнерах 86, 138 в языке Си 27-28, 42 Унарная Функция, концепция 70-71, 121-125 унарные функциональные объекты 69-70 Уникальный Ассоциативный Контейнер, концепция 95-96, 161-163 hash_map, модель 487 hash_set, модель 482 тар, модель 462 set, модель 456 Уотермен, Алан 281 упорядочение в Итераторе Произвольного Доступа 45 в Однонаправленном Контейнере 144 в Сортированном Ассоциативном Контейнере 171 в стабильных сортировках 295-296 полное 105-106 с помощью <Сравнимого 103-104 с помощью Строго Слабо Сравнимого 104-106 с помощью Строгого Слабого Упорядочения 132-134 частичное 104
544 Алфавитный указатель Ф фи ксирова! । н ы й размер для кон гсйнсров 86, 140 для массивов 78 флажковый элемент 208 форматированный ввод, istream_iterator 351-353 форматированный вывод, ostream_iterator 354-356 Фрайди, Боб 456 функторы см классы функциональных объемов, функциональные объекты, koi ще111(и и, фун кцио! 1алыIые об ьекты функциональные объемы 23.67-69, 122 см также классы функциональных объемов, функциональные объекты, концепции ада!и еры 73-75 ассоциированные тины 71-72 предикаты и бинарные предикаты 70-71 11 редо! । редели I ные 75 унарные и бинарные 69-70 функциональные объекты, концепции Адаптируемая Бинарная Функция 128-129 Адаптируемая Унарная Функция 127-128 Адаптируемый Бинарный Предикат 131-132 Адаптируемый Генератор 126-127 Адаптируемый Предикат 131 Бинарная Функция 69-70,125-126 Бинарный Предикат 70-71,130-131 Генератор 123-124 Генератор Случайных Чисел 134-135 Предикат 70-71, 129-130 Строгое Слабое Упорядочение 132-134 Унарная Функция 69-70, 124-125 Функция Хеширования 135-136 функция хеширования 172-175 Функция Хеширования, концепция 135-136 X Хешированный Ассоциативный Контейнер, концепция 171-176 ч частичная специализация типов 52, 186 частичная специализация (продолжение) шаблонов класса 519-521 шаблонов функций 521 ш шаблонные функции 521 шаблонный класс-обер i ка 31-32 шаблоны typename для них 523-525 модель компиляции 516-517 параметры но умолчанию 517-518 частичная специализация 519-521 член класса 518-519 шаблоны класса, их частичная специализация 519-525 шаблоны-члены класса в Последовательности 151 обдаст применения 518-519 Швеппе, Е. Дж 450 Шекспир, Вильям 528 э эквивалентные объекты 105 эквивалентные элементы 292 элементы в контейнерах 86 замена 249-254 обмен 243-245 перестановка в обратном порядке 268-270 перестановки 272-276 подсчет 222-226 разбиение 276-278 случайная иерее «ановка и выборка ‘ 278-284 сортировка 292-305 удаление 257-268 циклический сдвиг 270-272 Эллис, Маргарет 515 эффективность STL 24 Я явная спецификация аргументов шаблона функции 206
чета ' ’ J ДИАЛЕКТ 7 > ▼ ▼ Addison-Wesley Publishing Company