Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
278
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

60 Часть ! # Вевдеии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.

Соседние файлы в предмете Программирование на C++