
Динамо-машины Метод Сократа
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 86 87 88 89 90
всякий раз по окончании вывода строки не требуется, лучше использовать endl один раз, в конце цикла; это также повысит производительность вашей профаммы.
Все это были комментарии низкого уровня ; однако есть замечание и на более высоком уровне.
6. Вместо разработки собственных циклов, используйте там, где это ясно и просто, стандартные библиотечные алгоритмы сору и for each. Больше полагайтесь на свой вкус. Я говорю так потому, что это как раз тот случай, когда определенную роль и фа ют вкус и эстетизм. В простых случаях сору и for each могут улучшить читаемость и понятность кода по сравнению с циклами, разработанными вручную. Тем не менее, часто код с использованием for each может оказаться менее понятным и удобочитаемым, так как тело цикла придется разбивать на отдельные функторы. Иногда такое разбиение идет коду только на пользу, а иногда совсем наоборот.
Вот почему вкус играет здесь такую важную роль. Я бы в рассматриваемом примере заменил цикл чем-то наподобие
copyCv.begi п(),v.end(),ostream iterator lt;i nt gt;(cout, \n ));
Кроме тою, при использовании того же алгоритма сору вы не сможете ошибиться в применении !=. ++, end() и endl, поскольку вам просто не придется ничего этого делать самостоятельно. (Конечно, при этом предполагается, что вы не намерены сбрасывать буферы потока при выводе каждого целого числа. Если эго для вас критично, вам действительно придется писать свой цикл вместо использования стандартного алгоритма std: :сору.) Повторное использование, в случае корректного применения, не только делает код более удобочитаемым и понятным, но и повышает его качество, позволяя избежать различного рода ловушек при написании собственного кода.
Вы можете пойти дальше и написать свой собственный алгоритм для копирования контейнера, т.е. алгоритм, который работает со всем контейнером в целом, а не с какой-то его частью, определяемой диапазоном итераторов. Такой подход автоматически заставит использовать const iterator. Рассмотрим следующий пример,
tempiate lt;class Container, class Outputlterator gt; outputlterator copy(const Container amp; c,
outputiterator result)! return std::copy( с.begin(), c.endC), result );
Здесь мы просто написали обертку для применения std: :сору ко всему контейнеру целиком, и так как контейнер передается как const amp;, итераторы автоматически будут константными (const iterator).
gt; Рекомендация
Будьте максимально корректны при использовании модификатора const. В частности, используйте const i terator, если вы не модифицируете содержимое контейнера.
Сравнивайте итераторы при помоши оператора !=, а не lt;
Лучше использовать префиксную форму операторов -- и ++ вместо постфиксной, если только вам не требуется старое значение переменной.
Лучше использовать существующие алгоритмы, в частности, стандартные алгоритмы (такие как for each), а не разрабатывать собственные циклы.
Далее нам встречается код cout laquo; v[0];
При выполнении этого кода, вероятно, результатом будет 1. Это связано с тем, что программа просто пишет в паметь и читает из нее -- поступая при этом совсем не так, как положено, что тем не менее не приводит к немедленным фатальным сбоям (а жаль!).
V.reserveC 100 );
assertC v.capacityC) == 100 );
Здесь также должна выполняться проверка с использованием оператора gt;=, а не ==, и даже такая проверка будет излишней (.мы уже рассмазривали этот вопрос раньше).
cout laquo; v[0];
А вот тут нас ждет сюрприз! На этот раз, скорее всего, инструкция cout laquo; v[0] ; приведет к результату 0: значение 1 таинственно исчезает...
Почему? Будем считать, что вызов reserve(100) приводит к запуску перераспределения памяти для внутреннего буфера v (если только в результате первоначального вызова reserveC?) не было вьщелено достаточного количества памяти для размещения 100 или большего числа элементов). При этом контейнер v копирует в новый буфер только те элементы, которые он в действительности содержит - но на самом деле он не содержит ни одного элемента! Новый буфер изначально заполнен неопределенными значениями (чаще всего - нулями), что и становится очевидным при выполнении cout laquo; v[0];.
= 3; . . = 4;
v[99i = 100;
Думаю, теперь у вас нет никаких сомнений в ошибочности этого кода. Это неправильный, плохой, неверный код... но поскольку operator[] не требует выполнения проверки диапазона, в большинстве реализаций стандартной библиотеки этот код будет молча работать, не приводя к исключениям или аварийному завершению программы.
Если же вместо приведенного выше фрагмента написать
v.at(2) = 3; v.atCS) = 4;
...
v.at(99) = 100;
то проблема станет очевидной, так как первый же вызов приведет к генерации исключения out.....ofLrange.
for(vector lt;int gt;:literator i = v.beginC); i lt;v,endC); i++){ cout laquo; *i laquo; endl;
Здесь опять ничего не будет выведено, а я опять предложу вам заменить этот цикл на
copyCv.begiп(), v.endC), ostream iterator lt;int gt;(cout, \n ));
Еще раз замечу, что такой код автоматически решает все проблемы, связанные с использованием оператора сравнения !=, префиксной формы ++, вызовом endC) в теле цикла и использованием endl. К тому же повторное использование стандартных алгоритмов зачастую автоматически делает код более быстрым и безопасным.
Резюме
Теперь вы знаете, в чем разница между размером и емкостью, а также между оператором operator[] и функцией at(). Если необходима проверка диапазона, всегда используйте функцию at С), благодаря которой вы сэкономите немало времени, которое в противном случае придется потратить на работу в отладчике.
Задача 2. Строчный двор. Часть 1: sprintf Сложность: 3
В этой и следующей задачах мы рассмотрим чудеса sprintf и разберемся, почему альтернативные варианты всегда (да, всегда) лучше.
Вопрос для новичка
1. Что такое sprintf? Перечислите как можно больше стандартных альтернатив spri ntf.
Вопрос для профессионала
2. В чем сильные и слабые стороны spri ntf? Будьте конкретны в своем ответе.
Решение
Все животные равны, но некоторые животные равнее других . - Джордж Оруэлл, Скотный двор
1. Что такое spri ntf? Перечисли-ге как можно больше ста1здартных альтернатив spri ntf.
Рассмотрим следующий код С, который использует sprintf для преобразования целого числа в удобочитаемое строковое представление.
Пример 2-1: Строковое представление данных в С с использованием sprintf. Функция PrettyFormat получает в качестве параметра целое число и преобразует его в строку в предоставленный буфер. Результат должен иметь размер не менее 4 символов.
void PrettyFormatC int i, char* buf ) { код, простой и понятный: sprintfС buf, %4d , i );
Вопрос на засыпку: как сделать то же на С++1
Впрочем, это не совсем корректный вопрос, так как, в конце концов, этот же код вполне корректен и в С++. Вопрос на засыпку надо сформулировать так: если отбросить все офаничения стандарта С (если это и в самом деле Офзничения), то имеется ли лучший способ выполнить эти же действия в С++ с его классами, шаблонами и прочим?
Этот вопрос интересен тем, что пример 2-1 можно выполнить по крайней мере четырьмя разными стандартными способами, каждый из которых представляет собой определенный компромисс между ясностью, безопасностью типов, безопасностью времени исполнения и эффективностью. Кроме того, перефразируя свиней-ревизионистов из произведения Оруэлла, все четыре способа стандартны, но некоторые из них стандартнее других , да и взяты они из разных стандартов. Они приведены ниже в том порядке, в котором мы будем их рассматривать.
1. sprintf [С99, С++03]
2. snprintf [С99]
3. std: :stringstream [С++03]
4. std: :strstream fC++03]
Перевод с англ. Д. Иванова, В. Недошивина (Дж. Оруэлл. Скотный двор. - Пермь, Изд. КАПИК , [992.)
| 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 86 87 88 89 90 |