|
Программирование >> Обобщенные обратные вызовы
При выполнении этого кода, вероятно, результатом будет 1. Это связано с тем, что программа просто пишет в паметь и читает из нее -- поступая при этом совсем не так, как положено, что тем не менее не приводит к немедленным фатальным сбоям (а жаль!). V.reserveC 100 ); assertC v.capacityC) == 100 ); Здесь также должна выполняться проверка с использованием оператора >=, а не ==, и даже такая проверка будет излишней (.мы уже рассма1ривали этот вопрос раньше). cout v[0]; А вот тут нас ждет сюрприз! На этот раз, скорее всего, инструкция cout v[0] ; приведет к результату 0: значение 1 таинственно исчезает... Почему? Будем считать, что вызов reserve(100) приводит к запуску перераспределения памяти для внутреннего буфера v (если только в результате первоначального вызова reserveC?) не было вьщелено достаточного количества памяти для размещения 100 или большего числа элементов). При этом контейнер v копирует в новый буфер только те элементы, которые он в действительности содержит - но на самом деле он не содержит ни одного элемента! Новый буфер изначально заполнен неопределенными значениями (чаще всего - нулями), что и становится очевидным при выполнении cout v[0];. = 3; . . = 4; v[99i = 100; Думаю, теперь у вас нет никаких сомнений в ошибочности этого кода. Это неправильный, плохой, неверный код... но поскольку operator[] не требует выполнения проверки диапазона, в большинстве реализаций стандартной библиотеки этот код будет молча работать, не приводя к исключениям или аварийному завершению программы. Если же вместо приведенного выше фрагмента написать v.at(2) = 3; v.atCS) = 4; ... v.at(99) = 100; то проблема станет очевидной, так как первый же вызов приведет к генерации исключения out.....ofLrange. for(vector<int>:literator i = v.beginC); i<v,endC); i++){ cout *i endl; Здесь опять ничего не будет выведено, а я опять предложу вам заменить этот цикл на copyCv.begiп(), v.endC), ostream iterator<int>(cout, \n )); Еще раз замечу, что такой код автоматически решает все проблемы, связанные с использованием оператора сравнения !=, префиксной формы ++, вызовом endC) в теле цикла и использованием endl. К тому же повторное использование стандартных алгоритмов зачастую автоматически делает код более быстрым и безопасным. Резюме Теперь вы знаете, в чем разница между размером и емкостью, а также между оператором operator[] и функцией at(). Если необходима проверка диапазона, всегда используйте функцию at С), благодаря которой вы сэкономите немало времени, которое в противном случае придется потратить на работу в отладчике. Задача 2. Строчный двор. Часть 1: sprintf Сложность: 3 В этой и следующей задачах мы рассмотрим чудеса sprintf и разберемся, почему альтернативные варианты всегда (да, всегда) лучше. Вопрос для новичка 1. Что такое sprintf? Перечислите как можно больше стандартных альтернатив spri ntf. Вопрос для профессионала 2. В чем сильные и слабые стороны spri ntf? Будьте конкретны в своем ответе. Решение Все животные равны, но некоторые животные равнее других . - Джордж Оруэлл, Скотный двор 1. Что такое spri ntf? Перечисли-ге как можно больше ста1здартньс4 альтернатив spri ntf. Рассмотрим следующий код С, который использует sprintf для преобразования целого числа в удобочитаемое строковое представление. Пример 2-1: Строковое представление данных в С с использованием sprintf. Функция PrettyFormat получает в качестве параметра целое число и преобразует его в строку в предоставленный буфер. Результат должен иметь размер не менее 4 символов. void PrettyFormatC int i, char* buf ) { код, простой и понятный: sprintfС buf, %4d , i ); Вопрос на засыпку: как сделать то же на С++? Впрочем, это не совсем корректный вопрос, так как, в конце концов, этот же код вполне корректен и в С++. Вопрос на засыпку надо сформулировать так: если отбросить все офаничения стандарта С (если это и в самом деле Офзничения), то имеется ли лучший способ выполнить эти же действия в С++ с его классами, шаблонами и прочим? Этот вопрос интересен тем, что пример 2-1 можно выполнить по крайней мере четырьмя разными стандартными способами, каждый из которых представляет собой определенный компромисс между ясностью, безопасностью типов, безопасностью времени исполнения и эффективностью. Кроме того, перефразируя свиней-ревизионистов из произведения Оруэлла, все четыре способа стандартны, но некоторые из них стандартнее других , да и взяты они из разных стандартов. Они приведены ниже в том порядке, в котором мы будем их рассматривать. 1. sprintf [С99, С++03] 2. snprintf [С99] 3. std: :stringstream [С++03] 4. std: :strstream fC++03] Перевод с англ. Д. Иванова, В. Недошивина (Дж. Оруэлл. Скотный двор. - Пермь, Изд. КАПИК , [992.) и наконец, если этого вам покажется мало, есть еще пятый, нестандартный (но, возможно, станет таковым) способ для простых преобразований, не требующих специального форматирования. 5. boost:: 1 exical......cast [Boost] Итак, приступим к рассмотрению. РадосI и и печали sprintf 2. В чем сильные и слабые стороны sprintf? Будьте конкретны в своем ответе. Рассмотренный код функции PrettyFormat - всего лишь один из примеров использования sprintf. Я использовал его лишь как повод для обсуждения, так что не надо придавать слишком большое значение этой однострочной функции. Вопрос гораздо шире - мы выбираем способ для представлена нсстроковых значений в виде строк. Давайте проанализируем функцию sprintf более детально. У нее есть два наиболее важных достоинства и три недостатка. /. Простота использования и ясность. Как только вы изучите, как флаги форматирования и их комбинации влияют на форматирование строки, использование sprintf становится простым и очевидным. Очень трудно превзойти семейство функций pri ntf в задачах по форматированию текста. (Да, запомнить все флаги не просто, но обычно это относится к достаточно редко используемым флагам форматирования.) 2. Максимальная эффективность (возможность непосредственного использования существующих буферов). При использовании функции sprintf результат помешается непосредственно в заранее подготовленный буфер, так что функция PrettyFormat выполняет свою работу без динамического выделения памяти или других побочных действий. Она получает уже выделенную память и записывает результирующую строку непосредственно в эту память. Конечно, не стоит придавать этому достоинству большее значение, чем оно того заслуживает. Ваше приложение может даже не заметить разницы между использованием sprintf и другими методами. Никогда ничего не оптимизируйте преждевременно, приступайте к оптимизации только тогда, когда профилирование покажет, что она действительно необходима. Начинайте с ясного и понятного кода, быстрым его можно сделать потом........ если в этом появится необходимость. В нашем случае необходимо учесть, что эффективность достигается за счет инкапсуляции управления памятью. Увы, как известно большинству пользователей функции sprintf, у нее есть и значительные недостатки. 3. Безопасность. Функция spri ntf - распространенный источник ошибок, связанных с переполнением буфера, когда размера выделенного буфера не хватает для размещения выводимой строки. Рассмотрим, например, следующий код. char smal1Buf[5]; int value = 42; PrettyFormat( value, smallBuf ); вроде бы все в порядке assertC value ==42 ); В этом случае значение 42 достаточно мало для того, чтобы результат 42\0 полностью разместился в пяти байтах smal 1 Buf. Но представим, что однажды код изменится на такой, как показано ниже. char smal1Buf[5]; int value = 12108642 ; В настоящее время - [C99], стандарт С++ [С++031 основан на более ранней версии С. Распространенная ошибка начинающих программистов - полагаться на спецификатор ширины вывода, в нашем случае - 4, который не работает так, как они рассчитывают, поскольку этот спецификатор ука.зывает минимальную длину вывода, а не максимальную.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |