Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

Программирование в Visual С++ 2005 7 страница



Модификация переменной

Часто возникает необходимость модифицировать существующее значение пере-
менной, например, увеличивая или удваивая его. Увеличит значение переменной по
имени count можно с помощью следующего оператора:

Здесь просто к текущему значению count прибавляется 5 и полученный результат
помещается обратно в count, поэтому, если сначала count было равно 10, то после
этого оно будет равно 15.

В С++ предусмотрен альтернативный сокращенный синтаксис для записи того же
выражения:

Это значит "взять значение count, добавить к нему 5 и сохранить полученный ре-
зультат в count". Можно также использовать и другие операторы в подобной нота-


ции. Например:


дает эффект, выражающийся в умножении текущего значения count на 5 и сохране-
нии результата в count. В общем случае, вы можете писать операторы в форме:

 

где ор — любая из следующих операций:

 

 


Вы уже встречались с первыми пятью из этих операций, а с остальными (опера-
циями сдвига и логическими) вы ознакомитесь далее в этой главе. Ihs представляет
любое корректное выражение, которое можно поместить в левой части присваива-
ния, и обычно (но не обязательно) это имя переменной, rhs — это любое корректное
выражение, которое можно поместить в правой части оператора.

Общая форма этого оператора эквивалентна следующей:

Скобки вокруг rhs указывают на то, что это выражение вычисляется первым, а за-
тем результат становится правым операндом операции ор.
Это значит, что вы можете писать такие операторы, как:

и это даст тот же результат, что и:

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


100 Глава 2

 

Операции инкремента и декремента

Теперь я представлю несколько необычные арифметические операции, называе-
мые операциями инкремента и декремента, и уверен, что вы сочтете их весьма по-
лезными по мере серьезного применения С++. Это унарные операции, которые слу-
жат для увеличения или уменьшения значения, хранимого в переменной целого типа.
Например, если предположить, что переменная count имеет тип int, то следующие
три оператора дадут один и тот же эффект:

 

 


Каждый из них увеличивает переменную count на 1. Последняя форма, использу-
ющая операцию инкремента — вне всяких сомнений, самая краткая.

Операция инкремента не только изменяет значение переменной, к которой он
применен, но также сама возвращает значение. Таким образом, используя операцию
инкремента для увеличения значения переменной, ее также можно сделать частью
более сложного выражения. Если увеличивать переменную, используя операцию ++,
как в ++count, в составе другого выражения, то действие операции будет заключать-
ся в том, что сначала увеличится значение переменной, а затем это новое значение бу-
дет использовано в выражении. Например, предположим, что count имеет значение
5, и вы определили переменную total типа int. Предположим, что вы записываете
следующий оператор:



В результате это увеличит count до б, и к этому значению будет прибавлено 6, по-
этому total получит значение 12.

До сих пор мы помещали операцию инкремента ++ перед переменной, к которой
она применялся. Это называется префиксной формой операции инкремента. Однако
эта операция также имеет постфиксную форму, когда операция ставится после пере-
менной, к которой она применяется. В результате получается несколько другой эффект.
Переменная, к которой применена операция, увеличивается только после того, как ее
значение будет использовано в объемлющем контексте. Например, сбросьте значение
count снова до 5 и перепишите предыдущий оператор следующим образом:

 


В результате total получит значение 11, потому что начальное значение count
используется для вычисления всего выражения до того, как count увеличится на 1
операцией инкремента. Этот оператор эквивалентен следующим двум:

 

 


Но когда у вас есть такое выражение, как а+++b, или даже а+++b, то не совсем
очевидно, что это должно означать, и что будет делать компилятор. На самом деле
это одно и то же, но во втором случае вы на самом деле могли иметь в виду а + ++Ь,
что не одно и то же.

Точно те же правила, о которых я рассказал относительно операции инкремента,
применимы к операции декремента —. Например, если count имеет начальное зна-
чение 5, то оператор:

в результате присвоит total значение 10, в то время как:



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

Описание полученных результатов

Если вы скомпилируете и запустите эту программу, то получите следующий вы-
вод:

Это не требует пояснений. Переменная num4 принимает значение последнего из
трех присваиваний, а присваиваемым значением является значение, которое полу-
чает его левая часть. Скобки в присваивании num4 важны. Вы можете попробовать
выполнить этот пример без них, чтобы увидеть, что получится. Без скобок первое
выражение, отделенное запятыми, станет таким:

То есть, num4 будет присвоено значение 10.

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

 

 


Эффект от этого оператора присваивания будет выражен в увеличении значений
переменных numl, num2 и num3 на 1, а также в присваивании num4 значения послед-
него выражения, которое в данном случае будет равно 101. Этот пример нацелен на
то, чтобы проиллюстрировать эффект от операции запятой, но не является приме-
ром того, как следует писать хороший код.


