Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 112
Текст из файла (страница 112)
Например: «есгог<!п<> «1 (10); . гУо)с вектор иэ 10 !л! «ес!ог<т > «2 = «ес!ог<т > (10); (Уоlсг вектор иэ !О т( «есгог<(п > «3 = «2„. (У о)с «3 - копия «2 «ес!ог<т > «4 = 10; УУ елог: попытка неявного преобраэования 10 в «ес!ог<!пг> Копирующий конструктор и перегруженная операция присваивания копируют элементы вектора.
В случае векторов с большим числом элементов такие операции весьма дороги, так что вектора передают в функции чаще всего по ссылке. Например: УУ обычный стиль (У обычный стиль У редкий стиль «оЫ 17 («ес1ог<!л1>а); «о!ч!(2 (солт «есгог<тг>ь) ! юЫ (3(«ес!ог<!пг>) ! (( передаем ссылку /l передаем ссьику (( копируем 10000 элементов в новый вектор для 13(! Функция-член аээ10и () предоставляет альтернативу конструкторам со многими аргументами. Она нужна, поскольку операция = принимает единственный право- сторонний операнд, так что ашйл () используется тогда, когда требуется значение по умолчанию или диапазон значений.
Например: с1авв Вооа ( УУ ... )! «о!а' Г(«есгог<)«ит>ь «и, «ее!о«<слог>ь «с, «ее!о«<Вола>а «Ь, 101<Вола>ь 1Ь) ( «л. аээ!Оп (10, )уит (О) ); УУ присвоить вектору «л 10 копий Мит(О) слог э [ ) = "Й!ега1" э «с. аээ!Ол (э, ьэ [э!геог (э) -1] ) ) «Ь . аээ!Оп ( 1Ь . Ьед!и ( ), 1Ь . елд О ) 1 УУ ... ) Таким образом, имеются две возможности — инициализировать вектор последовательностью элементов того же самого типа или присвоить такую последовательность вызовом ашдл() .
Важно, что все это выполняется без введения сонма юЫЬ() ( «ее!о«<!лг> «(10000); УУ ... !7 («); 12 («); 33 («); ) УУ присвоить "(пега!" вектору «с УУ присваиваем вектору «Ь элементы списка 1Ь 541 16.3. Контейнер типа чес(от конструкторов и функций преобразования. Отметим еше, что присваивание полно- стью изменяет значения элементов вектора. По идее, старые элементы удаляются и новые элементы вставляются. После присваивания размер вектора равен числу присвоенных элементов. Например: Естественно, то что делает авЫВи (), можно выполнить и менее прямолинейно, создав сначала подходящий вектор, а затем использовав его в операции присваивания. Вот пример: гоЫ)л (гес!ос<Вова>а гЬ, Втп<Воой>ь 1Ь) ( гесгог<Воой> г!(!Ь.Ьед!п(),1Ь.еаза() ); гЬ = г!! У,.
Это, однако, и неэффективно, и некрасиво. Может показаться, что использование конструкторов с двумя аргументами одного типа неоднозначно: гес!ог<1пг> г (10, 50) ! // нес(ог/зисе,га!ие) или гес!ог/1!ега!ог!,вегагог2)? // гес!ог/з!зе/га!ие)1 Однако 1п! — это не итератор, и реализация должна гарантировать, что на самом деле при этом вызывается гесгог(гесгог<Ы/>::иге (уре, сопл! 1пгй, сопи гес!ос<!а!>::айосатог гуреь) а не геиог ( гес!ог<1пт>:: Вегагог, гесгог<!пг>:: Вега!ог, сопл! гес!ог<!пг>:: аНосагог Гуреь ); В стандартной библиотеке это достигается за счет подходящей перегрузки конструкторов. Таким же образом разрешаются неоднозначности при вызове азЫВп () и 1пзегг() (в)6.3.6).
16.3.5. Стековые операции Чаше всего, мы представляем себе вектор в виде компактной структуры данных, доступ к элементам которой осуществляется по индексу. Однако можно игнорировать эту идею и рассматривать вектор как реализацию абстрактного понятия последовательности. Отсюда и из практики применения векторов и массивов вытекает, что для векторов определенный смысл имеют и стековые операции: !етр(аге<с1аев Т, с1авл А = а1!оса!ос< Т» с(авв гесгог ( риЫ!с: гоЫг () ( гесгог<сваг > г (10, 'х' ); г.азз!Вп(5, 'а'); /'... /кз!ге/)==!О, каждый элемент имеет значение 'х' // кз!зе0 ==5, каждый элемент имеет значение 'а' Глава ) 6. Организация библиотеки и контейнеры 542 У... УУ стековые операции: гоЫ рилЬ Ьасл (сопл( Та х); го(дрор ЬасЬ() г У...
)' УУ добавление в конец УУ удаление последнего элемента Эти функции работают с вектором как со стеком, добавляя и удаляя элементы с конца. Например: гога Г (гесгогксваг>а л) ( л.риэЬ Ьасл('а' ); л.риэЬ Ьаса('Ь' ); л.риэЬ Ьаск ( ' с ' ) ( л.рор Ьас(г() г (Г"(л[в.э1ге() -1) ! = 'Ь' ) еггог(«)тровв~Ые! "); л.рор Ьаса(); (1(л. Ьасл() ! = 'а' ) еггог('лЬоийу псгег Ьарреп( "); ) гесгог< Ро!пгг с(г(ел ( гога айй ро(п(л (Ро(пг лепипей ( Ротс Ьиг) Каждый раз при вызове рилЬ Ьасй () вектор л увеличивается на один элемент, который добавляется в его конец, Таким образом, в [в. л(хе () -1), также получаемый с помощью л.
Ьасй() (516.3.3) — это последний добавленный (рва))е(() в вектор элемент. В этом нет ничего необычного, если не считать применения слова «вектор» вместо слова «стек», Суффикс Ьасй означает, что элементы добавляются в конец вектора, а не в начало. Добавление элементов в конец вектора может оказаться дорогостоящей операцией, поскольку это может потребовать выделения дополнительной памяти под новый элемент. Конкретные реализации обязаны обеспечить более гладкое поведение, когда операции выделения памяти происходят намного реже повторяющихся стековых операций. Операция рор Ьасй() не имеет возврата. Она лишь выполняет удаление элемента, так что если мы хотим знать, что было на вершине стека до выполнения этой операции мы сами должны озаботиться этим и предварительно посмотреть туда. Я бы не назвал стек с таким поведением моим любимым стеком (52.5.3, э2.5.4), но он считается более эффективным, и это стандарт.
Зачем вообще могут потребоваться стековые операции над векторому Наиболее очевидная причина — чтобы реализовать стек Ц17.3.1), но есть и другая причина— чтобы строить вектор постепенно, например вектор геометрических точек, принимаемых из входного потока.
В такой постановке задачи мы заранее не знаем, сколько точек будет получено, и поэтому не можем с самого начала определить размер вектора с последующим чтением значений его элементов. Вместо этого можно написать следующее; 16.3. Контейнер типа не<1ог 643 ыЫ/е (сгп » Ьи/) ( З (Ьиз == зепипе!) гетгп! Р <лес/г пе»г роии с!аез.риза Ьасй (Ьи/) 1 ) ) Это гарантирует правильное расширение вектора. Если бы нам требовалось лишь добавлять новые точки в вектор с!без, то можно было бы инициализировать его подходящим конструктором прямо из потока (516.3.4).
Но чаше всего сначала требуется несколько преобразовать входные значения и лишь затем наращивать структуру данных — ризЬ Ьасй() поддерживает этот подход. В программах на языке С в этих случаях принято использовать библиотечную функцию геаИос() . Таким образом, гесгог (и вообще любой стандартный контейнер) обеспечивает более общую и элегантную, притом не менее эффективную альтернативу функции геаИос ( ) .
Размер вектора, возвращаемый функцией з!ее(), неявно увеличивается при вызове разЬ Ьасй ( ), так что переполнение (огезу/оп) векторам не грозит (пока имеется свободная память; см. З19.4.1). Однако им грозит обратное явление — «переисчерпание» (апз/ег//он ): юЫ !'() ( ГЕС/ОГ<!Пг> Г! ».рор Ьаса(); г.ризЬ Ьаса(7) ! ) // состояние г становится неопределенным //состояние г не определено, возможно оно недействительное 16.3.6. Операции над векторами, характерные дпя списков Операции ризЬ Ьас/г(), рор Ьас/с() и ЬасЬО (Ь16.3.5) позволяют эффективно использовать вектор в качестве стека. Однако иногда бывает полезно добавлять элементы в середину вектора, а также удалять их оттуда: /етр!а/е<с!азз Т, с/азз А = а!!оса/ос< Т» с/азз гесзог ( риЬИс: // ...
У опера ции, характерные для списков: Иега1ог !изет (!/ега1ог роз, сопл! ТЗ х); // добавить х перед роз ЮЫ 1ПЗЕГ1(вегагог роз, з!1е гуре и, сопл! Тз х); //и копий х перед роз У/и - итеротор ввода /з/92./) // вставить эл-ты из последовательности /етр/а1е<сйт 1п> юЫтзегз(вега/ог роз, /пу/гзг, 1п 1аи) 1 !/ега1ог егазе (!(ега/ог роз ) 1 !1ега1ог сказе (Ьегагог /)гзз, Ьега1ог !азз); //удалить элемент в позиции роз д удалить последовательность Результат «переисчерпания» не определен, но очевидная реализации функции рор Ьасй() приведет к записи в область памяти, не принадлежащей веюпору, «Переисчерпания» (как и переполнения) лучше избегать. 544 го1«с!еаг() < У ... )< л удалить все элементы Итератор, возврашаемый функцией 1ивегг(), указывает на вновь добавленный элемент. Итератор, возвращаемый функцией егаге(), указывает на элемент, следуюший за последним удаленным элементом. Чтобы посмотреть, как работают указанные операции, давайте проделаем несколько абсолютно формальных манипуляций с вектором, содержашим названия фруктов.
Сначала определяем контейнер и «заселяем» его фруктами: нес<огсз<с<их> 1гий; Если я разлюблю фрукты, названия которых начинаются на букву р, я могу удалить соответствуюшие названия следуюшим образом: еог<(Гги(<.Ьех(п(),1гид.еп«() ) < гес<огкл<гтд> <: йега<ог р1 =1<и<< ф'~ги(<. Ьеа(п (),1гий. ел<1 (), иица1 ( 'р' ) ) < гес<ог<мппх>::1<его<ос р2 =1<и<< <1(р1,1ги(<.еп<<(), <пй1а< по<( 'р' ) ) < 1ги1<.
егале (р1, р2); Другими словами, сначала сортируем вектор, находим первое и последнее названия фруктов, начинаюшиеся на р, и удаляем из 2<и!< этот интервал элементов. О том, как пишутся функции-предикаты (рге<1(саге 1ипспопв), например 1и(па1(х) (начальная буква есть хт) или иипа1 по<(х) (начальная буква отличается от хз), рассказывается в 818.4.2. Операция е< иле(р1, р2) удаляет элементы, начиная с указуемого итератором р1, и вплоть до (но не включая) элемента, указуемого итератором р2.
Это можно проиллюстрировать графически следующим образом; рг э<аг1ги1< р1 ягоре й(<«Цги(< реасй реаг 1ги1<() < арр<е Операция егале(р1,р2) удаляет репей н реаг, что приводит к следующему содержимому контейнера: 1ги!<() < арр<е ягоре й(ними(< э<а<уса(< Как всегда, затрагиваемая операцией последовательность элементов фиксируется указанием первого из них, а также указанием элемента, расположенного за последним элементом, вовлекаемым в операцию. 1гий.рилй Ьасй ( "реасй" ) < 1гий.риэй Ьасй ( "арр!е" ) < 1гий.рилй Ьасй( "йпп1гий" ) < 1гий.рилй Ьасй ( "реагь ); 1п«й.риэй Ьасй ( "э<а<уса(<" ); )ги1<.риэй Ьасй( "хгаре" ) < Глава ) б.