![]() |
![]() |
![]() |
Анимация
JavaScript
|
Главная Библионтека Само собой разумеется, присваивания v[0] = 1; v[l] =2; будут вполне корректны и осмысленны, если заменить вызов v.reserve(2) вызовом v.resize(2). Можно также получить корректный код, заменив присваивания вызовами v.push„back(l); v.push back(2);, которые обеспечивают безопасный способ размещения элементов в конце контейнера. for(vector<int>::iterator i = v.beginC); i<v.end(); i++){ cout « *i « endl; Во-первых, заметим, что этот цикл ничего не выводит, поскольку вектор все еще пуст. Это может удивить автора рассматриваемого кода, если, конечно, он не сообразит, что по сути он ничего не внес в контейнер, а всего лишь поиграл (так и хочется сказать - с огнем) с зарезервированным местом в памяти, которое официально вектором не использовано. То есть формально в цикле нет ошибки, что, однако, не исключает необходимости привести ряд стилистических замечаний. 1. Старайтесь по возможности использовать const-вариант итератора. Если итератор не используется для модификации содержимого вектора, следует использовать const... i terator. 2. Итераторы следует сравнивать при помощи оператора сравнения ! = , но не при помощи <. Конечно, так как vector<int>: : iterator - итератор произвольного доступа (само собой разумеется, не обязательно типа i nt*!), нет никаких проблем при сравнении < v.end(), использованном в примере. Однако такое сравнение при помоши < работает только с итераторами произвольного доступа, в то время как сравнение с использованием оператора ! = работает со все.ми типами итераторов. Поэтому мы должны везде использовать именно такое сравнение, а сравнение < оставить только для тех случаев, где это действительно необходимо. (Заметим, что сравнение != существенно облегчит переход к использованию другого типа контейнера в будущем, если это вдруг потребуется. Например, итераторы std: : 1 i st не поддерживают сравнение с использованием оператора <, так как являются би-направленными итераторами.) 3. Лучше использовать префиксную форму операторов -- и ++ вместо постфиксной. Возьмите за привычку писать в циклах по умолчанию ++i вместо 1++, если только вам действительно не требуется старое значение i. Например, постфиксная форма оператора естественна при использовании кода наподобие v[i++] для обращения к /-му элементу и одновременно увеличения счетчика цикла. 4. Избегайте излишних вычислений. В нашем случае значение, возвращаемое при вызове v.endO, НС изменяется в процессе работы цикла, так что вместо вычисления его заново при каждой итерации лучше вычислить его один раз перед началом цикла. Примечание. Если ваша реализация vector<i nt>:: i terator представляет собой простой указатель int*, а функция end() встраиваемая, то при определенном уровне оптимизации накладные расходы будут сведены практически к пулю, так как интеллектуальный компилятор будет способен обнаружить, что значение, возвращаемое end О, не изменяется в процессе работы цикла. Современные компиляторы вполне способны справиться с такой задачей. Однако если vec-tor<int>: : iterator не является int* (например, в большинстве отладочных реализаций это тип класса), функции не являются встраиваемыми и/или компилятор не способен выполнить необходимую оптимизацию, вьтесение вычислений из цикла может существенно повысить производительность кода. 5. Предпочтительнее использовать \п вместо endl. Использование endl заставляет выполнить сброс внутренних буферов потока. Если поток буферизован, а сброс буферов всякий раз по окончании вывода строки не требуется, лучше исполызовать endl один раз, в конце цикла; это также повысит производительность вашей профаммы. Все это были комментарии "низкого уровня"; однако есть замечание и на более высоком уровне. 6. Вместо разработки собственных циклов, используйте там, где это ясно и просто, стандартные библиотечные алгоритмы сору и for each. Больше полагайтесь на свой вкус. Я говорю так потому, что это как раз тот случай, когда определенную роль и фа ют вкус и эстетизм. В простых случаях сору и for each могут улучшить читаемость и понятность кода по сравнению с циклами, разработанными вручную. Тем не менее, часто код с использованием for each может оказаться менее понятным и удобочитаемым, так как тело цикла придется разбивать на отдельные функторы. Иногда такое разбиение идет коду только на пользу, а иногда совсем наоборот. Вот почему вкус играет здесь такую важную роль. Я бы в рассматриваемом примере заменил цикл чем-то наподобие сору(v.begi пО,v.end(),ostream iterator<i nt>(cout,"\n")); Кроме тою, при использовании того же алгоритма сору вы не сможете ошибиться в применении !=. ++, end() и endl, поскольку вам просто не придется ничего этого делать самостоятельно. (Конечно, при этом предполагается, что вы не намерены сбрасывать буферы потока при выводе каждого целого числа. Если эго для вас критично, вам действительно придется писать свой цикл вместо использования стандартного алгоритма std: :сору.) Повторное использование, в случае корректного применения, не только делает код более удобочитаемым и понятным, но и повышает его качество, позволяя избежать различного рода ловушек при написании собственного кода. Вы можете пойти дальше и написать свой собственный алгоритм для копирования контейнера, т.е. алгоритм, который работает со всем контейнером в целом, а не с какой-то его частью, определяемой диапазоном итераторов. Такой подход автоматически заставит использовать const iterator. Рассмотрим следутощий пример. tempiate<class Container, class Outputlteratoo outputlterator copy(const Container* c, outputlterator result){ return std::copy( с.begin(), c.endC), result ); Здесь мы просто написали обертку для применения std: :сору ко всему контейнеру целиком, и так как контейнер передается как const&, итераторы автоматически будут константными (const iterator). > Рекомендация • Будьте максимально корректны при использовании модификатора const. В частности, используйте const i terator, если вы не модифицируете содержимое контейнера. • Сравнивайте итераторы при помощи оператора !=, а не < • Лучше использовать префиксную форму операторов - - и ++ вместо постфиксной, если только вам не требуется старое значение переменной. • Лучше использовать существующие алгоритмы, в частности, стандартные алгоритмы (такие как for each), а не разрабатывать собственные циклы. Далее нам встречается код cout « v[0]; При выполнении этого кода, вероятно, результатом будет 1. Это связано с тем, что программа просто пишет в памяпгь и читает из нее -- поступая при этом совсем не так, как положено, что тем не менее не приводит к немедленным фатальным сбоям (а жаль!). V.reserveC 100 ); assertC v.capacityС) == 100 ); Здесь также должна выполняться проверка с использованием оператора >=, а не ==, и даже такая проверка будет излишней (.мы уже рассматривали этот вопрос раньше). cout « v[0]; А вот тут нас ждет сюрприз! На этот раз, скорее всего, инструкция cout « v[0] ; приведет к результату 0: значение 1 таинственно исчезает... Почему? Будем считать, что вызов reserve(100) приводит к запуску перераспределения памяти для внутреннего буфера v (если только в результате первоначального вызова reserveC2) не было вьщелено достаточного количества памяти для размещения 100 или большего числа элементов). При этом контейнер v копирует в новый буфер только те элементы, которые он в действительности содержит - но на самом деле он не содержит ни одного элемента! Новый буфер изначально заполнен неопределенными значениями (чаще всего - нулями), что и становится очевидным при выполнении cout « v[0];. = 3; . . = 4; v[99i"= 100; Думаю, теперь у вас нет никаких сомнений в ошибочности этого кода. Это неправильный, плохой, неверный код... но поскольку operator[] не требует выполнения проверки диапазона, в большинстве реализаций стандартной библиотеки этот код будет молча работать, не приводя к исключениям или аварийному завершению программы. Если же вместо приведенного выше фрагмента написать v.at(2) = 3; v.at(3) = 4; ... v.atC99) = 100; то проблема станет очевидной, так как первый же вызов приведет к генерации исключения out.....otLrange. forCvector<int>:literator i = v.beginC); i<v,endC); i++){ cout « *i « endl; Здесь опять ничего не будет выведено, а я опять предложу вам заменить этот цикл на copyCv.begiп(), v.end(), ostream iterator<int>(cout,"\n")); Еще раз замечу, что такой код автоматически решает все проблемы, связанные с использованием оператора сравнения !=, префиксной формы ++, вызовом end() в теле цикла и использованием endl. К тому же повторное использование стандартных алгоритмов зачастую автоматически делает код более быстрым и безопасным. Резюме Теперь вы знаете, в чем разница между размером и емкостью, а также между оператором operator[] и функцией at(). Если необходима проверка диапазона, всегда используйте функцию at(), благодаря которой вы сэкономите немало времени, которое в противном случае придется потратить на работу в отладчике. 0 1 2 [ 3 ] 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
![]() |