Последовательность вычислений

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

Порядок выполнения операций

 

 


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


Данные, переменные и вычисления 103

 

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

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

 

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

 

 

Типы переменных и приведения

Вычисления в С++ могут выполняться только между однотипными значениями.
Когда вы пишете выражение, включающее переменные или константы разных типов,
то для каждой выполняемой операции компилятор должен выполнить преобразова-
ние типа одного операнда к типу другого. Процесс преобразования типов называется
приведением. Например, если вы хотите прибавить значение типа double к значе-
нию целочисленного типа, то целочисленное значение сначала преобразовывается в
double, после чего выполняется сложение. Конечно, сама переменная, содержащая
исходное значение, не изменяется. Компилятор сохраняет преобразованное значе-
ние во временной памяти, которая теряется по завершении вычислений.

Выбор операнда, подлежащего преобразованию в любой операции, регулируется
строгими правилами. Любое выражение, которое должно быть вычислено, разбива-
ется на серии операций с двумя операндами. Например, выражение 2*3-4+5
состоит из следующих серий: 2 * 3 в результате дает б, затем 6-4, которая в ре-
зультате дает 2 и, наконец, 2 + 5, результат которого — 7. Таким образом, правила
приведения операндов необходимы только для принятия решений относительно пар
операндов. Поэтому для любой пары операндов разного типа проверяются описан-
ные ниже правила, в том порядке, как они записаны. Если правило применимо к кон-
кретной паре, оно используется.

Правила приведения операндов

1. Если любой из операндов имеет тип long double, то второй преобразуется в
тип long double.

2. Если любой из операндов имеет тип double, то второй преобразуется в тип
double.

3. Если любой из операндов имеет тип float, то второй преобразуется в тип
float.


104 Глава 2

4. Любой операнд типа char, signed char, unsigned char, short или unsigned
short преобразуется в тип int.

5. Перечислимый тип преобразуется сначала в int, unsigned int, long или
unsigned long, в зависимости от того, какого из них достаточно, чтобы вме-
стить диапазон перечислителей.

6. Если один из операндов типа unsigned long, то другой преобразуется в
unsigned long.

7. Если один из операндов типа long, а другой — типа unsigned int, то оба опе-
ранда преобразуются к типу unsigned long.

8. Если любой из операндов типа long, то второй преобразуется в тип long.

Это выглядит неимоверно сложным, но базовый принцип прост: всегда преобразу-
ется значение того типа, которые имеет более ограниченный диапазон, к типу второ-
го значения. Это увеличивает вероятность получения правильного результата. Чтобы
увидеть, как работают эти правила, вы можете попробовать их на примере гипоте-
тического выражения. Предположим, что у вас есть следующая последовательность
объявлений переменных:

 


Также предположим, что у вас есть следующее, достаточно произвольное арифме-
тическое выражение:

Теперь вы можете попробовать предположить, какие приведения выполнит ком-
пилятор при выполнении этого оператора.

Первая операция, которую нужно вычислить — это (value — count). Правило 1
здесь не применимо, зато подходит правило 2, поэтому значение count преобразует-
ся в double, и в результате получается значение 15.0 типа double.

Следующим должно выполниться (count — num), и здесь первое подходящее пра-
вило из последовательности — это правило 4, поэтому num преобразуется из char в
int, и получается результат 12 типа int.

Следующий шаг вычисления — перемножение двух первых результатов — double,
равное 15.0, и int, равное 12. Здесь применимо правило 2, и 12 преобразуется в
12.0 с типом double, в результате получается значение double, равное 180.0.

Полученный результат теперь должен быть разделен на many, поэтому опять при-
меняется правило 2, и значение many преобразуется в double перед генерацией
double результата 90.0.

Следующим вычисляется выражение num/many, и здесь применяется правило 3,
чтобы получить значение float, равное 2. Of, после преобразования типа num из
char в float.

В конце к double-значению 90.0 прибавляется float-значение 2. Of, для чего
применяется правило 2, которое требует преобразования 2. Of в double-значение
2.0, и окончательный результат 92.0 присваивается value.

Хотя чтение этой последовательности несколько напоминает "песню аукционис-
та", основную идею вы поняли.


Данные, переменные и вычисления 105

 

Приведения в операторах присваивания

Как вы видели ранее в этой главе на примере , вы можете вызвать не-

явное приведение, записав справа от оператора присваивания выражение, тип кото-
рого отличается от типа переменной, находящейся слева от него. Это может изме-
нить значение, и информация будет утеряна. Например, если вы присвоите float
или double переменной типа int или long, то дробная часть float или double будет
потеряна, а сохранена только целая часть. (Вы можете потерять даже больше инфор-
мации, если ваша переменная с плавающей точкой содержит значение, выходящее за
диапазон допустимых значений целочисленных типов).

