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

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

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

j

Часть ! ^ Введение а i:;..-... .- . ^-..-•,.^йoвaнl1e на С^-Ф

В C++ есть оператор sizeof, вычисляющий размер данных в байтах. Аргумен­ том может быть имя переменной или имя типа. На любой платформе следующее выражение возвращает значение оператора sizeof:

sizeof(short int) <= sizeof(int) <= sizeof(long i n t )

Интересное следствие: short int и long int всегда имеют строго установлен­ ный размер, независимо от платформы (32или 16-разрядной), т. е. в любой архитектуре значение short int занимает 16 бит, а long int — 32. Вот почему программисты, озабоченные вопросами переносимости программного кода, не используют простых целых, а применяют short int для относительно небольших значений и long int для всех других, которым не хватает short int. Обычно это проектировщики встроенных и коммуникационных систем. В таких системах память приходится экономить (ее объем ограничен, а цена значительна), а один и тот же программный код должен работать на нескольких аппаратных плат­ формах.

С о в е т у е м в зависимости от аппаратных средств, целочисленные значения могут занимать в памяти 16 бит или 32 бита. Значения short i n t всегда занимают 16 бит, а long i n t — 32 бита. Их явное указание устраняет

проблемы переносимости.

Можно ли скомбинировать спецификатор unsigned и short или long int, как в следующем примере?

insigned short int short_cnt; long unsigned int long_cnt;

Да, можно. (Обратите внимание на порядок спецификаторов — это важно.) Такой вариант можно встретить, например, в драйверах контроллеров жестких дисков, где размер файла или чис/ю цилиндров требуют больнлих неотрицательных целых значений. Тем не менее в большинстве приложений лучше избегать излиш­ ней сложности и использовать обычные целые.

И еще одно замечание. В начале данной главы уже упоминалось о старом правиле: когда имя типа опускается, по умолчанию это целое. Данное правило применимо и к описанной ситуации. Если используются типы данных short и long, нет никакой необходимости писать ключевое слово int:

int cnt; short short_cnt; long long_cnt; / / смысл тот же

Целочисленные значения можно представлять как десятичные, восьмеричные и шестнадцатеричные. Например, десятичное 64 представляется как восьмерич­ ное 100 или шестнадцатеричное 40. Чтобы избежать путаницы, целочисленные литералы, начинающиеся с О, обозначают восьмеричную систему, а с Ох —

(или ОХ) — шестнадцатеричную. Так 100 записывается как

100 в десятичном

представлении, но 0100 в восьмеричной записи означает

64 в десятичной,

а 0x100 в шестнадцатеричной — это 256 в десятичной.

 

Для литеральных значений память распределяется точно так же, как для пере­ менных. Единственная разница в том, что их адресами манипулировать нельзя, следовательно, нельзя изменять хранящиеся там значения. Таким образом, для значения 63 может выделяться 2 байта (литерал short) или 4 байта (литерал long). Чтобы указать разницу, в константах часто применяют спецификаторы short или long. Здесь они записываются так: 63s, 63S, 631 или 63L. Аналогично с без­ знаковыми значениями: бЗи, 63U, 63UL или 63us. На практике их применяют редко.

Глава 3 # Работа с данными и выражениями С+Ф

71

Символы

Символьный тип интерпретируется в C++ как еще один вид целого. Он имеет размер 1 байт (8 бит) и может представлять любой символ ASCII: букву, цифру или непечатаемый управляющий символ. Вот несколько примеров определения переменных символьного типа:

char с, oh; char f i r s t ,

last;

Спецификаторов short

и long для символов не существует, но допускаются

спецификаторы signed или unsigned. К сожалению, назначения по умолчанию не стандартизированы. На некоторых машинах символ char означает unsigned char, на других — signed char.

Зачем нужно знать такие подробности? Обычно это не требуется, отсюда и символьный тип не стандартизирован. Но от подобных деталей зависит интер­ претация значений char в вычислениях. Например, signed char может содержать библиотечную константу "конца файла" (EOF), значение которой определяется как - 1 . Тип unsigned char содержит только положительные значения. Таким образом, если попробовать записать - в значение unsigned char, то там окажет­ ся 255, а не - 1 . Поскольку символ char неявно определяется как signed char или unsigned char, возможны проблемы переносимости.

