Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 113
Текст из файла (страница 113)
Организация библиотеки и контейнеры ) б.3. Контейнер типа нес(ог 545 Было бы соблазнительно написать так: гес(о <возов>::аегагогр1 =фпИ «5[!гии.Ьед!п (),1гий.епИ(), 1пй(а1('р' ) ) з гестас<ипил>::гегегве йега!огр2 =флИ (узкий.гьед!п (),ггпу. гелИ(), 1лй!а1 ( 'р' ) ) з угой. егаве (р1, р2ь1); р оорвп неверньш тип Однако гестог<уги1г>:: йегагог и гестас<!си!г>:: гегегзе йега!ог не обязаны иметь одинаковый тип, так что нельзя рассчитывать, что такой вызов егаве() скомпилируется. Для совместного применения с прямым итератором обратный итератор нужно преобразовать явным образом: угой.
егаве (р1, р2. Ьаве () ) з «з' извлечь йегагог из гегегве пега(ог Я!9.2.5) Удаление элемента из вектора изменяет его размер; при этом элементы, располагающиеся во след удаляемым, копируются на освобождающиеся позиции. В нашем примере угий.в!те() возвращает число 4, а маг)зий, ранее доступная как угой[5), становится угой[3) . Естественно, можно удалять и единственный элемент. В этом случае нужно с помощью итератора указать один лишь этот элемент.
Вот соответствующий пример: угой. егаве [Г(пИ [Ггий. Ьед1п () <1гий. епИ (), "вгаг1гш!" ) ); )гий. егаве [1гш(. Ьедгл ( ) ь1) з Здесь удаляются Миф ий и ягоре, после чего в контейнере угийостаются два элемента: !гий[): арр1е Ь!«г(згш! В вектор также можно вставлять элементы. Например: 1гий. швег! [1гий. Ьеагп ( ) ь1, "сйезту" ) з )гий. (лвег! [1гий. епИ ( ), "сгапЬеггу" ) з Новый элемент вставляется перед указуемым элементом с одновременным сдвигом всех элементов [включая указуемый) для освобождения необходимого места. В итоге контейнер)[гий получит следующее содержимое: 1гий [ ]: арр1е сьеггу Рдп(1 гий сгапЬеггу Отметим, что У.
йгеегг [г". еш1(), х) эквивалентно у.раей Ьасй(к) . Можно вставить целую последовательность элементов: уги1г. 1лвегг [1гий. Ьефл () ь2, с!!гав. Ьед1п (), сйгив. елИ () ) Если контейнер сйгие имеет содержимое сзсгив [ ): 1епшп ЛгареГгий агапке Йте то вектор уги!г станет таким: угой [ ): арр!е сйеггу !етол дгаре)гий агапке йте Ь!«п~гий сгапЬеггу Глава 16. Организация библиотеки и контейнеры 546 ЭЛЕМЕНТЫ КОНтЕйНЕра С!ГГПЗ КОПИруЮтСя В ВЕКтОр )(га)Г фуНКцИЕй 1ЫЕГГО, Прнчем содержимое с(ггаз остается при этом без изменений. Очевидно, что операции (ыегг() и егые () более универсальны, чем операции, затрагивающие лишь концевое содержимое вектора (э16.3.5).
Но они и более дорогие операции, так как, например, при вставке элемента может потребоваться перемещать часть элементов на новые места. Если для конкретной задачи типичны вставки и удаления элементов, то лучше выбрать список вместо вектора. Список Ю оптимизирован для операций изжгт() и егазе(), а не для операций индексирования (Я]6.3.3).
Вставка элементов и их удаление из вектора (но не списка или ассоциированного контейнера вроде тар) потенциально передвигает элементы. Следовательно, итератор, указывавший на какой-то элемент вектора, после операций зыегг() или егазе() может уже показывать на другой элемент вектора, или даже вообще не на элемент вектора. Никогда не обращайтесь к элементу по недействительному итера- тору — результат такой операции не определен и последствия, скорее всего, будут катастрофическими.
В частности, остерегайтесь использовать итератор, отмечавший место вставки; )пзегг() делает его недействительным. Например: воЫ йир)(сазе е)етептз (вес(огсз(над>з 7] ( гог(гесгог<мппд>::йегагогр = г'.ьее)п (); р(=(.епг((); ввр) (Нпзегг(р, *р); н Не надо! ) Подумайте над этим (З16.5(15]). Реализация вектора передвинет все элементы или, по крайней мере, все элементы после р для того, чтобы освободить место лля нового элемента. Операция с(еаг() удаляет из контейнера все элементы. Таким образом, с. с(еаг ( ) — зто краткая запись для с. агаве (с. Ьеязп ( ), с.
егиз () ) . После с. с1еаг ( ) вызов с.иге() вернет нуль. 16.3.7. Адресация элементов гетр(все<с(азз С> иоЫ7(Сз с) с. егазе (с. Ьед(п ( ) 67); с.егазе(зс(7] ); с . егазе ( св 7); с. егазе (с. Ьасд () ); II о)г (если итераторм для с поддерживают в (см. з!9 2. !Д гУ не универсальное решение ,У еггог: прибавление 7 к контейнеру бессмысленно )(еггог; с.ЬасИ() это свитка, а не итератор Чаще всего операции егазе() или омеге() применяются к хорошо известным местам в контейнере (например, Ье)рп () или егЫ() ), к результатам операций поиска (например, )згпз () ) или к позициям, определяемым итерационно. В таких случаях итератор указьвает на соответствующий элемент. К элементам векторов мы также часто обращаемся по индексу. В связи с этим возникает вопрос, как нам получить итератор, необходимый операциям ймегт() или егазе(), для элемента вектора с с индексом 7? Поскольку это седьмой элемент вектора после его начала, ответом будет выражение с.
Ьея(п () в 7. Другие альтернативы, кажущиеся приемлемыми по аналогии с массивами, вполне могут оказаться ошибочными (а значит их следует избегать). Рассмотрим пример: ) б.3. Контейнер типа нес(от 547 /У оа (предпоследний элемент) /У еп ог.' ге»есле Пега!ог и Пега(ог - разные типы // туманно, но о!с (см. В!9.2.5) с. егазе (с. еаза () -2); с.егазе(с.гЬея)п () +1) ! с. егазе( (с.
гаеачп ()»2) .Ьаве() ); ) 16.3.8. Размер и емкость До сих пор мы описывали вектор с минимальным упоминанием вопросов, связанных с управлением памятью — вектор растет по мере необходимости, и все. Обычно этого достаточно. Но можно задать и явный вопрос о том, как именно вектор получает память, и даже прямо вмешаться в этот процесс. Вот некоторые операции вектора: гетр!а!в <с1азз Т, с(алв А = а1!оса!ос< Т» с)алв гес(ог ( риЫ!с: // ...
/' сарае!Гу — емкост»к мхе гуре з!ае() соплы // число элемеюиов Ьоо! етр(у () сопл( (ге!игп з!се() ==0) ) псе (уре тал з!се() сопл(! //размер максимал~но длинного вектора гоЫ гезйе(в(се Гуре зг, Т га! = Т() ) ! //добавляемые эл-ты иниц-ся значением на! зИе гуре сарае!(у() сопле; Уразмер выделенной (в элементы) помята гоЫ гезегге (Ыте (уре п); //выделяет память для и элементов без инициализации; // генерирует исключение 1епя(Ь еггог если п>тал Ыге() // ... )' В любой момент времени вектор «знает» число своих элементов.
Это число возвращается функцией з!ае() и может изменяться вызовом гез)ае () . Таким образом, пользователь всегда может определить размер вектора и изменить его, если это нужно. Например: с1алв Н!згоегат Самая соблазнительная альтернатива, ьс(7), работает для очевидных реализаций вектора, в которых с( 7) ссылается на элемент и его адрес есть действительный итератор. Однако с может оказаться контейнером, для которого итераторы не являются простыми указателями на элементы.
Например, операция индексирования для ассоциативных массивов щар (917.4.1.3) имеет возврат типа таррег! !уреь, а не ссылку на элемент (га!ие гуреь). Не все контейнеры поддерживают операцию + для своих итераторов. Например, 11м не допускает даже с. Ьея1п ()» 7. Если вам на самом деле необходимо увеличить !Ы::зтегатог на 7, примените многократно операцию +», или вызовите аг!гипсе() (519.2.1). Мнимые альтернативы с+ 7 и с.
Ьас(с() — просто ошибки в типах. Контейнер не является числовой переменной, к которой можно прибавить 7, а с. Ьасйы — это элемент со значением вроде реаг (груша), не идентифицирующий положение чгруши» в контейнере с. Глава 16. Организация библиотеки и контейнеры 548 уесгог<!и!> соип!/ риЬ1!с: НН(олтат (!п( Ь): соил!(тах(Ь,О) ) (] юЫ гесогИ (1п(1); //... во!И НН(одгат:: гесоп1((п! !) ( ф'(!<0) 1=0; (Г(соип!.и!ее () <=!) саин!. геиге (!з1); саин![!] +-~; ) // выделяем достаточно памяти гагисг Ип)[ ( 1лпа* пех(; Ип3с(1лпа' и =О): пех!(и) () // ... вес!ог<Ипа> в; юЫ сла1п (з(ее !и) //заполняет у и элементами /лп/г так, чтобы каждый Е!пй //указывал на предшествующий ( у. газетке (и); ч.рилЬ баса (1та (0) ) ) /ог(1п! в'=1/ 1<п; 1++) в.ризЬ баса(1лпа(ау[1-1] ) ); / ...
) Вызов г. гелегюе (и) гарантирует, что при увеличении размера вектора в не потребуется никакого дополнительного выделения памяти до тех пор, пока г. в(ее(] не превысит и. Применение гелпе() к вектору очень похоже на применение функции геа11осО из библиотеки языка С ко встроенным массивам, динамически вьиеляемым в свободной памяти (куче). Когда размеры вектора изменяются под большее (или меньшее) число элементов, элементы при этом могут быть перемещены в новую область памяти.
Поэтому идея хранить указатели на элементы вектора, который может подвергнуться операции гембле (), весьма плоха, ибо после этой операции старые адреса элементов могут стать недействительными. Лучше хранить индексы элементов. Обратите также внимание на то, что операции рилЬ Ьас!с(), рор Ьасй(), 1плегг() и егале() изменяют размер вектора неявно. Помимо объема памяти, необходимого для хранения элементов, конкретная реализация может вьшелять дополнительную память для потенциального расширения. Программист, полагающий, что такое расширение вероятно, может вызовом функции гелегге () зарезервировать заданное количество памяти под будущее расширение. Например: 16.3.