Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 111
Текст из файла (страница 111)
!) оогй аяяуп (Тп 0гя1, 1л !ая1! // копирование ие (6гз1:!аз1( оо!даял!уп (яеее 1уре л, сопя! Тй оа!); // и копий оа! О.. ); Контейнер оес1ог обеспечивает быстрый доступ к произвольным элементам, но изменение размера вектора обходится относительно дорого. Поэтому обычно при создании вектора мы задаем его начальный размер. Например: иес!ог !(есогд> ег (10000); оо!с!/(1п! е1, т'1я2) ( оесгогк!л1> и' (я1); иес1огчдоиЫе>" р = песо оес1огкс!оиЫе> (я2); ) 16.3, Вектора 505 Элементы размещенного таким образом вектора инициализируются конструкторами по умолчанию для типа элементов.
То есть каждый из 10 000 элементов ог инициализируется конструктором л(есогс((], а каждый из в1 элементов контейнера о! инициализируется !п1(). Отметим, что для встроенных типов конструкторы по умолчанию выполняют инициализацию нулем соответствующего типа Я 4.9.5, з 10.4.2). Если у типа нет конструктора по умолчанию, то невозможно создать вектор с элементами такого типа без явного присваивания каждому элементу какого-то значения. Например: // бесконечная точность с!аьв Мит ( риб!1а !унт (!опу); // нет конструктора по умолчанию оес1ог 1чит>о! (!000); //оичибка: нвт конструктора по умолчанию оес1огччит>и! (!000,!уит (0)); //всв в порядке оо!с!Д!п1 !) ( овсгогкс!ьаг> осО (-!); // колтилятору довольно легко видать предупреждение овсгог'с!ьаг> ос! (!); ) оо!а!д() ( У(-!); ) //обманиваеч/!), передавая ей большов положительное число! При вызовеД-1) литерал -1 преобразуется в (довольно большое) положительное целое число Ц В.6,3).
Если повезет, компилятор пожалуется на это. Размер вектора можно также указать неявно, задав начальный набор элементов. Это делается путем задания конструктору последовательности значений, из которых конструируется оес1ог. Например: ооИ /(сопя! !!з1кХ>й !в1) ( овс1огкХ> о! (!в! беаь'и (), !з1 епс! ()); //копирование злечвнтов из списка спас р() = 'ь!евра!г'; оес1огкс!ьаг> о2 (о, йр(в!яво/(р)-Ц; // копирование сипволов из С-строки И в том, и в другом случае конструктор изменит размер контейнера оес1ог при копи- ровании элементов из его входной последовательности.
Поскольку оес1ог не может иметь отрицательное число элементов, его размер должен быть неотрицательным. Это отражено в требовании, что его в!яе 1уре должен быть типом ипв!Опес(, что в некоторых архитектурах допускает больший диапазон разме- ров векторов. Однако это может и привести к сюрпризам: 506 Конструкторы оес1ог, которые можно вызвать с единствеььным аргументом, во избеэкание случайного преобразования Ц 11.7.1) объявлены ехр1!с11. Например; Копирующий конструктор и копирующий оператор присваивания копируют элементы вектора. Для вектора с большим количеством элементов это может быть дорогой операцией, поэтому вектора обычно передаются по ссылке. Например: оо!б/1 (оес1ог<1а1>й); //обычный стиль соЫ /2 (сопя! оес1ос<!и!>й] // обычный стиль оо(ь! ГО (оесг ос< т 1>). //редкий стиль ооЫ6 () ( //передача ссылка //передача ссылки // копирование 1О 000 элементов в йован оес1ог // для использования ф!!нкцией!З () Функции присваивания аяя(дп предоставляют альтернативу конструкторам со мно- гими аргументами.
Они нужны, поскольку оператор = воспринимает только опиноч- ный правый операнд, так что аяя!Вп () используется там, где требуется значение аргу- мента по умолчанию или диапазон значений. Например: с1аьз Вопд ( /* ... */); ооЫ/(оес1ог<!чит>й оп, оес1ог<сааг>й ос, оес1ог ВооЬ>й оЬ, !1я1<ВооЬ>й !Ь) ( // присваивание ип вектора иэ ! О копий !чит (0) //присваивание ос строки литерал // присваивание элементов списка Таким образом мы можем иницию!изи ровать вектор любой последовательностью элементов его типа и так же присвоить любую такую последовательность. Важно отметить, что это делается без явного введения большого числа конструкторов и функций преобразования.
Также отметим, что присваивание полностью заменяет элементы вектора. По замыслу все старые элементы удаляются и вставляются новые, После присваивания размер вектора становится равным числу присвоенных элементов. Например: ооЫ Г() оесгог<сдаг > о (!О, 'х ); //о Изео==!О, каясдыйэлемент имеет значение 'х' оес1ог<Ы1> о (!О); вес!о г<Ы !> о2 = оесгог<1пс> (10); оесгог<т1> оЗ = о2; оес1ог<1п1>о) = 10, осего г<еп 1> о (10000), 0- э'! (о); /2 (о); /2 (о)'; ожаззкгп (10, Ь!ит (0)); сйаг я(] = 'литерал"; ос аяя!Оп (я, йя(я!есор (я) — 1\); оЬ аяятип (!Ь Ьеигп (), 16.епс( ()); Глава 16.
Организация библиотеки и контейнеры // правильно; вектор из ! 0 т1 О правильно: вектор из 10 !п1 //правильно: иЗ вЂ” копия о2 //о~иибка: попытка неявно //преобразовать !О в оес1ог<1пг> о07 1б 3. Вектора // ссяые!) ==5, казкдий элемент илгеет значение 'а' и.аяя!уп (5, 'а'); Естественно, то, что делает азз(ца (), может быть неявно выполнено предваритель- ным созданием соответствующего контейнера иес1ог и последующим присваиванием этого контейнера. Например: ио!д32 (иеегогкВаай>й ий, !!я1 -Ваой>й !Ь) ( иесгоггВовй> иг ',!Ь.Ьеуш (), !Ь.епд ()); ей=ой // Однако это может оказаться некрасиво и неэффективно.
Использование конструктора вектора с двумя аргументами одного и того же типа может вызвать кажущуюся неоднозначность. иве!от-!и1> и (10, 50), //иес1ог(ягэе иа!ие) ичи иег1огп1ега1ог! 51ега1огу)? сег1ог!яеяе иа!ие)! иес1ог (иес1огкш1>эяие 1уре, сопя1!пгй, сапя1иее1ог<!п1>эаПоса1аг 1урей); а не иее1ог(иее1ог !пйк Пега1ог, иессог !п1>эгега1ог, сапя1 иессог шг>са1!оса1ог 1урей); Библиотека достигает этого за счет подходящей перегрузки конструкторов и аналогичным образом разбирается с похожими неоднозначностями для аяз!!!п () и !пзег! () Я 163 6). 15.3.5. Операции со стеком Чагце всего мы представляем себе вектор компактной структурой данных, к элементам которой мы имеем доступ через индексы. Однако мы можем игнорировать это конкретное представление и рассматривать вектор как пример абстрактного представления последовательности.
Такой взгляд на вектора и рассмотрение обычных применений массивов и векторов делают очевидным, что для векторов имеет смысл использовать операции со стеком: 1етр!а1екс!аяя Т, с!аяя А = ацоса1огкТ» с!аяя иес1ог( риЫсс: 0". // операции ео стеком // добавление в конец О удаление последнего элемента иаЫ ризй Ьасй (еопя1 Тйх); иоЫрор Ьасй (); //- Функции рияй Ьасй () и рор Ьасй () обращаются с вектором как со стеком, добавляя элементы в конец и удаляя последние элементы. Например: иоЫЯ(иес1огкейаг й я) ( я.рияй Ьасй ('а'); Однако 1п1 — не итератор, и реализация должна гарантировать, что на самом деле будет вызван Глава 18. Организация библиотеки и контейнеры 508 е.рией Ьасй ('Ь'); е.раей Ьасй ('с3; е.рор Ьасй (); (1 (е(елее ()-1]!= 'Ь') еггог ('невозможиор) е.рор Ьасй (); у («.Ьасй ()!= 'а') еггог( не должио произойтиГГ Каждый раз при обращении к ривй Ьасй () вектор в увеличивается на один элемент; этот элемент добавляется в конец.
Поэтому в(в.в(яе ()-1], также получаемый функцией з.Ьасй () Я 16.3.3), — это элемент, который был последним размещен (розней) в векторе. Если не считать использования слова «вектор» вместо «стек», во всем этом нет ничего необычного. Приписка Ьасй (назад) используется, чтобы подчеркнуть, что элементы добавляются в конец вектора, а не в начало. Добавление элементов в конец вектора может оказаться дорогой операцией, поскольку требует дополнительной памяти. Однако реализация должна гарантировать, что повторяющиеся стековые операции не приволят к дополнительным расходам прн каждом своем вызове'.
Отметим, что функция рор Ьасй () не возвращает значения. Она просто снимает (рор) элемент с вершины стека, и если мы хотим узнать, что было на вершние стека перед этим, то должны посмотреть. Я бы не назвал такой стек моим любимым Я 2.53, 8 2.5.4), но он имеет основания считаться более эффективным, и это стандарт. Зачем совершать стековые операции с вектором? Одна очевидная причина — чтобгя реализовать стек 8 17.3.1), но более общая причина состоит в том, чтобы строить постепенно. Например, нам может захотеться прочитать из входного потока последовательность точек, однако мы не знаем, как много точек прочтем, и поэтому не можем сразу выделить память под вектор нужного размера, чтобы сразу прочитать туда данные.
Вместо этого мы можем написать: нессог«ротс> с(бее; но(а' а<ЫротГе (Ротг еепапе() ( Рот1 ЬиЯ ыдд(е (с(п» Ьиу) ( (У (Ьи/== еепапе() гаги; сгаее.раей Ьасй (ЬиЯ // проверяем новую ~почку Итератор, возвращаемый, указывает на только что вставленный элемент. Это гарантирует, что вектор расширится насколько нужно. Если нам болыпе ничего не требуется, кроме как записать в оес1ог новую точку, мы могли бы инициализировать сЫез прямо из входного потока, обеспечив соответствующий конструктор Ц 16.3.4).
Однако обычно на входе производят неболыцую обработку и по мере развития программы постепенно расширяют структуру данных; ривй Ьасй () поддерживает такой подход. В программах на С чаще всего пользуются функцией геайос () из стандартной библиотеки С. Таким образом пес(ог — и вообще любой стандартный контейнер — обеспечивает более универсальную, более изящную и не менее эффективную альтернативу геайос ().