Как любую переменную, char можно инициализировать в определении или присвоить значение позднее. Для инициализации или присваивания разрешается использовать небольшие целые значения. Они будут интерпретированы как коды символов. Символьные литералы заключаются в одиночные кавычки. Это могут быть символы, восьмеричные или шестнадцатеричные значения, либо ESC-no- следовательности. Важно не путать одиночные и двойные кавычки. Одиночные служат для обозначения символьных литералов, а двойные — для строковых литералов (последовательностей или массивов символов):

char с = 'А' , ch = 65;

/ / с и ch содержат 'А'

Это пример использования символьного литерала в кавычках и применения деся­ тичного целого числа. Другие представления символов начинаются с ESC-симво- ла *\\ Он не интерпретируются как обычный символ — это сигнал компилятору, что следующие символы должны восприниматься особым образом, например как восьмеричное или шестнадцатеричное значение:

с = '\0101'; ch = '\0x41';

/ / восьмеричное и шестнадцатеричное значение 'А'

Здесь кавычки и ESC-символы не являются необходимыми. Можно непосред­ ственно использовать шестнадцатеричные и восьмеричные значения, подобно тому, как записано выше десятичное значение 65 (восьмеричный литерал начина­ ется с О, а шестнадцатеричный — с Ох или ОХ):

с = 0101; ch = 0x41;

/ / восьмеричное и шестнадцатеричное значение 'А'