Например, после выполнения следующего фрагмента кода:

 


значение number будет равно 2. Обратите внимание на f в конце константы 2. 5f.
Это указывает компилятору, что это константа с плавающей точкой одинарной точ-
ности. Без f по умолчанию она бы имела тип double. Любая константа, содержащая
десятичную точку, является значением с плавающей точкой. Если вам не нужно, что-
бы она имела двойную точность, добавляйте к ней f. Заглавная F тоже подходит.

Явные приведения

Когда смешанные выражения включают базовые типы, то компилятор при необхо-
димости выполняет нужные приведения, но вы также можете принудительно указать
приведение одного типа к другому, используя явные приведения. Чтобы привести
значение выражения к определенному типу, необходимо написать так:

Ключевое слово staticcast отражает тот факт, что приведение выполняется
статически — то есть, когда программа компилируется. Никаких дальнейших прове-
рок безопасности приведения во время выполнения программы не осуществляется.
Позднее, когда вы будете иметь дело с классами, вы познакомитесь с dynamic_cast —
когда преобразование проверяется динамически, то есть, во время выполнения
программы. Есть еще два других вида приведений: const_cast — для исключения
константности выражения и reinterpretcast, которое означает безусловное при-
ведение, но о них я пока не буду говорить.

Эффект операции static_cast заключается в преобразовании значения-ре-
зультата вычисления выраженья к типу, который указан между угловыми скобками.
Выражение может любым — от отдельной переменной, до сложнейшего составного,
содержащего множество вложенных скобок.

Вот специфический пример применения :

 

 


Инициализирующим выражением для whole_number является сумма целых частей
valuel и value2, в которых остается 10.5 и 15.5 соответственно. Значения 10 и 15,
порожденные приведением, сохраняются лишь временно, для использования в вы-
числении суммы, а затем теряются. Хотя оба приведения приводят при вычислении
к потере информации, компилятор предполагает, что вы знаете, что делаете, приме-
няя явное приведение.


106 Глава 2

 

Кроме того, как я писал в Ех2_05. срр относительно присваивания с участием раз-
ных типов, вы всегда можете прояснить код, сделав приведение явным:

 

 


Вы можете применять явные приведения с числовыми значениями любых типов,
но должны осознавать возможность потери информации. Если вы приводите значе-
ние типа float или double к типу long, например, то при преобразовании теряете
дробную часть, поэтому если значение было меньше 1.0, результатом будет 0. Если
же вы приводите значение типа double к типу float, то теряете точность, потому
что float имеет лишь 7-значную точность, в то время как double — 15-значную. Даже
приведение между целочисленными значениями может привести к потенциальной
потере информации, в зависимости от конкретных значений, участвующих в нем.
Например, значение целого типа long может выйти за пределы допустимых для типа
short, поэтому приведение значения long к типу short может привести к потере
информации.

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

Приведения в старом стиле

До того, как в С++ появилась операция приведения staticcatso () (а также
другие операции приведения: const_cast<> (), dynamic_cast<> () и reinterpret_
casto (), о которых мы поговорим позднее), явное приведение результата выраже-
ния к другому типу записывалось следующим образом:

 


Здесь результат выражения приводится к типу, указанному в скобках. Например,
оператор вычисления strips_per_roll из предыдущего примера мог быть записан
так:

 

 


По сути, существуют четыре разных вида приведений, и синтаксис приведений
старого стиля покрывал все. По этой причине код, использующий приведения в ста-
ром стиле, более подвержен ошибкам — компилятору не всегда были ясны ваши наме-
рения, и вы не получали ожидаемый результат. Хотя вы встретите в унаследованном
коде интенсивное использование приведений старого стиля (это все еще часть язы-
ка, и их много в коде MFC — по причинам исторического характера), я настоятельно
рекомендую в новом коде применять только новые приведения.

Битовые операции

Битовые операции трактуют свои операнды как последовательности индивидуаль-
ных битов, а не числовые значения. Они работают только с целочисленными пере-
менными или целыми константами в качестве операндов, поэтому с ними могут ис-
пользоваться только типы данных short, int, long, signed char и char, а также их
беззнаковые варианты. Битовые операции удобны для программирования аппаратных


Данные, переменные и вычисления 107


 

устройств, где состояние устройства часто представляется серией индивидуальных
флагов (то есть, каждый бит может описывать состояние определенного аспекта дан-
ного устройства), или же их можно применять в ситуациях, когда желательно упако-
вать набор флагов "включено/выключено" в единую переменную. Вы увидите их в
действии, когда будете подробно изучать средства ввода-вывода, где отдельные биты
служат для управления различными опциями управления потоками данных.
Существует шесть битовых операций.

