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

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

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

80Часть I • Введение в програттшрошоиив на С+Ф

3)для сложения чисел с плавающей точкой (и эти операции реализованы не так, как операции целочисленного сложения);

4)как часть префиксных и постфиксных операций (мы еще не закончили их обсуждение).

Операции ' « ' и ' » ' сдвигают биты целочисленного значения влево или впра­ во. Второй операнд задает число битов, на которые сдвигается первый операнд. На самом деле, все не так плохо, как звучит. Рассмотрим сначала операцию сдвига вправо:

i nt х=5, у=1, result; result = х » у;

/ / результат равен 2

Данная операция сдвигает последовательность битов левого операнда (здесь х, где содержится 5) на число позиций, которые задает правый операнд (в данном случае у со значением 1). Двоичное представление 5 — это 101. Если сдвинуть данную последовательность битов на позицию вправо, получится 10, что соответ­ ствует целому значению 2 (или степень двойки, заданная вторым операндом).

Операция << сдвигает биты в обратном направлении. Здесь из 101 получает­ ся 1010, что соответствует десятичному 10:

int х=5, у=1, result; result = х « у;

/ / результат равен 10

Когда биты сдвигаются влево, то первые биты операнда теряются, а правые биты заполняются нулями (как в последнем примере). Аналогично при сдвиге вправо теряются правые биты, но то, что происходит с левыми битами, зависит от машины.

Самым левым битом целого со знаком (signed) будет бит знака. Если он равен нулю, то число положительное, а если 1 — отрицательное. Если число положи­ тельное, то проблем нет: нулевой бит знака сдвигается вправо, а нули слева зани­ мают его место. Если же значение отрицательное, то вправо сдвигается единица, и возникает проблема переносимости. На некоторых машинах в бите знака оказы­ вается единица (и распространяется дальше). Это называется арифметическим сдвигом. На других в бит знака сдвигается О (распространяемый вправо). Такой

сдвиг называется логическим.

Поразрядные логические операции

