Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdf60 Часть ! # Вевдеии1е в програтмироваитв на С^^н
Честно говоря, не стоит рассчитывать на существенную помощь компилятора с его сообщениями об ошибках. Лучше полагаться на собственный разум. Един ственная надежная информация в сообщении об ошибке — это место, где она произошла. Все остальное не всегда корректно. И даже такая информация бывает некорректной. Иногда источник ошибки — предыдущая строка. Так что, когда получите сообщение об ошибке (а без этого вряд ли обойдется), не концентри руйтесь на указанной строке — проверьте и предыдущую.
Часто компилятор выдает несколько сообщений об ошибках. Многие програм мисты, сомневаясь в причине, начинают с первого сообщения, потом анализируют второе, третье. Им кажется, что они увидят в них больше смысла.
Не стоит идти таким путем. Не анализируйте второе сообщение, если не поняли первого. Исправив первую ошибку, не беритесь сразу за вторую. Это неправиль но. Раньше компиляция занимала несколько минут или даже часов. В таком случае имело смысл потратить время на анализ сообщений компилятора (даже спонтан ных) и устранить максимальное число ошибок.
В наши дни компиляция выполняется за секунды. Это быстро и недорого, так что не тратьте время на анализ второй и третьей ошибок. Они могут появиться не из-за реальных ошибок, а потому, что компилятор не уследил за ходом программы из-за первой ошибки. Время программиста стоит дороже времени компилятора. Более опытные программисты отличают ложные ошибки от реальных. Начинаю щим же лучше исправлять первую ошибку и перекомпилировать программу.
Ниже приведена версия листинга 2.1 с одной небольшой ошибкой. Компилятор генерирует три сообщения об ошибках с номерами строк.
#include <cmath> #inclucle <iostream> using namespace std;
const double PI = 3.1415926;
int main(void)
double |
х=Р1, у=1, Z, |
cout « |
"Добро пожаловать в мир C++ << endl; |
Z= у + 1 ;
у= pow(x,z);
cout |
« |
"Вэтом мире pi в квадрате равно « |
cout |
« |
"Приятного дня!" « endl; |
return 0;
}
Compiling.
ch02.cpp
/ / |
директива |
препроцессора |
/ / |
директива |
препроцессора |
/ / |
директива |
компилятора |
/ / |
определение константы |
|
/ / |
функция возвращает целое значение |
|
/ / |
определение переменных |
|
/ / |
вызов функции |
|
/ / |
операция |
присваивания |
/ / |
вызов функции |
|
у « |
endl; |
|
/ / |
оператор возврата |
|
/ / |
конец блока функции |
|
c:\Data\ch02.cpp(7) : error C2143: syntax error |
: missing |
'; ' before ' « ' |
|
||||
c:\Data\ch02.cpp(7) :error C2143: syntax error |
: missingr |
' ;' before ' « ' |
|
||||
c:\Data\ch02.cpp(10) : error C2296 |
'«' |
illegal |
l e f t |
operand |
has type |
'double' |
|
c:\Data\ch02.cpp(10) : error C2297 |
'«' |
illegal |
right |
operand has type |
'char [29]' |
||
c:\Data\ch02.cpp(11) : error C2296 |
'«' |
illegal |
l e f t |
operand |
has type |
'double' |
|
c:\Data\ch02.cpp(11) : error C2297 |
'«' |
illegal |
right |
operand |
has type |
'char [17]' |
|
Error executing ci.exe. |
|
|
|
|
|
|
|
ch02.exe - 6 error, 0 warning(s)
Вся эта интеллектуальная деятельность компилятора совершенно напрасна. Можно потратить часы, пытаясь найти истинную причину и пробуя добавить точку
сзапятой перед << в строке 7, или рыться в справочнике, чтобы понять, что не так
стипами в строках 10 и 11. Все это ложный путь. Строки 7, 10 и 11 не содержат ошибок — не важно, что говорит компилятор. Ошибка в строке 5. К примеру, программист просто нажал запятую вместо точки с запятой.
Глава 2 » Быстрый старт: краткий обзор С-^^- |
| |
61 |
| |
И еще одно замечание относительно сообщений компилятора. Программисты |
|
||
весьма часто делают такого рода ошибки. Они простые, незаметные и неожидан |
|
||
ные. Учитывая вводящие в заблуждение сообщения компилятора, найти их бывает |
|
||
непросто. Нередко под подозрение попадают совсем другие операторы, особенно, |
|
||
если их синтаксис не очень хорошо понятен. Программист может |
приступить |
|
|
к исправлению этих операторов. Они становятся неверными, что лишь увеличи |
|
||
вает число сообщений об ошибках. Программист анализирует их, находит еще |
|
||
что-то, пытается исправить, но опять усугубляет дело и т. д. Потратив |
много |
|
|
времени (и нервов), он, наконец, находит ошибку с мыслью: "Как можно было сделать такую глупую ошибку? Как я мог ее не увидеть? Как плохо быть таким тупым..."
Так что совет "не обращать внимания на сообщения компилятора об ошибках" нужно понимать буквально. Не следует оценивать себя по этим ошибкам. В конце концов они будут найдены и исправлены, но важно то, сколько времени на это уйдет. Важна уверенность в своих знаниях. Излишняя самокритичность ее подо рвет. Настанет момент, когда вы заслужите одобрительного похлопывания по пле чу: ведь вы устранили ускользаюшую от внимания проблему. Не вините себя. Развивайте чувство уверенности в своих силах и способностях.
Иногда компилятор генерирует предупреждающие сообщения. Когда набере тесь опыта, лучше анализировать их аналогично сообщениям об ошибках, ведь предупреждения часто отражают реальные ошибки. Со временем лучше перестать обращать на них внимание. Они порой вводят в заблуждение. В то же время, предупреждения не препятствуют генерации объектного кода и отладки, т.е. можно протестировать исходный код на этапе выполнения. Позднее следует ра зобраться в предупреждениях и избавиться от них.
Следующий шаг после успешной компиляции — компоновка (в некоторых инструментальных средствах это называется building — построение программы). Если программа состоит из нескольких исходных файлов, каждый файл может (и должен) в процесс разработки компилироваться отдельно. Когда исходный код содержит описания идентификаторов (переменных или функций), они часто опре деляются в другом файле, и у компилятора возникает проблема — он не знает адресов этих идентификаторов, так как обрабатывает файлы поочередно. Компо новщик проходит все объектные файлы и разрешает внешние ссылки на иденти фикаторы, определенные в других файлах.
При компоновке к объектному добавляется другой скомпилированный код из некоторых библиотек С+Н-. Хотя библиотечный исходный код обычно доступен, он не компилируется при каждом вызове pow(), operator, << или другой библио течной функции. Такие функции компилируются заранее, и компоновщик разре шает эти внешние ссылки так же, как другие.
Результат этапа компоновки — выполняемый файл программы. Если это про грамма из одного файла, то имя выполняемого файла будет совпадать с именем исходного. Для многофайловой программы именем выполняемого файла будет имя проекта (обычно программист может выбрать любое имя). Расширением, как правило, является . ехе.
Ошибки компоновки встречаются нечасто. Они происходят, как правило, из-за неверного имени функций или некорректных операций с проектом.
Выполняемый файл можно запускать. Большинство интегрированных средств разработки (IDE) позволяют выполнять программу в режиме отладки. Замеча тельно, но лучше отладчик не использовать. На первых этапах освоения С+-Ь вы будете заняты изучением языка, редактора, компилятора и компоновщика — без этого не обойтись. Изучение отладчика — непростая задача. Оно себя не окупит и не даст заметной экономии времени, по крайней мере, пока вы не начнете писать сложные программы C+ + . А до той поры достаточно исследования вывода программы (и добавления в нее при необходимости дополнительных операторов вывода).
ш&
62Часть I « Введение в програм;.>
Иеще одно предупреждение. Очень часто, глядя на результаты программы, программист не замечает их некорректности. Трудно понять, почему, но факт. Воз можно, это как-то связано с самовнушением, но, кто знает... Как бы то ни было, ошибки этапа выполнения часто пропускают.
Некоторые программисты пытаются избежать этого, записывая предполагае мый вывод еще до запуска программы. Полезный метод, но он не дает полной гарантии. Так что будьте бдительны, проверяя результаты тестов.
Итоги
Итак, вы познакомились с самым важными компонентами языка программиро вания C+ + , знаете, что такое директивы препроцессора и как их использовать, как определять переменные и функции, управлять ходом выполнения программы с помощью операторов условия и циклов, умеете комментировать исходный код, не обращать внимания на вводящие в заблуждения сообщения компилятора. И даже имеете представление о том, как определять и использовать класс C+ + . Замечательно!
Фактически этого достаточно для написания большей части кода C+ + , кото рый потребуется писать, что весьма напоминает изучение обычного языка. "Упро щенный" английский намного компактнее и легче обычного английского, но даже такая малая часть языка позволяет выразить удивительно много. Возможно, это еще одно проявление правила, согласно которому 80% работы делают 20% при нимающих в ней участие.
Но не стоит ограничивать себя этими 20%. Пришло время двигаться дальше и приступить к изучению средств языка, доступных опытному программисту.
^
J^—^ абота с данными и выражениями C+ +
Темы данной главы
^ Значения и их типы ч/ Интегральные типы
•^ Типы с плавающей точкой
,^ Работа с выражениями С++ 1^ Смешанные выражения: скрытая опасность %/ Итоги
1^ ^ данной главе рассказано о том, как работать с данными C+ + , какие
ЖвИк^оступны типы данных, какие поддерживаются операции со значениями
^4!^|[^]^^ разных типов, каких недостатков C++ следует опасаться программисту. Как и многое другое, C++ объединяет в себе противоположные черты. Его набор целочисленных типов данных очень мал, разница между существующими типами невелика, а выбор между ними не всегда очевиден. Между тем набор операций в этом языке очень велик. Некоторые операции C + + весьма сложны, другие отличаются необычной записью. Объединяют типы данных и операции проблемы переносимости: не всегда все одинаково работает на разных машинах.
C++ унаследовал из языка С исключительную гибкость преобразования значений из одного типа в другой и их комбинирования в сложные выражения. Давайте посмотрим, что он предлагает.
Значения и их типы
в C++ каждое значение в каждый момент своего существования (при выполне нии программы) характеризуется своим типом. Переменные C++ ассоциируются
стипами при определении. Тип описывает следующие характеристики значения:
•Размер значений данного типа в памяти компьютера
•Набор допустимых для типа значений (метод интерпретации представляющих значение данного типа последовательностей битов)
•Набор действительных для типа операций
64 |
Часть ! ^ Вне А. |
Например, для значений типа int на некоторых машинах выделяется 4 байта, диапазон разрешенных значений составляет от -2 147 483 648 до 2 147 483 647. Набор допустимых операций включает в себя присваивание, сравнение, сдвиги, арифметические операции и некоторые другие. Значения типа TimeOfDay, о кото ром мы говорили в главе 2, имели размер, вдвое больший, чем int (если компиля тор не добавлял места для выравнивания в памяти с целью обеспечения более быстрого доступа). Для TimeOfDay допустимым является набор значений, состав ляющих любые комбинации значений первого целого (от О до 23) и второго целого (от О до 59). В число допустимых операций с TimeOfDay входят setTimeO, displayTime() и clisplayMilitaryTime(), присваивания, но не сравнения. Конечно, компо ненты TimeOfDay сравнить можно (это целые, и к ним применимы правила int), но сами значения типа TimeOfDay сравнивать нельзя. Следует различать свойства типа и свойства его компонентов. Если в коде клиента нужно сравнить значения TimeOfDay, класс должен поддерживать это с помощью реализации функций isLaterO или compareTime(). (Обратите внимание, что здесь снова используется клиент-серверная терминология).
Каждая переменная C + + должна определяться путем спецификации типа ее значений. Кроме того, типы характеризуют значения констант, функций и выра жений. Это означает, что можно комбинировать типизированные значения в вы ражения, где результатом будут другие типизированные значения, использовать их в других выражениях и т. д.
В большинстве случаев тип обозначается идентификатором, т. е. имеет имя (например, TimeOfDay). Такой способ общепринят и вполне естествен, но это не единственный путь определения типа. C + + допускает так называемые анонимные типы, не имеющие конкретных имен. Эти типы не так широко используются.
Для встроенных типов C + + зарезервированы имена: int, char, bool, float, double и void (фактически, это полный список). Тип void обозначает отсутствие значения, которое можно использовать в выражениях. Мы будем применять его, когда требуется указать на невозможность включения значения в выражения. Например, функция computeSquareO в главе 2 возвращает значение, включаемое в выражения, а функцию displayResultsO в том же разделе таким образом ис пользовать нельзя, так как она значения не возвращает. Если попытаться сделать
это, |
компилятор даст сообщение об ошибке. |
|
|
i nt |
а, Ь; |
/ / |
допускается в С+-ь |
а = computeSquare(x, у) * 5; |
|||
b = displayResults(Pi*Pi) * 5; |
/ / |
ошибка |
|
Другие языки таких специальных "типов" не имеют, так как в них различаются функции (возвращающие значения) и процедуры (не возвращающие значений). C + + наследует синтаксис функций из С, где процедуры и функции едины. Логи чески отсутствие заданного типа возвращаемого значения можно интерпретиро вать как отсутствие типа возвращаемого значения. В языке С это не так. Более
того, |
отсутствие спецификации типа в языке С означает целочисленный тип. |
C + + |
реализует некий компромисс. Если возвращаемый тип не задается, компи |
лятор не требует, чтобы функция возвращала целый тип (как компилятор С). Он предполагает, что возвращается тип void.
displayResults(double у) |
/ / |
в С-ь+ это void |
||
cout |
« |
"В этом мире pi в квадрате равно " « у « |
endl; |
|
cout |
« |
"Приятного дня!" « endl; |
/ / |
нет ошибки в C++ |
Если употребить эту функцию как операнд в выражении, C + + предполагает, что используется старое соглашение С, и программист хочет возвращать целое
Глава 3 • Работа с данными и выражениями С-^нн |
65 |
значение. На этапе выполнения функция displayResultsO будет возвращать "мусор". Компилятор, не долго думая, просто "снимает заш,иту":
b = clisplayResults(PI*PI) * 5; |
/ / не синтаксическая ошибка |
Если оператор return не указывается, то функция, не возвращающая типа, интерпретируется как функция с целочисленным возвращаемым значением:
clisplayResults(clouble у) |
/ / |
C++ предполагает, |
что это int |
||
{ |
|
|
|
|
|
cout |
<< "В этом мире pi в квадрате равно |
" « |
у « endl; |
|
|
cout |
« |
"Приятного дня!" « endl; |
|
|
|
return |
0; |
/ / |
не синтаксическая |
ошибка |
|
} |
|
|
|
|
|
Если необходимо, код клиента может использовать возвращаемое значение:
b = displayResults(PI*PI)*5 |
/ / это допустимо |
Применение int как заданного по умолчанию типа возвращаемого значения тянется из тех времен, когда большинство функций С разрабатывались для воз врата значений и программист мог сэкономить три нажатия клавиши (нетривиаль ное преимущество). Лучше избегать такой практики. Если функция должна возвращать целое значение, напишите int. А если функция не возвращает значе ния, обозначьте ее тип как void.
О с т о р о ж н о ! Всегда указывайте возвращаемый функцией тип. Если функция не возвращает значения, напишите void.
Не полагайтесь на тип, назначаемый C++ по умолчанию.
Типы, определяемые в программе в дополнение к встроенными типам C+ + , называются пользовательскими. Мне не нравится эта терминология, так как пользователи не определяют типов. Пользователь — человек или организация, применяющие разработанную систему для достижения каких-то целей. Состав ти пов и их имена определяет программист. Один из примеров — функция TimeOfDay в главе 2. Вот почему предпочтительнее называть их типами, определяемыми программистом.
Хотя разные типы имеют в C++ разный размер, нет ничего необычного в том, что значения разных типов могут иметь одинаковый размер в памяти. Для разных типов значения отличаются интерпретацией представляющих эти значения битов. Например, битовая последовательность 01000001 интерпретируется как 65, если хранится в целочисленной переменной, или как А, если хранится в переменной символьного типа.
Раньше программисты должны были разбираться в двоичных, восьмеричных, шестнадцатеричных числах, кодах ASCII и EBCDIC, запоминать, сколько будет 2 в 16-й степени (а иногда в 20-й или даже в 32-й), понимать, что такое представле ние с дополнением до 1 или до 2 от отрицательных чисел. Сегодня большинству из них этого не требуется. Тем не менее оборудование компьютера устроено так, что работает с 8-битовыми байтами. В полуслове — 16 бит, в слове — 32. На некоторых машинах слово имеет размер 16 бит, а двойное слово — 32, поэтому полезно знать хотя бы диапазон значений, которые можно хранить в памяти различного размера.
Итак, 4 бита (одна шестнадцатеричная цифра) могут представлять 16 различ ных комбинаций. Обычно эти 16 комбинаций присваиваются целым числам от О до 15. Аналогично 8 бит могут представлять 256 значений (2 в степени 8). 256 комбинаций присваиваются целым числам от О до 255. Как быть, если нужно представлять положительные и отрицательные числа, а не только положительные?
66 Част I ^ Введение в прог,: :-^*^^'*рование на С
В нашем распоряжении все равно 256 комбинаций. Диапазон от-128 до +128 не подойдет, так как в нем 257 значений, а не 256. Стандартное решение — представлять числа от -128 до Н-127.
Два байта (16 бит) позволяют представить 65 536 битовых комбинаций (2 в степени 16). Для положительных чисел это диапазон от О до 65 535, а для отрицательных — от -32 768 (2 в степени 15) до +32 767 (2 в степени 15 - 1). Аналогично 32 бита (4 байта) могут представлять 4 294 967 296 значений. Для чисел со знаком (signed) четыре байта покрывают диапазон от -2 147 483 648 (2 в степени 31) до +2 147 483 647. Вероятно, это все, что нужно знать о двоич ных числах.
Интегральные типы
На машинах любой архитектуры целочисленные типы C++ являются базовы ми. Что означает "базовый"? Просто то, что операции с этими значениями на любой платформе выполняются быстрее. Для обозначения такого типа служит ключевое слово int:
int cnt;
|
|
Размер int определяет диапазон значений, доступных для |
представления |
|
|
(2 в степени, равной числу битов). Сейчас отрасль переходит от |
16-разрядной |
|
|
к 32-разрядной архитектуре, но обе они какое-то время будут использоваться |
|
|
|
одновременно. В большинстве стационарных инсталляций это 32-разрядные |
|
|
|
платформы, но во встроенных и коммуникационных системах продолжают при |
|
|
|
меняться 16-разрядные процессоры, число подобных систем растет. Микропро |
|
|
|
цессоры можно встретить теперь в автомобилях, крупной бытовой технике |
|
|
|
и даже в тостерах. Это означает, что программы, написанные для одной архитек |
|
|
|
туры, не будут работать точно так же на другой. |
|
|
|
Что произойдет, если сохраняемое целое значение не поместится в отведенную |
|
|
|
память? Ничего особенного. В C++ нет такого понятия как арифметическое |
|
|
|
переполнение. Хотите прибавить 1 к 32 767 на 16-разрядной машине? Ради бога. |
|
|
|
Результатом будет -32 768. Хотите прибавить еш.е единицу? Прибавьте. Получи |
|
|
|
те — 32 767. |
|
Л и с т и нг 3.1 |
Демонстрация целочисленного переполнения |
|
|
#inclucle |
<limits> |
|
|
#inclucle |
<iostream> |
|
|
using |
namespace std; |
|
|
int main(void) |
|
||
{ |
|
|
|
int |
num = INT_MAX - 2; |
|
|
int |
cnt |
= 0; |
|
cout « |
"Целочисленное переполнение в C++;" endl; |
|
|
cout « |
"Увеличение от " « num « endl; |
|
|
while (cnt < 5) |
|
||
{ num = num + 1; |
|
||
cnt = cnt + 1; |
|
||
cout « cnt « " " « num « endl; } |
|
||
cout « |
"Спасибо, чтопобеспокоились о границах диапазона целого" « endl; |
|
|
return 0;
|
|
|
Глава 3 # Работа с донными т выражвииймт С+нн |
| |
67 |
||
Целочисленное |
переполнение в C++ |
В листинге 3.1 |
показана программа, которая |
выполнялась |
|||
на 16-разрядной платформе (это была 32-разрядная машина |
|||||||
Увеличение |
от32765 |
|
с 16-разрядным компилятором). Заголовочный файл limit |
||||
|
|
|
|
содержит библиотеку констант для зависимых от реализации |
|||
|
|
|
|
числовых значений на данной платформе. Константа INT_MAX — |
|||
Спасибо, что |
побеспокоились |
одно из таких значений (32 767). В данном примере использо |
|||||
вался цикл while (см. главу 2) и библиотека iostream. Вывод |
|||||||
о границах |
диапазона целого |
программы представлен на рис. 3.1. Переменная num благо |
|||||
Рис. 3 . 1, Целочисленное |
получно проходит весь диапазон и начинает принимать отри |
||||||
цательные значения. Каждый элемент оператора cout имеет |
|||||||
|
переполнение |
|
собственную операцию вывода << (даже разделитель в двойных |
||||
|
не |
прерывает |
|||||
|
кавычках между выводимыми значениями cnt и num). |
|
|||||
|
выполнения |
|
|
||||
|
программы — |
В прежних версиях C++ (и С) для инициализации перемен |
|||||
|
она просто |
дает |
ных нельзя было использовать значения, получаемые на этапе |
||||
|
некорректные |
||||||
|
результ,аты |
|
выполнения — они должны были вычисляться во время ком |
||||
|
|
|
|
пиляции. Однако всегда можно инициализировать переменные |
|||
|
|
не |
только |
конкретным значением, |
но и выражением (например, INT_MAX-2 |
||
|
|
в листинге 3.1). В современном варианте C++ инициализация выражений может |
|||||
|
|
быть достаточно сложной и даже содержать значения, возвращаемые при выпол |
|||||
|
|
нении функций. Например, в C++ допустимо следующее: |
|
|
|||
|
|
int |
а = computeSquare(x, у) * 5; |
/ / разрешается в C++ |
|
|
|
Не очень изящное решение разработчиков компилятора. Вот почему старые версии компиляторов С и C++ не поддерживают это средство. Но разве выше не говори лось, что в вычислениях можно использовать возвращаемое функцией значение?
а = computeSquare(x,y) * 5 / / допустимо в С и C++
Нужно понимать разницу. В примере из предыдущего раздела показано при сваивание. Оно всегда возможно и в C+ + , и в С, и в любом другом языке. В примере данного раздела представлен случай несколько иного рода — инициа лизация. Хотя примеры похожи, смысл совершенно разный. При инициализации выделяется память и устанавливается значение. Присваивание имеет дело с объектом (переменной), для которой память уже выделена. Ей уже назначен адрес и, возможно, размещенное по этому адресу некоторое начальное значение. Записанное по указанному адресу значение заменяется на новое. О разнице уже говорилось в главе 2, а к связанным с нею моментам мы еще вернемся.
Несмотря на прогресс в области разработки компиляторов, компилятор C+ + не стал двухпроходным. Все компиляторы С — однопроходные и не могут "загля дывать вперед". Вот почему они не в состоянии использовать еще не определенное значение, даже если оно определяется на следующей строке. Например, такая запись будет ошибочной:
int а = b, b(5); / / ошибка в C++
Здесь переменную b нельзя использовать для инициализации переменной а. Обратный порядок допустим. (Обратите внимание, что синтаксис инициализации аналогичен вызову функции. В языке С следующая запись не разрешается, но она допустима в C+ + ):
int b(5), а = b; |
/ / это приемлемо |
Спецификаторы типов
C++ наследует из языка С методы тонкой настройки диапазонов целых чисел. Это осуществляется с помощью спецификаторов типов. Ключевые слова signed, unsigned, short и long изменяют размер памяти, выделенной для целых чисел, или интерпретацию битовой последовательности.
I 68 I |
Часть I # Введение в протратштрог- |
Спецификатор signed используется по умолчанию — указывать его не нужно. Например, следующее определение переменной cnt имеет в точности тот же смысл, что и предыдущее:
|
|
|
signed |
int |
cnt; |
|
/ / signed по умолчанию |
|
||
|
|
|
Спецификатор unsigned можно использовать для переменных, которые не |
|||||||
|
|
|
могут принимать отрицательные значения (индексов, счетчиков, данных по |
|||||||
|
|
|
остатку, количеству и пр.). Этот спецификатор |
не изменяет размера |
области |
|||||
|
|
|
|
|
|
|
памяти, выделенной для значения (16 или 32 бита), но |
|||
Целочисленное переполнение |
в |
C++ |
|
изменяет |
интерпретацию |
последовательности |
битов. |
|||
Увеличение |
от 32765 |
|
|
|
|
Допустимым диапазоном для целых без знака (unsigned) |
||||
1 |
32766 |
|
|
|
|
|
будет от -32 768 до +32 767 на 16-разрядной машине (но |
|||
2 |
32767 |
|
|
|
|
|
не от О до 65 535) и от О до 4 294 967 295 на 32-разрядной |
|||
3 |
32768 |
|
|
|
|
|
||||
4 |
32767 |
|
|
|
|
|
машине. В листинге 3.2 представлен предыдущий пример, |
|||
5 |
32766 |
|
|
|
rpi |
|
где вместо целого signed используется unsigned. Результат |
|||
Спасибо, что побеспокоились |
о |
|
||||||||
диапазона |
целого |
|
границах |
выполнения данной версии программы показан на рис. 3.2. |
||||||
|
|
|
|
|||||||
|
|
|
|
|
|
|
Как можно видеть, проблема на данном этапе исчезла. |
|||
Рис. |
3 . 2 . При использовании |
целых |
Конечно, |
она проявится на верхнем диапазоне чисел |
||||||
|
|
значений |
unsigned |
|
unsigned, |
но проявится по-другому. При переполнении |
||||
|
|
переполнение |
происходит |
числа unsigned значение возвращается к О, а не становится |
||||||
|
|
при больших |
значениях, |
|||||||
|
|
большим отрицательным числом. Неизвестно, что лучше. |
||||||||
|
|
чем для |
простых |
целых |
||||||
Листинг 3.2. Демонстрация типа unsigned int
#include <limits> #include <iostream> using namespace std; int main(void)
{
int unsigned num = INT_MAX - 2; int cnt = 0;
cout « "Целочисленное переполнение в C++:' endl; cout « "Увеличение от " « num « endl;
while (cnt < 5)
{ num = num + 1; cnt = cnt + 1; cout « cnt « '
cout « "Спасибо, return 0;
}
" « num « endl; }
что побеспокоились о границах диапазона целого" « endl;
Отрицательные значения в переменной unsigned Обратный отсчет, начиная с +1
11
2О
365535
465534
565533
Спасибо, что побеспокоились о границах диапазона целого
Рис. 3 . 3 . Переменная unsigned |
|
не может, содержать |
|
опприцаппельных |
значений. |
При дальнейшем |
уменьшении |
она принимает |
большие |
полоэюительные |
значения. |
Предупреждение |
не выводится |
Применение чисел unsigned integer — неплохая идея (не с точки зрения расширения диапазона значе ний, а в плане уведомления об этом тех, кто будет заниматься сопровождением программы), особенно, когда значение не может быть отрицательным. Но, если программист, сопровождаюилий программу, не поймет намерений разработчика и использует пере менную unsigned integer для отрицательных значений, результат может быть катастрофичным. В листинге 3.3 показана предыдуш^ая версия программы, где пере менная num инициализируется значением 2 и весьма неблагоразумно уменьшается при выполнении цикла (см. рис. 3.3).
Глава 3 # Работа с данны^ли и выражениями С^-ь |
\ 69 |
Л и с т и нг 3.3. Отрицательные значения в переменной типа unsigned
#include <iostream> using namespace std;
int main(void)
{
int |
unsigned num = 2; |
|
int |
cnt |
= 0; |
cout « |
"Отрицательные значения в переменной unsigned" endl; |
|
cout « |
"Обратный отсчет, начиная с +1" « endl; |
|
while (cnt < 5) |
||
{ num = num - 1; |
||
cnt = cnt +1; |
||
cout « |
cnt « " " « num « endl; } |
|
cout « |
"Спасибо, что побеспокоились о границах диапазона целого" « endl; |
|
return 0; |
||
} |
|
|
Два квалификатора управляют памятью, перераспределяемой для переменных long и short:
int cnt; short int short_cnt; long int long_cnt;
Цель здесь не только в том, чтобы обеспечить больший диапазон целых значений, а чтобы сэкономить место там, где можно. Обычно для программистов, применя ющих СН- + , имеет значение производительность. Это касается как скорости выполнения программы, так и занимаемой памяти. Применение чисел signed (без спецификаторов типа) дает самый "быстрый" тип данных, а использование спецификатора long может защитить от переполнения (за счет памяти). Исполь зуя целые значения short, программист может избежать напрасной траты памя ти. Например, переменная cnt в предыдущем примере изменяется от О до 5. Зачем выделять для нее 32 бита на современной машине? Достаточно было бы байта. Если память в дефиците, то указание спецификатора short может стать хорошим вариантом.
Насколько важно использование спецификатора short для экономии памяти и long для расширения диапазона значений? Эти спецификаторы типа усложняют программу. Многие программисты применяют их только в том случае, если им известна проблема переполнения (или нехватки памяти) и ясно, что спецификато ры помогут ее разрешить. В остальных ситуациях большинство программистов предпочитают работать с обычными целыми без спецификаторов и не задумыва ются о подобных вопросах. Особенно это относится к современным 32-разрядным машинам. Использование 4-х байт для обычных целых в какой-то степени защи тит программу от. переполнения. Наличие достаточных объемов памяти делает указание спецификаторов short излишним.
Как это часто бывает со средствами С4-Н-, унаследованными из С, ситуация не вполне такова, как кажется на первый взгляд. Логично предположить, что ддя целого short памяти выделяется меньше, чем для просто целого, а для целого long — больше. Однако стандарт С (и СЧ- + ) требует от разработчиков компиля тора, чтобы тип short int был не д/шннее просто int, а long int — не короче int. На самом деле все не так запутано. На 16-разрядных машинах для переменных short int и int выделяется одинаковое количество памяти, а для переменных long int — 32 бита. На 32-разрядных машинах все как раз наоборот: для short int отводится 16 битов, а для int и long int — 32.