В следующем разделе мы посмотрим, как работает каждая из них.

Битовое И

Битовое И (&) — это бинарная операция, которая комбинирует соответствующие
биты своих операндов определенным образом. Если в обоих операндах соответству-
ющие биты равны 1, то и в результате будет 1, если же оба или один из битов операн-
дов равен 0, то и в результате соответствующий бит будет равен 0.

Эффект применения конкретной битовой операции часто показывают с помощью
таблицы истинности. В ней показаны все возможные комбинации битов операндов
и соответствующий результирующий бит, полученный в результате применения опе-
ратора. Таблица истинности для & представлена в табл. 2.5.

Таблица 2.5. Таблица истинности для операции битового И


Рис. 2.8. Работа операции битового И


Для того чтобы понять, что произойдет, нужно взглянуть на битовые шаблоны.
Буквы 'А' и 'Z' соответствуют шестнадцатеричным значениями 0x41 и 0х5А, соответ-
ственно (см. в приложении Б таблицу кодов ASCII). Работа операции битового И с
этими двумя значениями показана на рис. 2.8.


108 Глава 2


 

Вы можете проверить это, пользуясь таблицей истинности операции &, которая
была приведена выше. После присваивания result будет иметь значение 0x40, что
соответствует символу

Поскольку & дает 0, если любой из соответствующих битов операндов равен 0, вы
можете применять его в тех случаях, когда в переменной определенные биты должны
были установлены в 0, независимо от их исходного значения. Это достигается соз-
данием так называемой "маски" и комбинацией ее с исходной переменной с помо-
щью &. Вы создаете маску, специфицируя значение 1 в тех битах, в которых старое
значение нужно сохранить, и 0 — в тех битах, значение которых нужно сбросить в 0.
Результатом применения И к маске и другому целому будет бит 0 в тех позициях, где
стоит 0 в маске, и неизменное значение бита исходной переменной там, где в маске
стоит 1. Предположим, что имеется переменная letter типа char, где в целях демон-
страции вы хотите сбросить в 0 старшие 4 бита. Это очень просто достигается уста-
новкой значения маски OxOF и комбинацией ее со значением letter посредством &:

или, более коротко:

Если изначально значение letter равно 0x41, то в результате любого из этих двух
операторов оно станет равным 0x01. Эта операция проиллюстрирована на рис. 2.9.

 

А

 

&

 

1'

 

I l

 

 

Рис. 2.9. Применение маски к значению переменной

Нулевые биты маски сбрасывают в ноль соответствующие биты letter, а биты
маски, равные 1, оставляют соответствующие биты letter неизмененными.

Аналогично вы можете использовать маску 0xF0, чтобы сохранить 4 старших бита
и сбросить в ноль 4 младших.

Таким образом, следующий оператор:

даст в результате изменение значения letter с 0x41 на 0x40.

Битовое ИЛИ

Битовое ИЛИ (|), иногда называемое включающим ИЛИ, комбинирует соответ-
ствующие биты операндов так, что результат равен 1, если хотя бы один из битов
операндов в данной позиции равен 1, и 0, если оба бита равны 0. Таблица истинности
для битового ИЛИ показана в табл. 2.6.


 
 

Вы можете проверить это на примере установки индивидуальных флагов, упакован-
ных в переменную типа int. Предположим, что имеется переменная по имени style
типа short, которая содержит 16 индивидуальных 1-битных флагов. Предположим
далее, что вам нужно установить отдельные флаги в переменной style. Одним из
способов сделать это является определение значений, которые можно скомбиниро-
вать операцией ИЛИ для установки определенных битов в 1. Чтобы установить край-
ний правый бит, вы можете определить переменную:

Чтобы установить второй справа бит, вы должны определить вторую переменную

hredraw:

Тогда для установки двух правых крайних битов переменной style в 1 можно вос-
пользоваться следующим оператором:

Эффект от этого оператора проиллюстрирован на рис. 2.10.

Поскольку операция ИЛИ дает в результате 1, когда любой из двух битов операн-
дов равен 1, то объединение с ее помощью двух переменных дает в результате уста-
новку обоих бит.

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

Этот оператор установит два крайних правых бита переменной style в 1, остав-
ляя нетронутыми все остальные.


НО Глава 2


 

Битовое исключающее ИЛИ

Битовое исключающее ИЛИ (А), так называется потому, что работает подобно
обычному ИЛИ, но когда оба бита операндов установлены в 1, возвращает 0. Таким
образом, таблица истинности для него выглядит так, как показано в табл. 2.7.


Дата добавления: 2015-11-04; просмотров: 21 | Нарушение авторских прав







mybiblioteka.su - 2015-2024 год. (0.035 сек.)







<== предыдущая лекция | следующая лекция ==>