Эти операции включают в себя поразрядную операцию "И" (&), "исключаюидее ИЛИ" ("), "включаюш,ее ИЛИ" (|) и самую высокоприоритетную операцию поразрядного дополнения — отрицания (~). Первые три операции двоичные, а последняя — унарная (требуется только один операнд).

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

Поразрядная операция "И" устанавливает бит результата в 1, если оба соответствуюш,их бита операндов равны 1, и в О, если хотя бы один из двух участвуюШИХ. в операции битов равен 0. В следуюш.их примерах рассматриваются только четыре бита операнда и предполагается, что другие четыре бита содержат нули. Чтобы проиллюстрировать операцию "И", предположим, что первый операнд равен 12 (1100 в двоичном представлении), а второй — 10 (1010 в двоичном виде). Сравним каждый бит первого операнда с соответствуюш,им битом второго. Как видно, только старший бит в обоих операндах равен 1. Остальные содержат разные значения (О или 1). Следовательно, значением результата будет 1000 (десятичное 8): 1100 & 1010 дает 1000.

Поразрядная операция "включаюш^ее ИЛИ" устанавливает результат в 1, если один или оба бита операнда равны 1. Если оба бита нулевые, то результатом

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

81

будет 0. Для операндов 12 (двоичное 1100) и 10 (двоичное 1010) все биты резуль­ тата, за исключением самого правого, устанавливаются в 1, что дает двоичное значение 1110 (десятичное 14), т. е. 1100 | 1010 дает 1110.

Поразрядная операция "исключаюш^ее ИЛИ" устанавливает результат в 1, если только один из битов операнда равен 1. Если оба бита содержат одинаковое значение (О или 1), то результатом будет 0. В нашем примере первый и последний биты операндов совпадают, а второй и третий отличаются, что дает двоичный код 0110 (десятичное 6): 1100 МОЮ дает 0110.

Операция поразрядного дополнения устанавливает биты результата в обрат­ ные значения, т. е. для получения результата она меняет биты операнда на проти­ воположные. Если бит операнда равен 1, то бит результата становится нулевым и наоборот. Например, дополнение 12 (двоичное 1100) дает результат 0011 (деся­ тичное 3), или ~1100 дает 0011.

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

Предположим, третий бит справа в слове состояния (пусть оно называется flags) означает, что устройство включено. Когда устройство включается, програм­ ма должна устанавливать этот бит в 1.

Чтобы установить бит в 1, нужна переменная (назовем ее onMask), у которой третий бит имеет значение 1. Если применить к третьему биту переменных flags и onMask операцию "включаюш,ее ИЛИ", то третий бит результата будет устанав­ ливаться независимо от того, каково состояние третьего бита в переменной flags. Это быстрее, чем проверять данный бит, а потом выполнять операцию "включаюш,ее ИЛИ". Проблема в том, что логические операции нельзя применять к отдельным битам — они применяются одновременно ко всем битам операнда. Это означает, что все биты переменной onMask (за исключением третьего бита) должны иметь такое значение, чтобы другие биты переменной flags не менялись. Для "включаюш,его ИЛИ" биты должны содержать 0.

Именно так создаются маски для последовательностей битов: отдельные биты устанавливаются в значения, как того требует состояние, а остальные — в значе­ ния, не изменяюидие суш,ествуюш,его состояния. В данном примере в переменной onMask третий бит устанавливается в 1, а остальные биты — в 0. Для четырех­ битового представления получаем 0100 или десятичное 4:

i nt flags, onMask = 4;

 

flags = flags | onMask;

/ / устанавливает третий бит в 1

Если устройство выключено, это должно отражаться сбросом третьего бита в О с сохранением значений остальных битов. Тогда потребуется другая маска (назо­ вем ее offMask), устанавливаюидая третий бит в О, и логическая операция "И". Чтобы остальные биты не изменились, нужно установить эти биты маски в 1. Следовательно, переменная offMask будет содержать 1011 или десятичное 11.

Впрочем, здесь есть одна тонкость. Данная битовая последовательность рав­ на 11, только когда ее размер 4 бит. Для 8 бит последовательность будет иметь вид 11111011, а десятичным значением будет 244. Для 16 или 32 бит потребуется

82

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

еще одно значение. Вот типичный пример проблемы переносимости. Решение здесь достаточно простое. Все эти битовые последовательности представляют отри­ цание битового набора 0100 для слов разного размера. Следовательно, переноси­ мым методом инициализации переменной offMask будет использование битового шаблона, обратного 0100:

i nt OffMask = "onMask;

 

flags = flags & offMask;

/ / при этом третий бит сбрасывается в О

Чтобы проверить, установлен ли третий бит, можно применить операцию "И" к маске onMask и переменной flags. Эта операция устанавливает все биты резуль­ тата в О, за исключением третьего бита (поскольку все биты в маске onMask, кроме третьего, содержат 0). Если третий бит переменной flags равен О (устрой­ ство выключено), то результатом будет О (false). Если третий бит переменной flags равен О (устройство выключено), то результат отличен от О (содержит true). Еш,е один метод доступа к значениям битов состоит в сдвиге последовательности битов флага на две позиции вправо и применении операции "И" к флагу и маске, содержаидей все нули, кроме правого бита. Если результатом будет 1, то бит установлен, а если результат нулевой, то бит также нулевой (об операции провер­ ки на равенство — см. ниже):

i f (((flags » 2)&1)==1) cout « "Третий бит установлен\п";

/ / проверка

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

Операции отношения и равенства

Операции отношения используются во всех приложениях. C + + поддерживает четыре такие операции: меньше (<), меньше или равно (<==), больше (>) и боль­ ше или равно (>=). Знаки двухсимвольных операций не должны разделяться пробелами (как и в любых других подобных операциях C+ + ). Эти операции наиболее часто применяются при сравнениях в операторах условия и в циклах. Например, в листинге 3.1 в условии цикла проверяется cnt < 5, Если cnt меньше 5 (при первой итерации), то выполняется тело цикла. Если же значение cnt увели­ чивается и становится равным 5, то 5 < 5 дает false и цикл завершается. Что может быть прош^е? Это еш,е одно наследие языка С, которое не столь просто, как кажется.

C + + не имеет встроенного булева типа со значениями, отличными от целых. Обсуждавшийся выше булев тип реализован как короткое целое: true — это 1,

а false — 0. То есть результат сравнения в С+Н

не просто true или false

(как в других языках программирования), а числа

1 и 0. Размер такого целого

равен одному байту, но при необходимости его можно преобразовать в целое большего размера.

Следовательно, х > у принимает значение 1, если х больше у. В противном случае оно равно 0. Выражение х < у равное 1, если х меньше у, иначе это 0. Аналогично, х >= у дает 1, если х не меньше у и О, если х < у. Значением х <= у будет 1, если х не меньше у, и О, если х меньше у.

Сказанное не меняет форму простого сравнения и особенности его работы, однако использование логических значений как целых открывает возможности для злоупотреблений. Например, каково значение х > у > z? В большинстве языков программирования это просто синтаксис и все (хотя есть исключения).

В C + + такое выражение вполне законно. Так как операции отношения ассо­ циируются слева направо, сначала мы сравниваем х и у. Если х больше у, то

Глава 3 • Работа с данными и выражения1У11.1 Сч-^-

83

результатом будет 1, которая затем сравнивается с z. Если 1 больше z, то значе­ нием выражения станет 1. В противном случае это 0. Если же х не больше z, то результатом будет О, который сравнивается с z. Если О больше z, то значением выражения будет 1, а иначе — 0. Сомнительно, чтобы кто-то стал писать подоб­ ное выражение, просчитывая все варианты.

Далее в таблице следуют операции равенства. C++ поддерживает две опера­ ции равенства: "равно" ( = = ) и "не равно" (! = ). Символы этих операций также неразделимы. Если сравнение истинно, операция возвращает 1, иначе опадает 0.

Таким образом, значением выражения х == у будет 1, если х равно у. В против­ ном случае оно равно 0. Значением х ! = у будет 1, если х не равно у; иначе 0.

Предположим, что нужно присвоить z значение 10, если х равно у, и 9, если X и у содержат разные значения. Во всех языках программирования (включая С и C+ + ) можно записать некую простую и недвусмысленную конструкцию:

i f (х == у)

/ / устанавливает значение z в 10 или в 9

z = 10; else

z = 9;

Но в C++ возможен такой вариант: Z = 9 + (х == у);

Понять, конечно, труднее, но такая конструкция, несомненно, элегантнее и умнее.

Ситуация усугубляется тем, что эквивалентом true может быть любое (отлич­ ное от нуля) значение, а не обязательно 1. Соответственно оно может использо­ ваться вместо true. Кроме того, в C++ все возвращает true, включая операцию присваивания. Например, данная операция присваивания устанавливает перемен­ ную X в значение у и возвращает это значение для дальнейшего использования в выражениях (если необходимо):

X = у;

Это означает, что, если вы укажите операцию равенства *==' как *==', пеняйте на себя. Такая операция будет не ошибкой, а вполне допустимым выражением C++.

Предположим, например, что в приведенном выше примере значением х будет 1, а значением у — также 1. Тогда z должно присваиваться 10. А теперь посмотрим, что получится в случае ошибки в первом выражении:

i f (X = у) z = 10;

else

z = 9;

Данный оператор устанавливает значение х в у (что не меняет значения х, так как X и у в данном примере содержат 1), возвращает это значение в операторе if, интерпретирует его как true (поскольку оно ненулевое) и устанавливает z в 10. Все замечательно.

Проведя поверхностное тестирование, можно подумать, что такой код работает вполне корректно, но если не остановиться на этом и протестировать его на дру­ гом наборе значений, например х равным 1 и у равным 2, то обнаружится, что значение z все еще равно 10, а не 9 (опять же, присваивание х = у возвращает здесь значение 2, а это есть true).

Теперь предположим, что ошибка сделана во втором выражении:

Z = 9 + (х = у);

84 I Часть I ^ Введе^*^-:-: ::- . ^ ,:-^:,шытрошаитв но C+*

Такое присваивание будет возвращать новое значение х, которому присваива­ ется значение у, оно складывается с 9 и используется для установки значения z. В результате z будет равно не 9 или 10, а 11.

Те, кто видят это впервые, могут подумать, что проблема невелика, поскольку разница между операциями ' = ' и ' = = ' заметна и бросается в глаза — ошибку нетрудно обнаружить. Конечно. Никто и не говорит, что разницы нет. Но в отрас­ ли, где ПО разрабатывается коллективно, "охота" за подобными ошибками может отнимать немало времени, энергии, нервов и выливаться в финансовые расходы. Ведь подобные ошибки нередки.

Таким образом, следует всегда проверять правильность написания операции как в присваиваниях, так и в равенствах. Хочется еш.е раз подчеркнуть это, чтобы привлечь внимание читателя к данному вопросу.

О с т о р о ж н о ! Ошибочная запись операции равенства '==' как операции присваивания '=' не будет синтаксической ошибкой. Она дает допустимое

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

#Всегда проверяйте в условиях такие ошибки.

Логические операции

Следуюш,ий набор операций — это логические операции: логическое И (&&), логическое ИЛИ (включаюш.ее) (||) и логическое отрицание (!). Подобно пораз­ рядным операциям, "И" и "ИЛИ" представляют собой двоичные операции (они требуют двух операндов). Исключаюш^его ИЛИ среди логических операций нет.

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

В отличие от поразрядных операций в логических операциях операнды интер­ претируются как одно целое: если значение равно О, оно рассматривается как false, а ненулевое значение считается истинным (true).

Логическая операция И (&&) возвраш,ает 1 (размера bool), только когда оба операнда ненулевые. В противном случае возвраш,ается 0:

i f (х < у && у < z) cout « "у между х и z\n"

Логическая операция ИЛИ (||) возвраш^ает 1, когда оба операнда отличны от нуля. Когда оба операнда равны О, возвраш^ается 0:

i f (х > у I I у > 0) cout « "По крайней мере одно положительное\п";

Логическая операция отрицания (!) возвраш,ает О, если операнд ненулевой. Если операнд нулевой, возвраш^ается 1. Применения данной операции всегда можно избежать за счет реорганизации других условий, но иногда прош.е исполь­ зовать отрицание.'Рассмотрим, например, программу, которая дает скидку пенсио­ нерам (age >= 65) с хорошим кредитным рейтингом (rating ==2). С помондью отрицания этих условий нетрудно выявить тех, кто подобной скидки не заслужива­ ет. Программист может посчитать, что прош,е всего записать:

i f (! (age >= 65 && rating == 2)) cout « "Нет скидок\п";

Вкачестве логических операндов можно использовать целочисленные объекты

иобъекты с плаваюндей точкой. Любое ненулевое значение дает true, а нуле­ вое — false. Заметим, что нет необходимости заключать операнды логических

операций в скобки. В то же время, логическое выражение в операторе if (и в операторе while) должно быть в скобках. Вот почему в последнем примере два набора скобок.

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

[ 85 р

Как и в других языках, логические операции вычисляются слева направо, но, в отличие от прочих языков, операция '&&' имеет более высокое старшинство, чем *||\ Это позволяет писать весьма сложные логические выражения без скобок. Пусть, например, нужно предоставить скидку в 10% пожилым гражданам с кредит­ ным рейтингом 2 и покупающим товар впервые, если сумма заказа не менее $200. Это можно выразить так:

i f (age>=65 && rating==2 | | first_time =^= true && total_orcler>200.0) discount = 0 .1;

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

i f ((age>=65 && rating==2) | | (first_time == true && total_orcier>200.0) discount = 0.1;

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

i f (age>=65 && rating==1 || rating==2) discount = 0. 1;

/ / некорректное логическое выражение

В данном операторе скидка предоставляется старшим гражданам с рейтингом 1

и2, но не просто пожилым (как уже говорилось, операция "И" имеет более высо­ кий приоритет, чем "ИЛИ"). Применение скобок устранит проблему:

i f

(age>=65 && (rating==1 || rating==2))

discount = 0.1;

 

/ /

корректное логическое выражение

Внимание

Логическая операция "И" (&&) имеет более высокий приоритет,

чем логическое "ИЛИ" (| |). Использование скобок поможет программисту, занимающемуся сопровождением программы, понять смысл сложных выражений.

Логические операции в C++ вычисляются по короткой схеме. Это означает, что сначала вычисляется первый операнд, и, если результат определяется первым вычислением, второй операнд вычисляться не будет. В следующем примере, если X не меньше у, то не имеет смысла проверять, что у меньше х: нельзя будет заключить, что у лежит между х и z, в таком случае второе условие не вычисляется:

i f < у && у < z) cout « "у находится между х и z\n"

Операции присваивания

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

Когда нечто имеет адрес в памяти, говорят об I-значении (lvalue — выра­ жении, которое может находиться в левой части оператора присваивания, семан­ тически представляющем собой адрес размещения переменной, массива, элемента

86

Часть I« Введение в програ^^^шрование на С4>+

структуры и т. п. в памяти). Это означает просто, что выражение может использо­ ваться слева от присваивания. Оно имеет адрес, и значение данного адреса моди­ фицируется при использовании в качестве цели присваивания. До сих пор нам встречался только один вид 1-значения — имя переменной. В C-f-f существуют и другие 1-значения, мы познакомимся с ними позднее. Заметим, что ничто не мешает использовать 1-значение справа от присваивания.

Другой вид значения в С+Ч- называют г-значением (rvalue — значением

вправой части оператора присваивания). Оно имеет значение, но не адрес в памя­ ти, который программа может использовать для изменения данного значения. Примеры г-значений — это литеральные значения, значения, возвращаемые фун­ кциями, результаты двоичных операций. R-значения можно использовать только

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

5 = foo();

/ /

литерал не может использоваться как lvalue

 

 

foo()

= 5;

/ /

возвращаемое значение не должно использоваться

как

lvalue

score

* 2 = 5;

/ /

результат операции не должен использоваться

как

lvalue

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

int X, у, z; у = Z = 0;

Присваивание ассоциируется справа налево: выражение х = у = z = 0; означает X = (у = (2 = 0));, но не (((х = у) = z) = 0);, так как х = у — не 1-значение и при­ сваивать ему ничего нельзя. Данное средство легко использовать неправильно.

X = (а = Ь*с)*4; X = а = Ь*с*4; X = 4*а = Ь*с;

/ /

допустимо в C/C++

/ /

это имеет другой смысл

/ /

синтаксическая ошибка: для 4*а нет 1-значения

Кроме традиционной операции присваивания, в C++ имеется ряд вариантов — арифметические операции присваивания. Их назначение — сократить арифмети­ ческие выражения. Например, вместо записи х = х + у; можно записать х += у;. Результат будет тем же. Такие операции присваивания доступны для всех двоич­ ных операций ( + = , - =, * = , /=, %=, &=, |= "=, << = , >> = )• Они почти столь же популярны, как операции инкремента/декремента и используются для тех же целей. Вот пример сегмента кода, в котором вычисляется сумма квадратов первых 100 целых чисел:

double

sum = 0.0; int

i = 0;

while

(i++ < 100)

 

sum += i * i ;

/ / арифметическое присваивание

cout «

"Сумма квадратов первых 100 чисел равна " « sum « endl;

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

double sum = 0.0; int i = 0;

while

( i

< 100)

{ i

= i

+ 1;

sum = sum + i * i ; }

cout

«

"Сумма квадратов первых 100 чисел равна " « sum « endl;

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

Глава 3 « Робота с данньши и выражениями С-ь-^

| 87 ^

Условная операция

Следующая в иерархии операций С+Н условная операция. Это единственная тернарная операция в С4- + . У нее три операнда. Сама операция состоит из двух символов — 7' и ':', но в отличие от других двухсимвольных операций эти симво­ лы разделяются вторым операндом. Вот обобщенная форма условной операции:

операнд1 ? операнд2 : операндЗ

/ /

если операнд1 равен true,

 

/ /

вычисляется операнд2

Здесь операнд1 — тестовое выражение. Это может быть любой скалярный тип (простой, без программно-адресуемых компонентов), включая float. Данный операнд всегда вычисляется первым. Если результат вычисления первого операн­ да дает true (не ноль), то вычисляется операнд2, а операндЗ пропускается. Если результатом вычисления первого операнда будет false (0), то операнд2 пропус­ кается, и вычисляется операндЗ.

Не заблуждайтесь насчет смысла true и false в данном описании. Выражение операнд1, конечно, может быть булевым, но вовсе не обязательно. C++ позволя­ ет использовать любой тип, позволяющий получать О и ненулевые значения.

В следующем примере переменной а присваивается самое меньшее из значе­ ний у или Z. Первым операндом здесь является выражение у < z;. Если выраже­ ние равно true, вычисляется операнд2 (здесь — переменная у), и его значение

возвращается как значение выражения. Если выражение у < z;

отлично от true,

то возвращается значение операндаЗ (здесь — переменная z):

 

а = у < Z ? у : z;

/ / а устанавливается в минимальное

из значений у, z

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

i f ( y < z )

//аустанавливается в минимальное иззначений у, z

а = у;

else

 

а = z;

 

Вот еще один пример, демонстрирующий преимущества условной операции. Возвращаемое ею значение используется в другом выражении (оператор вывода). Если сумма баллов у претендента больше 80, то оператор выводит: "Вы приняты"; . иначе выводится "Вы не приняты":

cout « "Вы" « (score > 80 ? "" : " не")

«" приняты.\п";

Традиционный подход дает более объемный, но простой в восприятии код:

i f (score > 80)

cout « "Выприняты.\п"; else

cout « "Вы не приняты.\п";

Операция запятой

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

выражение1, выражение2, выражениеЗ, . . . . выражением

88

I

Часть 1«Введен'' - 'Г^ограмтшрошаищГ'-

 

 

 

Здесь вычисляется каждое выражение, начиная с самого левого, так как

 

 

запятая имеет низший приоритет. Возвраш.ается значение последнего выражения.

 

 

Это часто используется как побочный эффект самых левых выражений. Вот

 

 

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

 

 

double

sum = 0.0; int i = 0;

 

 

 

while

( i < 100)

 

 

 

i

= i+1, sum = sum + i * i ;

/ / ограничители блока не нужны

 

 

cout

«

"Сумма квадратов первых 100 чисел равна

" « sum « endl;

Не очень хорошая идея, но интерпретация запятой как операции вполне до­ пустима. Еще один пример намеренного злоупотребления, хотя и относительно безвредный. Более опасно применение запятой как операции, когда это делается ненамеренно. В результате получается некорректный код, в котором не отмеча­ ется синтаксическая ошибка, поскольку никаких синтаксических правил С+4- не нарушается. Рассмотрим пример с циклом, в котором вычисляется сумма квадратов:

double

sum = 0.0; int i = 0;

 

 

while

(i++ < 100)

 

 

sum += i * i ,

/ /

арифметическое присваивание

cout «

"Сумма квадратов первых

100 чисел равна

" « sum « endl;

Единственная разница между первой версией и этой состоит в том, что в конце тела цикла вместо точки с запятой поставлена запятая. К сожалению, эта ошибка не делает код синтаксически некорректным. Он будет компилироваться и работать. Конечно, неправильно. Результат выведется 100 раз, а не один. Данную ошибку легко найти, но если оператор после цикла был бы не такой простой, то ошибка могла бы стать трудно обнаруживаемой. Нужно знать о том, что операция запятой

внеподходяш[,ем месте часто маскируется под вполне законную операцию C-f-h.

Ос т о р о ж н о ! Ошибочное использование запятой может не дать сообш,ения компилятора об ошибке, так как запятая — допустимая операция C++.

Смешанные выражения: скрытая опасность

С + Н язык со строгим контролем типов. Это означает, что если контекст требует значения одного типа, то применение значения другого типа даст синтак­ сическую ошибку. Такой важный принцип позволяет программистам вылавливать ошибки, затрачивая значительно меньше усилий. Вместо тестирования на этапе выполнения достаточно посмотреть на то, что сообш,ает компилятор.

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

TimeOfDay х, у;

у.setTime(22,40);

// законная операция

x.setTime(20,50);

X +=4;

// синтаксическая

ошибка: некорректный

тип операнда

if (х < у);

// синтаксическая

ошибка: некорректный

тип операнда

X = у - 1;

// синтаксическая

ошибка: некорректный

тип операнда

Глава 3 * Работа с данными и выражения1^1и C-f-ь

89

В том что касается числовых типов, С+Н

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

Если бы значения х и у в предыдущем примере имели тип int, первые три строки были бы синтаксически корректны. Они будут корректны и для любого другого числового типа: unsigned int, short, unsigned short, long, unsigned long, signed char, unsigned char, bool, float, double, long double. Более того, они корректны даже тогда, когда х и у принадлежат к разным числовым типам. На этапе выполне­ ния операция будет работать корректно, несмотря на то, что эти переменные имеют разный размер и их битовые наборы интерпретируются по-разному.

Этим С+Ч- существенно отличается от языков с сильным контролем типов. Например, в C+-I- вполне допустим (и часто используется) следующий код:

double sum;

sum = 1 ; / / нет синтаксической ошибки

С точки зрения современных языков со строгим контролем типов это явный пример несостоятельности программиста — ошибка будет помечена на этапе компиляции. В одном месте программы утверждается, что переменная sum имеет тип double, а в другом — интерпретируется как целая переменная. Если выводит­ ся синтаксическая ошибка, программисту представляется возможность подумать и решить, как устранить несоответствие. Либо нужно определить переменную как целую, либо заменить последнюю строку на

sum = 1.0;

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

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

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

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

В смешанном выражении возможно три вида преобразования типа:

Приведение размера

Неявное преобразование

Явное преобразование (приведение типа)

Приведение размера (расширение) применяется к "коротким" целым типам для преобразования их к "естественному" размеру целого. Такой экзекуции под­ вергаются значения типа bool, short int, signed char. После извлечения из памя­ ти для использования в выражениях они всегда приводятся к int. При этом значение всегда сохраняется, ведь размера int достаточно для представления лю­ бого значения этих более "компактных" типов. В следующем примере складыва­ ются два значения short, а результат и его размер выводятся на экран:

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