ESC-символы необходимы только в том случае, если значения встраиваются в строку. Кроме того, его применение говорит программисту, сопровождающему программу, что здесь она работает с символами, а не с числами. Наиболее распро­ страненная ESC-последовательность— новая строка, '\п'. Другие стандартные ESC-последовательности включают в себя '\г' (возврат каретки), '\^' (перевод формата или новая страница, '\t' (табуляция), '\v' (вертикальная табуляция) и '\а' (звуковой сигнал).

Одиночные и двойные кавычки имеют в С-1-+ особый смысл, поэтому для пред­ ставления собственно символа одиночной или двойной кавычки также требуется ESC-символ, например, ' \' ' или ' \"'. Это же относится к символу \. Если нужно вывести его на экран, то потребуется два символа — ' \\' .

I

72

I

 

Часть ! # Введение в nporpaft^^i^poi^>

 

 

 

 

 

 

 

 

Есть

и

еще две ESC-последовательности: '\Ь' и ' \ 0 ' . Первая обозначает

 

 

 

 

 

 

возврат на позиции, а вторая — числовой ноль. Это не печатаемый символ (печа­

 

 

 

 

 

 

таемый ' О' имеет числовое значение 48), однако он важен. Данный символ компи­

 

 

 

 

 

 

лятор C + +

вставляет в конец любой строки. Он используется также для гюиска

 

 

 

 

 

 

конца строки. Например, литеральная строка "Hello" содержит не пять, а шесть

 

ABCDEFGHIJKLMNOPQRSTUVWXYZ

 

 

символов. Последний, нулевой символ, вставляет компилятор.

 

 

 

Поскольку массивы мы еще не обсуждали, то строки подробнее

 

abcdefghijklmnopq rstuvwxyz

 

 

сейчас рассматривать не будем, а вернемся к этому позднее.

 

Одиночная

' и двойная

"ка^

 

 

 

имеют специальный

смысл

 

 

C + + интерпретирует символы как малые целые. Следова­

 

новая

строка: тоже. что \

 

 

тельно, можно выполнять с ними арифметические операции

 

 

 

 

 

 

 

 

 

 

 

Рис.

3 . 4 . Вывод

программы

 

и сравнения. В листинге 3.4 показан пример такого рода опера­

 

 

ций. Сначала программа выводит алфавит буквами в верхнем

 

 

 

(листинг

ЗА),

 

 

регистре, а потом — в нижнем. Она демонстрирует также при­

 

 

 

выполняющей

 

 

 

 

 

арифметические

 

 

менение символа ESC для вывода одиночной кавычки, двойной

 

 

 

операции

 

 

 

кавычки и самого символа ESC. Вывод программы демонстри­

 

 

 

как

 

символьные

 

 

руется на рис. 3.4.

 

 

 

 

переменные

 

 

 

 

Листинг 3.4.

 

Пример операций с символьными значениями

 

 

#inclucle <iostream>

 

 

 

 

 

using namespace std;

 

 

 

 

 

int main(voicl)

 

 

 

 

 

 

{

char

ch;

int

cnt;

 

 

/ /

ch содержит 'А'

 

 

ch = 65;

cnt = 0;

 

 

 

 

while (cnt

< 26)

 

 

 

 

 

 

{

coun «

ch;

 

 

 

 

 

 

 

ch = ch + 1; cnt = cnt

+ 1;

}

 

 

 

cout

«

endl;

 

 

 

/ /

ch содержит символ

 

 

ch = 'a'

-

1;

 

 

 

 

 

while (ch

<

' z ' )

 

 

/ /

ch содержит 'а' , ' Ь'

 

 

{

ch = ch + 1;

 

 

 

 

 

cout

«

ch;

}

 

 

 

 

 

 

cout

«

endl;

 

 

 

 

 

 

 

cout

«

"Одиночная \ '

и двойная \ " кавычки имеют специальный смысл\п";

 

 

 

 

 

 

 

 

 

 

/ /

новая строка: то же, что endl

 

 

cout

«

как

и ESC-символ \ \ " «

endl;

 

 

 

return 0;

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

Выполнение арифметических операций с символами нельзя считать хорошей практикой. Программиста, занимающегося сопровождением программы, это мо­ жет запутать. Еще одна проблема в том, что подобные операции будут работать лишь на компьютерах с непрерывным набором символов (например, ASCII). Если машина использует не непрерывный набор символов (например, EBCDIC), то по­ лучатся непечатаемые символы. Между тем арифметические операции с символа­ ми выполняются очень часто, поскольку позволяют писать эстетически приятные программы.

Для символьных переменных отводится один байт. Это означает, что данный символьный тип может поддерживать набор из 256 знаков (включая управляющие и непечатаемые символы). Для английского языка этого более чем достаточно, но затрудняет введение новых наборов символов ASCII. В наборе символов Unicode сделана попытка стандартизировать подобные усилия, так что английские, фран­ цузские, русские, китайские и японские символы включены в один 16-битовый набор символов.

Глава 3 * Робота с донньши и выражения^^и C-t-i-

|

73

|

C-f + поддерживает данные попытки, предлагая расширенный набор символов

 

wchar_t (от wide — широкий). Память для значений такого типа

выделяется,

 

исходя из одного из целочисленных типов — int или short int. Для представле­

 

ния литеральных значений широких символов используется префикс L (в верхнем

 

регистре):

 

 

 

wchar_t WC = L' а' ;

 

 

 

C + + позволяет программисту использовать любой набор символов. Вероятно,

 

для переносимости ASCII будет лучшим вариантом.

 

 

 

Булевы значения

 

 

 

Большинство современных языков программирования поддерживают булевы

 

значения, которые могут быть равны только true или false. Они особенно полез­

 

ны в логических выражениях и для выбора выполняемых ветвей программы.

 

Язык С не поддерживает булев тип. В нем просто любое ненулевое значение

 

интерпретируется как true, а нулевое — как false. Он позволяет

выполнять

 

с этими значениями логические операции (опять же любой ненулевой результат

 

интерпретируется как true, а нулевой — как false). Для обработки логических

 

выражений этого достаточно, однако могут возникать ошибки, не идентифицируе­

 

мые компилятором. Первоначально C + + унаследовал тот же подход,

однако

 

в новом стандарте делается попытка исправить данную ситуацию. Для этого

 

введен тип bool с двумя значениями — true и false.

 

 

 

bool flag = false, result = true;

 

 

 

Именно "делается попытка", так как применение булева типа не устраняет

 

способствуюш^их ошибкам средств, которые унаследованы из языка С. В C + + они

 

используются как вполне легальные. Сами булевы значения интерпретируются

 

как короткие целые. Если вывести значения flag и result из приведенного приме­

 

ра, то первое даст О (а не true), а второе 1 (а не false).

 

 

 

Булевы значения занимают в памяти только один байт. Поскольку булев тип

 

может содержать лишь два значения, под них можно было бы отвести еш,е меньше

 

памяти. Достаточно одного бита, т. е. компилятор может упаковать 8 булевых

 

значений в 1 байт, однако это потребовало бы дополнительного кода на упаковку

 

и распаковку значений, так как современные компиляторы могут адресоваться

 

только к байтам, а не к битам, а некоторые — к паре байтов. Что касается ско­

 

рости выполнения, то нынешние компьютеры обладают наибольшим быстродей­

 

ствием, когда обраш,аются к единицам памяти по четыре байта. Вот почему для

 

целых чисел на многих современных машинах отводится 4 байта.

 

 

 

Начав работать с операциями отношения и логическими операциями, вы по­

 

знакомитесь с булевыми значениями подробнее.

 

 

 

Типы с плавающей точкой

 

 

 

Целые и символьные значения — это так называемые интегральные типы дан­

 

ных. Значения этих типов отличаются друг от друга только на величину, кратную 1.

 

Они не могут содержать дробную часть, т. е. хранящаяся в них последовательность

 

битов не может интерпретироваться компилятором C + + как дробь. Если нужна

 

дробная часть, то потребуются другие типы.

 

 

 

В C + + нет чисел с фиксированной точкой, что позволило бы программисту

 

управлять числом цифр после десятичной точки (запятой). Вместо этого он может

 

использовать значения с плавающей точкой, состоящие из мантиссы (с

целой

 

и дробной частью) и экспоненты. Экспонента выражается в литеральных числах

 

как степень десяти, но в памяти компьютера, конечно, представляется как степень

 

двойки.

 

 

 

74

I

Часть I« Введение в прогромтшроваиыв но C++

 

 

В C + +

есть три типа с плавающей точкой: float, double и long double. Такой

 

 

тип как short double отсутствует. Для этого есть тип float. Размер данных типов

 

 

зависит от машины. Обычно для типа float выделятся 4 байта, для double —

 

 

8 байт, а для long double — 10 байт (или 8, как для double).

 

 

Число цифр мантиссы, которая может представляться числом с плавающей

 

 

точкой, зависит от машины. Обычно значения типа float содержат 7 цифр, значе­

 

 

ния double — 15 цифр, а long double — 19.

 

 

Диапазон значений зависит от числа битов, выделенных для экспоненциальной

 

 

части. Для значений float — экспонента имеет диапазон от -38 до 38. Для значе­

 

 

ний double диапазоном будет от-611 до 611. Для long double это от-4932 до 4932

 

 

(более чем достаточно для всех данных Вселенной).

 

 

Приведем примеры определений переменных с плавающей точкой:

 

 

f l o at pi;

double г;

long double d;

 

 

Цель иерархии типов с плавающей точкой та же, что и для целых типов —

 

 

программист получает возможность выбора между размером памяти, точностью

 

 

представления и величиной значений, которые можно использовать для перемен­

 

 

ной. Для приложений, где важна не точность вычислений, а главным образом

 

 

объем памяти (как в большинстве встроенных систем реального времени), можно

 

 

использовать тип float. В приложениях, где нужна высокая точность вычислений

 

 

(например, в навигационных задачах), предпочтительным будет тип long double,

 

 

хотя он занимает больше места, а вычисления с ним выполняются медленнее.

 

 

Для прочих задач подойдет тип double.

 

 

Наверное, справедливости ради нужно отметить, что тип float слишком ко­

 

 

роток для

большинства приложений, а тип long double — слишком "дорог"

 

 

(в смысле памяти и времени). Все функции в математических библиотеках C+ +

 

 

ожидают аргументов double и возвращают значения double (как функция pow()

 

 

в программе C + + главы 2).

 

 

Типы данных с плавающей точкой имеют фиксированную точность. Например,

 

 

очень большие и очень малые числа типа double будут содержать на данной

 

 

платформе одинаковое число цифр. Как уже упоминалось, в C + + отсутствуют

 

 

типы данных с фиксированной точкой (с заданным числом цифр после десятичной

 

 

точки).

 

 

 

 

 

Типы с плавающей точкой представляются в позиционной системе счисления

 

 

(с десятичной точкой) или в экспоненциальном виде (буква Е или е обозначает

 

 

экспоненту).

 

 

 

 

double г=5.3;

long

double d=530.0e-2

 

 

Эти два числа представляют одно и то же значение. 10 в степени - 2 есть единица,

 

 

деленная на

10 в степени +2, т. е. l/lOO.

 

 

В данном

случае экспоненциальное представление не дает преимуществ по

сравнению с нормальным (позиционным). Оно удобно для компактного представ­ ления очень малых или очень больших значений.

Большинство литералов с плавающей точкой содержат все три компонента мантиссы: целую часть, десятичную точку (одну, конечно) и дробную часть. Не все они необходимы для представления чисел в каждом конкретном случае.

Между тем важно использовать это представление так, чтобы значение с пла­ вающей точкой отличалось от целого. Например, иногда можно опустить в числе целую или дробную часть:

double small = .09, large = 5. ;

Можно даже опустить дробную и целую часть, если от целого число будет отли­ чаться экспонентой:

double big = 500е2;

Глава 3 • Работа с донными и выражениями C++

75

В экспоненциальном представлении экспонента должна быть целым числом. Хотя с математической точки зрения это не обязательно, C + + воспринимает только целые экспоненты. Кроме того, экспонента может быть значением signed, даже если оно положительное:

double big = 500е+2; / / big = 500е+2.2; не воспринимается

Подобно целым литеральным значениям, C + + позволяет различать литералы разных типов с помощью предшествующих значению спецификаторов. Специфи­ катор f или F обозначает значение с плавающей точкой (float), а спецификатор 1 или L — значение long double. Можно предположить, что спецификатор d или D обозначает значение с плавающей точкой с двойной точностью. Логично, но неверно. Литералы с плавающей точкой имеют тип double по умолчанию:

float pi = 3.14f; double г = 5.3; long double d = 5.3 L;

Работа с выражениями C+ +

Операции C+ + Категория Область действия Основные

Вспомогательные

Выбор компонента Мультипликативные Аддитивные Сдвиг Отношение Сравнение

Выражения состоят из операндов и операций. Операнды — это все, что имеет типизированное значение, т. е. переменные, возвращающие значения функции, выражения, состоящие в свою очередь из операндов и операций. Операции — это символы, смысл которых в C + + зарезервирован. Применение операции

к операндам дает значение — его можно

использовать в другом выражении.

Пробелы используются для удобства чтения, но они не обязательны:

(а + Ь) * (а + 2*Ь) * (а+3*Ь);

/ / пробел не обязателен

На порядок вычислений влияют два атрибута операции: старшинство (операция с более высоким уровнем старшинства выполняется первой) и ассоциирование (операции с одинаковым старшинством выполняются слева направо или справа налево).

В C + + имеется 56 операций с 18 уровнями приоритетов. Список операций приведен в таблице 3.1. Невозможно запомнить эту таблицу, просто прочитав ее. Она приводится только для информации, а не для запоминания. Программисты запоминают операции постепенно, в процессе работы. Если есть сомнения в стар­ шинстве операций, используйте скобки. Кроме того, даже если выучить старшин­ ство операций наизусть, программист, занимаюш>1Йся сопровождением программы, может не знать данной таблицы и спутать порядок вычисления.

Таблица 3.1

Операция

О [1 "> typeid dynamic__cast static_cast reinterpret_cast const_cast

+ + -- ~ ! + - ° & Sizeof new delete (type)

- >° ."

v%

+ -

<<>>

<< = > > =

Ассоциирование Слева направо Слева направо

Справа налево Справа налево Слева направо Слева направо Слева направо Слева направо Слева направо Слева направо

76

Часть I ^ Введение в прогротмтрошоитв на С-и-

 

 

 

 

Таблица 3.1 (продолжение)

Операции

С+-^

 

 

Категория

 

Операция

Ассоциирование

Поразрядное И

&

Слева направо

Исключающее ИЛИ

 

Слева направо

Включающее ИЛИ

 

Слева направо

Логическое И

&&

Слева направо

Логическое ИЛИ

II

Слева направо

Присваивание

= °= /= %= += -= «= >>= &= ! =

Справа налево

Условные

 

 

Справа налево

Перемещение

throw

 

Запятая

 

 

Слева направо

Высокоприоритетные операции

в начале таблицы следуют операции самого высокого приоритета, выполняе­ мые в первую очередь. Например, высокоприоритетными операциями являются круглые скобки. Независимо от того, какие другие операции задействованы в выражении, сначала вычисляются подвыражения в скобках.

Теперь об операциях ' +' и ' -'. Это унарные операции, т. е. у них только один операнд. Например, +2.0, -2.0. Чем же отличается унарная операция '+' и ' -' от сложения и вычитания — низкоприоритетных операций? Унарная операция применяется только к одному операнду, а сложение и вычитание — к двум. Это отличие позволяет записывать смешанные выражения, не требуюндие для вычис­ ления дополнительных скобок, например 2,5 0.25. Поскольку унарный минус представляет операцию, ее можно отделить от операнда любым числом пробелов. Вычислению это не помешает. Конечно, чтобы работа была оценена программи­ стом, который будет программу сопровождать, лучше записать данное выражение так: 2,5- (-0.25).

Операция sizeof уже упоминалась выше. Это единственная операция C+ + , которая может применяться как к переменной, так и к идентификатору типа:

int X = sizeof(int); int у = sizeof(x); / / одни и те же значения

Здесь переменной х присваивается число байтов, выделяемых для любого целого, а в у записывается число байтов, выделенных для конкретной переменной х (в данном случае она целого типа). Как любую унарную операцию, sizeof можно использовать, не заключая в скобки операнд, если это имя переменной:

int X = sizeof(int); int у = sizeof x; / / те же результаты

Если же операндом является имя типа, то скобки потребуются — в этом случае они обязательны:

int X = sizeof int ; int у = sizeof x;. / / непорядок

К сожалению, это все, что можно сказать пока о высокоприоритетных операциях. Позднее мы к ним вернемся.

Это же относится к операциям выбора компонента (следующий уровень прио­ ритета) и к операции throw в конце таблицы. Оператор приведения типа (cast) мы обсудим чуть ниже.

Глава 3 * Работа с донными и выражениями C++

77

Арифметические операции

Пятый и шестой уровень приоритета в таблице 3.1 имеют мультипликативные и аддитивные операции, но здесь вряд ли стоит их обсуждать. У умножения и деле­ ния более высокий приоритет, чем у сложения и вычитания. Если нужно изменить порядок вычисления, используются круглые скобки:

X = (а + Ь) * (а + 2*Ь) * (а+3*Ь);

/ / будьте внимательны

В случае переполнения в C++ не возникает никаких исключительных ситуа­ ций (exception): программист сам должен позаботиться о том, чтобы переполнения не произошло, какие бы данные не вводились и не обрабатывались программой.

Арифметические операции выполняются с целыми значениями или значениями с плавающей точкой. При этом операция '/' будет вести себя по-разному. Если операндами являются значения с плаваюш^ей точкой, то в результате также полу­ чается значение с плаваюш^ей точкой, вычисленное с соответствуюш,ей точностью. Для целых результатом будет целое, усекаемое в сторону нуля. Например, 7/3 дает 2.333333 для операндов с плавающей точкой и 2 для целочисленных операндов.

Операция получения остатка целочисленного деления (%) дает остаток. Она допускается только для целочисленных операндов и символов, но не для значений с плавающей точкой. Например, 7/3 дает 2 с остатком 1. Следовательно, 7 % 2 будет 1. Если 9 разделить на 3, будет 3 без остатка. Тогда 9 % 3 равно нулю:

int х1=7, х2=8, хЗ=9; int

г1. г2, гЗ;

 

г1 = х1 % 3; г2 = х2 % 3;

гЗ = хЗ % 3;

/ / г1 равно 1, г2 равно 2, гЗ равно О

То же самое происходит, если первый операнд меньше второго. В этом случае получается только остаток — сам результат считается равным нулю. Например, операция остатка целочисленного деления 5 на 7 даст О и 5 в остатке. Следователь­ но, 5 % 7 равно 5. Аналогично, деление 6 на 7 дает О и 6 в остатке. Тогда 6 % 7 равно 6. При делении 7 на 7 получается 1 без остатка. Отсюда 1%1 равно нулю:

int

a1=5, а2=6, аЗ=7; int

г1, г2, гЗ;

 

П

= a1 % 7; г2 = а2 % 7;

гЗ = аЗ % 7;

/ / г1 равно 5, г2 равно 6,

 

 

/ /

гЗ равно нулю

Для положительных операндов все это довольно просто. Для отрицательных результаты зависят от машины. К счастью, нет никакой необходимости использо­ вать операцию получения остатка целочисленного деления с отрицательными опе­ рандами. Обычно она используется, когда нужно определить, есть ли свободное место в объекте-контейнере или он уже заполнен данными (и нужно перейти к началу контейнера). Длина контейнера и позиция следующего элемента в нем не бывают отрицательными.

Ассоциирование слева направо означает, что когда в выражение включаются несколько операций одного приоритета, они вычисляются слева направо. Для умножения и сложения это не важно, но важно для вычитания и деления. Это все равно, что вычислять а + b + с как (а + Ь) + с или как а + (Ь + с). Нужно убедиться, что выражение а - b - с вычисляется как (а - Ь) - с, а не как а - (Ь - с). Аналогично, а/Ь/с означает (а/Ь)/с но не а/(Ь/с).

Операции инкремента '++' и декремента ' --' являются своего рода торговой маркой программирования на С и C++. Это операции сложения и вычитания с одним операндом, равным 1. Следовательно, нужно определить только один операнд. Они реализуют обработку в стиле ассемблера: единица прибавляется или вычитается как непрерываемая операция высокого приоритета. По существу, данные операции создают для своих операндов побочный эффект:

int X = 6, у = 10; X++; у-;

/ / теперь х равно 7, у равно 9

гл

Часть I • Введение в программирование на C'^'t-

 

В своей базовой форме операции эти очень просты. Операция инкремента

 

увеличивает свой операнд на 1, а операция декремента — уменьшает его на 1.

 

Этот пример в точности эквивалентен слелуюш,ему:

 

i nt X = 6, у = 10; X = X + 1; у = у - 1;

/ / теперь X равно 7, у равно 9

Программисты, знакомые с другими языками, часто удивляются, зачем нужны операции инкремента и декремента, если они эквивалентны обычному сложению и вычитанию. Раньше можно было бы ответить, что для операций декремента и инкремента компилятор генерирует более эффективный объектный код, чем для обычного сложения и вычитания. Теперь при современной технологии разработки компиляторов и оптимизации кода разницы в производительности нет.

Сегодня применение данных операций — в основном дело вкуса и стиля. Ко­ нечно, никто не вынуждает использовать операции инкремента и декремента, можно обойтись сложением и вычитанием. Программа будет столь же элегантна, корректна и быстра, как программа с операциями инкремента и декремента. Разве что ваш начальник или коллеги усомнятся, так ли уж хорошо вы знаете C+-f, как вам кажется.

На самом деле операции инкремента и декремента весьма многосторонние. Они не ограничиваются целочисленными значениями. Можно использовать и зна­ чения с плаваюш^ей точкой:

float small = 0.09; small++; / / теперь small равно 1.09

Кроме того, суш,ествуют две формы операций инкремента и декремента: пре­ фиксная и постфиксная. В предыдущих примерах применялась постфиксная форма, в которой операция следует за модифицируемым операндом. В префиксной форме операция предшествует операнду. Вот пример использования префиксных операций:

int X = 6, у = 10; ++х; / / теперь X равно 7, у равно 9

В чем же разница? Похоже, что результат тот же. В действительности, в данном контексте префиксные операции эквивалентны следуюидему коду:

int X = 6, у = 10; X + 1; у = у - 1; / / теперь X равно 7, у равно 9

Это в точности то, что и в примере с префиксной формой. Разница между префикс­ ными и постфиксными операциями — в использовании выражений. Как видно, результатом данных операций будут значения (как и в случае любой операции в С+Н это очень важный принцип). В примерах х++ и ++х возвращает значе­ ние 7, а у-- и --у дает значение 9. Эти значения можно использовать в любом другом выражении, где допустимо целое. И вот здесь префиксная и постфиксная операции начинают вести себя по-разному.

Когда используется префиксная операция, значение операнда сначала увели­ чивается (или уменьшается), а затем полученный результат используется в выра­ жении:

int х=6, у=10, а, Ь; 5 + ++х; b = 5 + -у; / / а равно 12, b равно 14

Обратите внимание на пробелы, предшествующие префиксным операциям. Они полезны, чтобы избежать путаницы. Компилятор (и программист, сопро­ вождающий программу) может не понять запись 5+++х, хотя запись 5+--у будет понятна.

При применении постфиксной операции в выражении сначала используется значение операнда и только затем переменная увеличивается или уменьшается:

int х=6, у=10, а, Ь; а = 5 + х++; b = 5 + у-

/ / а равно 11, b равно 15

Глава 3 • Работа с данными и выражениями С++

79

Как видно, с помощью операций инкремента и декремента нетрудно написать исходный код, в котором легко запутаться. Возможно, это и так, однако в своих простейших формах эти операции весьма популярны — они часто используются, если на каждой итерации цикла нужно увеличивать или уменьшать значение счетчика или индекса. Посмотрите на листинг 3.1 (или циклы в приводившихся ранее примерах). Ни один опытный программист не стал бы писать их без опера­ ций инкремента (см. листинг 3.5: результат, конечно, будет тот же, что и в лис­ тинге 3.1).

Л и с т и нг 3.5. Демонстрация операции инкремента

#inclucle <limits> #inclucle <iostream> using namespace std;

int main(voicj)

{

int num = INT_MAX - 2; int cnt = 0;

cout « "Целочисленное переполнение в C++:" endl; cout « "Увеличение от " « num « endl;

. while (cnt < 5)

// операция инкремента

{ num++; cnt ++;

cout «

cnt « " " « num«endl; }

 

 

cout «

"Спасибо, чтопобеспокоились о границах диапазона целого" « endl;

 

return 0;

 

 

 

Вам скоро понравятся операции инкремента и декремента. Если пока с ними

 

не очень комфортно, нет проблем — применяйте обычные арифметические опе­

 

рации, как в других языках. Между тем, если вы будете вовсе избегать операций

 

инкремента и декремента, то ваш начальник начнет подозревать, что вы не столь

 

уж хорошо владеете C++, как предполагалось. Так что время от времени следует

 

делать так, как делают все.

 

 

 

Если вы читаете эту главу впервые, то следуюш^ие два раздела

("Операции

 

сдвига" и "Поразрядные логические операции") можно пропустить — так будет

 

легче воспринимать материал.

 

 

Операции сдвига

 

 

 

Далее в таблице операций C + +

следуют операции сдвига << и >>. Стоп! Но

 

это же не операции сдвига, а операции извлечения и вставки, которые использова­

 

лись для вывода в объекте cout и ввода в объекте cin. Да, все верно. Здесь мы

 

имеем дело с техникой разработки под названием "переопределение

операций"

(overloading). Операции сдвига применялись в языке С с незапамятных времен. Разработчики C + + решили применить суш,ествуюш.ие операции в новом кон­ тексте, так что вместо изучения новых операций (или новых ключевых слов) придется изучать новый смысл уже сундествующих.

Возможно, это и не легче, да и техника переопределения операций не такая уж новая. Например, сколько смыслов имеет обычная операция + в языке С? Она может использоваться как:

1)унарный плюс;

2)для сложения целых чисел;

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