Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 22
Текст из файла (страница 22)
Если бы мы захотели добавить новые элементы в конец контейнера, можно было бы написать следующее: оо!с!у'(оес1ог<Еп1гу>й ое, !1з1<Еп1гу»й 1е) зог1 (ое.Ьеу!п (), ое.еле! Ь); илй!ие сору (оеЬеу!л (), оеепс! (), Ьисй !лзеггег(!е)) Одаоавить к !е Ьасй слзег!ег () добавляет элементы в конец контейнера, увеличивая его до необходимых размеров Я 19.2.4). Таким образом, стандартные контейнеры посредством Ьасй слзег1ег () устраняют необходимость использования подверженного ошибкам явного управления памятью в стиле С с помощью геа!!ос (] Я 16.3.5).
Если вы забудете воспользоваться Ьасй спзег!ег () при добавлении, то это люжет привести к ошибкам, Например: Глава 3. Обзор стандартной библиотеки эо!аяэес1огсЕп1гу>й ие, Вз1<Еп1гуьй !е) ( сору (ие.беут (), ое.епа (), !е); сору (ое.беу!и (), ое.ешГ (), !е.епс! ()); сору (ие Ьедгп (), ое епс! (), !е беу!и ()); //ошибка: !е — не итератор // плохо: пшиет за конеи // перезаписывает элеяента 3.8.1. Использование итераторов Впервые появившись в программе, контейнер предоставляет лишь несколько итераторов, ссылающихся на полезные элементы. Лучшими примерами служат Ьеу!и () и епсГ().
Кроме того, итераторы возвращаются мпогимн алгоритмами. Например, стандартный алгоритм/Гпс( ищет значение в последовательности и возвращает итератор, указывающий на найденный элемент. С помощью ЯпсГ мы можем подсчитать количество вхождений символа в строку: т1 соип1(сопз1 з1ггпуй з, сбаг с) !п1п=у, з1г!пуэсопз1 11ега1ог1=ЯпсГ(збеу1п (),зепс!(), с); шб!!е (!!=з.епс(()) ( ++и; с =Япс! (сгь1, з.епа! (), с); ге!ига и; ФункцияЯпс( возвращает либо итератор, указывающий на первое вхождение значе- ния в последовательность, либо итератор, указывающий на элемент, следуюп!нй за последним.
Рассмотрим, что произойдет при простом вызове соил!; иоЫ Г() ( з1ппут = "УМэри была маленькая овечка'; !и! а соил!= соип1(т,'а'); Первый вызов ЯпсГ () найдет 'и' в слове была. Таким образом, итератор укажет на этот символ, а не на з.епсГ (), поэтому мы войдем в цикл шй!Ге. В цикле мы начинаем поиск с гьГ, то есть со следующего за 'а' символа. ГГосле того, как все 'а' будут найдены, Яп!Г () достигнет конца и вернет з.епсГ (). Условие!!= з.епа! () не выполнится и мы выйдем из цикла.
Вызов соил!() можно графически представить следующим образом: У М э р и б ы л а м а л е н ь к а я о в е ч к а Стрелки указывают на первое, промежуточные и конечное значения итератора 1. Естественно,/Гпс( будет работать аналогичным образом с любым стандартным ' контейнером. Следовательно, мы могли бы написать функцию соил! () в более общем виде: 3 В Алгоритмы 1етр!а!в<с!аяв С, с!азв Т> ш1 соип1 (сопв1 Сй и Тип() ( 1урелате Сссопзг Вегагог! =Япс!(о Ьедт (), о епй(), иа!); // "1уоепате" см у С !йд »а!п-О; шЬ1!е (»!=о.епс! ()) ( +-».и; +»1; //перейти на элемент, следуюи(ай за только что найденным ! =/сп»! (с, иенс! (), оа(); ) »е1игп л; Это работает и поэтому можно написать: ио!с(/(!1вгчсотр!ек>й 1с, пес!о» вггспу>й ив, я1г!пуз) ( ш1 11 = со ил1 (!с, сотр! ех (!, 3)), »лг»2 = соип1(оз, "Хривипп"), »п1 !2 = соил!(в, 'к'); На самом деле нам не нужно определять шаблон соил!, Подсчет числа вхождений элемента настолько часто встречается, что этот алгоритм реализован в стандартной библиотеке.
Для достижения большей общности аргументом функции соил!из стандартной библиотеки является последовательность, а не контейнер, поэтому мы можем писать так: оо!с(/(Е!я1<сотр!ех>й 1с, иес1огчз1ггпу> й оз, згг!пд з) !лг»1 = соил!(!с.Ьеу(п (), 1с ел»!(), сотр!ех (1, 3)); ш1»2 =- соил!(оя.Ьеу!л (), ов.еп»! (),'Диоген~; ш'1 !3 = со и п1 (я Ьеу1л (), з. елй (), 'х'); Использование последовательностей дает возможность вызывать соил/для встроенных массивов, а также производить подсчет числа вхождений в части контейнера. Например; оо!с! у(сЬаг св(), ш1 зх) ( »п1»1 = соип1 (йсз(О), йся(ях), 'х); // число сил»волов 'х' ел»асс»»ве »п1»2 = соил!(йся(О), йся(зх/2), 'х) // число жи»волов 'г' в первой половине»шссива 3.8.2.
Типы итераторов Что же такое итераторы на самом деле? Каждый конкретный итератор является объектом некоторого типа. Однако существует множество разновидностей итераторов, потому что нтератор должен содержать информацию, необходимую для работы с контейне- роз» определенного типа, Типы итераторов могут отличаться настолько же, насколько отличаются типы ко»пейнеров и цели, для которых используются итераторы. Например, итератором для вектора (пес1ог), скорее всего, будет обыкновенный указатель, по~ему что указатель является достаточно разумным способом ссылки на эдеме»п вектора: 96 Глава 3.
Обзор стандартной библиотеки итератор: р вектор; Или можно реализовать итератор вектора в виде пары (указатель на вектор, индекс): итератор: (начало==р, смещение==3) вектор Использование такого итератора позволяет осуществить проверку диапазона Я 19З). Итератор для списка должен быть немного сложнее, чем простой указатель на элемент, потому что элемент в списке, в общем случае, не знае~, где находится следующий элемент списка. Таким образом, итератор списка может быть указателем на связь (1пз1с): втератор: Р список: элементы: Р 1 е 1 Что является общим для всех итераторов, так это их смысл и имена операций.
Например, применение ++ к любому итератору приведет к тому, что итератор будет указывать на следующий элемент. Аналогична, " означает элемент, на который ссылается итератор. В действительности, любой объект, подчиняющийся нескольким простым праэилам, наподобие перечисленных, является итератором (9 19.2.1). Более того, пользователям редко требуется знать тип конкретного итератора.
Каждый контейнер «знает» тип своего итератора и обеспечивает доступ к нему через стандартные имена 11ега1ог и сопз1 11ега1ог. Например, 11з1<Еп1прс11ега1ог является общим типом цтератора для 11з1<Ел1гу>. Мне очень редко приходилось беспокоиться о деталях определения этого типа. 3.8.3. Итераторы и ввод/вывод Итераторы являются общей и полезной концепцией обработки последовательностей элементов в контейнерах. Однако контейнеры — не единственный случай, когда нам встречаются последовательности элементов.
Например, поток ввода является последовательностью значений; мы записываем последовательность значений в поток вывода. Следовательно, понятие итераторов можно применить ко вводу/выводу. Чтобы создать итератор оз1геат 11ега1ог нам необходимо указать, какой поток будет использоваться, и тип объектов, записываемых в него, Например, мы можем определить итератор, который ссылается на поток стандартного вывода соий ое1геат Вега1ег з1паде ее ~сои11 Эффект от присваивания *оп состоит в записи присваиваемого значения в стандартный поток вывода сои1. Например: 97 3.8. Алгоритмы // означает сои! « "Здравствуй, //означотп сои! «'зп!р!»»л Это еще один метод записи «канонического» текста в стандартный вывод. Выражение +ч-оо имитирует запись в массив через указатель.
Я вряд ли стал бы этим пользоваться для такой простой задачи, но идея интерпретации вывода в качестве контейнера с атрибутом лоступа «только для записи» скоро станет, если уже не стала, очевидной. Аналоплчно, итератор !я1геат !1ега1огявляется тем, что позволит нам интерпретировать поток ввода, как контейнер «только для чтения». Мы снова должны указать используемый поток и типы ожидаемых величин: 1з!геат !1ега1ог <з1г!пу» И (с!л); Так как итераторы ввода неизменно появляются парами, определяющими некую последовательность, мы должны предоставить !я!гент !1ега1ог, указывающий на ко- нец ввода.
Вот !я!гент !1ега1ог по умолчанию: !з!геат Нега 1о» з!г!пу» еоз; Теперь мы можем прочитать "Здравствуй, мир!" из потока ввода, а затем и вывестн эту строку следуюьцим образом; т»иг«з! «''«з2 «'»»л ) На самом леле итераторы !я1геат !1ега1ог и оя1геат !1ега1ог не предназначены для непосредственного применения.
Они, как правило, используются в качестве параметров некоторых алгоритмов. Например, мы можем написать простую программу, которая читает файл, сортирует прочитанные слова, удаляет дубликаты и записывает результат в другой файл: !л! тасп () ( з!г!пу/гот, 1о; с!л » /гот » 1о; //пропита»п» ииена игходного // и целевого файлов Я!геши гз(/гот с з1 г()), !з1геат Ыега1ог«з1плу» и (йч); !з1геат Иега1огчз1ппу> еоеп оес1ог з!плу» Ь (!1, еоз); зог! (Ь.Ьеу!л (), Ь.еле! ()).
%!геат оя (И»,с з1г ()) озггеат йега1ог<з!г!пу» оо (оз, »»и'), 1п1та!л () ( *оо = "Здравствуй, "; +чоо; 'оо = 'мир!'чл; иптагп () ( з!г!лд з! = *и'; ч-»!1, з!Мпд з2 = 'чй //поток ввода (с з1гО. ель Ф 3.5! //ип!ератор ввода дяя по»пока // сп»раж ввода // Ь вЂ” вектор, инпциахияируелгый вводол // сорпшруел буфер О поток вывода // и»иеран»ор вывода длл потока Глава 3. Обзор стандартной библиотеки 98 // копировать буфер в лоток, О удалив лова|ори!ощиеся значения // воэвраттль сост ояние //о!иибкн Гф 3.2, ~ 21 3.4) ил!див сору (Ь.Ьеугл (), Ь.елс! (), оо); ге!игл Ук.ео/() ))!оз, Тип (гз!геат — это поток ввода гз!геат, который может быть связан с файлом, а о/зГгеат — это поток вывода оз!геат, который может быть связал с файлом.
Вто- рой аргумент озГгеагп )!ега!ог используется для того, чтобы ограничить выход- ные значения. 3.8.4. Проход с выполнением и предиквты Итераторы позволяют писать циклы для перебора элементов последовательностей. Однако, написание циклов может оказаться утомительным занятием, поэтому стандартная библиотека предоставляет способы вызова функций для каждого элемента последовательности: рассмотрим программу чтения слов из потока ввода и записи частот их вхождений.
Очевидным способом представления строк с указанием частоты вхождения является ассоциативный массив тар: тар<кгплд, )л!> Ььчгоугат; //гастогризсяа часглот Очевилным действием, применяемым к каждой строке для подсчета частот, является; оо(с(гесоп!)соле!к!пар& к) ( Ьгк!аусат(з'гь+; // часл1ота вхождений з После считывания ввода нам необходимо вывести собранные данные. Ассоциатив- ный массив тар состоит нз последовательности пар Гз!г!лй, т!).