Битовые операции сдвига
Эти операции сдвигают значение целочисленных переменных на указанное количество битов вправо или влево. Операция >> предназначена для сдвига вправо, а операция << – для сдвига влево. Биты, которые при этом "выпадают" за край значения, теряются. На рис. 29 показан эффект от сдвига значения 2-байтной переменной влево и вправо.
Вы объявляете и инициализируете переменную по имени number с помощью следующего оператора:
unsigned int number = 16387U;
Рис. 29. Сдвиг значения 2-байтной переменной влево и вправо
Как было показано ранее в этой главе, беззнаковые целочисленные литералы пишутся с добавлением к числу буквы U или u. Можете сдвинуть содержимое этой переменной влево следующим образом:
number <<= 2; // сдвинуть влево на две битовых позиции
Левый операнд операции сдвига – это значение, которое нужно сдвинуть, а количество бит, на которое необходимо сдвинуть, задается правым операндом. Иллюстрация на рис. 29 демонстрирует эффект от операции сдвига. Как видите, сдвиг значения 16 387 на две позиции влево дает в результате 12. Такое значительное изменение значения объясняется потерей крайних бит, который уходят "за край" значения.
Значение можете сдвинуть и вправо. Если вернем переменной number начальное значение 16 387. Затем вы можете написать так:
number >>= 2; //сдвинуть вправо на две битовые позиции
Это сдвигает значение 16 387 на две позиции вправо, что дает в результате значение 4096. Сдвиг вправо на две позиции равнозначен делению на 4 (без остатка). Это также показано на рис. 29.
До тех пор, пока крайние биты не теряются, сдвиг на n бит влево эквивалентен умножению значения на 2n раз.
Но необходимо соблюдать осторожность: если значащие биты теряются, то результат будет совсем не таким, какой ожидается. Однако это не отличается от операции умножения. Если умножать 2-байтное число на 4, то получится тот же результат, поэтому сдвиг влево и умножение все-таки эквивалентны. Проблема точности возникает, когда результат умножения выходит за пределы допустимых значений 2-байтного целого.
Можете подумать, что между операциями сдвига и операциями, используемыми для ввода и вывода, возникает конфликт. До тех пор, пока это рассматривает компилятор, значение операции в каждом конкретном случае всегда ясно из контекста.
Если же нет, то компилятор выдаст сообщение, но все же программист должен быть осторожен. Например, если есть желание вывести результат сдвига переменной number на 2 бита влево, то надо написать следующий оператор:
cout <<(number << 2);
Здесь скобки очень важны. Без них операция сдвига интерпретировалась бы компилятором как операция потока, и на вывод было бы отправлено значение number, а за ним 2.
Операция сдвига вправо в основном подобна операции сдвига влево. Например, предположим, что переменная number имеет значение 24, и выполняется следующий оператор:
number >>= 2;
В результате этого number получит значение 6, разделив исходное значение на 4. Однако сдвиг вправо работает специальным образом со знаковыми целочисленными типами, которые содержат отрицательные значения (то есть, у которых бит знака – крайний левый – равен 1). В этом случае бит знака распространяется вправо. Например, можно объявить и инициализировать переменную number типа char с десятичным значением -104:
char number = -104/ // двоичное представление — 1001 1000
Теперь можно сдвинуть его вправо на 2 бита с помощью следующего оператора:
number >>= 2; // результат 1110 0110
Десятичное значение результата будет равно -26, поскольку бит знака повторяется. При операциях с беззнаковыми целочисленными типами, конечно, бит знака не повторяется, поэтому в первой позиции появляется ноль.
Следует вернуться к вопросу о последовательности выполнения операций. В этой главе приоритеты были указаны только для математических вычислений. В C присутствует множество других операций. Чтобы понять, что произойдет с ними, нужно рассмотреть механизм, используемый C для определения этой последовательности. Это то, что называется приоритетом операций.
Порядок выполнения операций упорядочивает операции в порядке приоритетов. В любом выражении операции с более высоким приоритетом всегда выполняются первыми, за ними выполняются операции со следующим по возрастанию приоритетом и так далее, вплоть до тех, чей приоритет самый низкий. Порядок выполнения операций C++ представлен в табл. 6.
Таблица 6
Порядок выполнения операций в C++
Операции | Наименование | Ассоциативность |
:: | Контекста | Левая |
( ) [ ] -> . | Первичные | Левая |
- ~ ! * & ++ -- sizeof приведение типа | Унарные | Правая |
* / % | Мультипликативные | Левая |
+ - | Аддитивные | Левая |
<< >> | Сдвиг | Левая |
< <= > >= == != | Отношение | Левая |
& | Побитовое и | Левая |
^ | Побитовое исключающее или | Левая |
| | Побитовое включающее или | Левая |
&& | Логическое и | Левая |
|| | Логическое или | Левая |
?: (условная операция) | Условная | Правая |
= *= /= %= += -= &= ^= |= <<= >>= | Простое и составное присваивание | Правая |
, | Перечисление | Левая |
Здесь приведено множество операций, не все они рассмотрены в пособии. Все операции C в одной таблице, позволяют в любой момент обратиться к ней, когда необходимо выяснить, как соотносится приоритет одной операции с приоритетом другой.
Операции с наивысшим приоритетом находятся в верхней части таблицы. Все операции, которые указаны в одной ячейке таблицы, имеют одинаковый приоритет. Если в выражении нет скобок, операции с равным приоритетом выполняются в последовательности, определяемой их ассоциативностью. То есть, если ассоциативность "левая", то самая левая операция в выражении выполняется первой, затем последовательно выполняются операции всего выражения — слева направо. Это значит, что выражение вроде а + b + с + d выполняется, как записано, то есть как (((a + b) +c) +d), потому что бинарная операция + имеет левую ассоциативность.
Следует обратить внимание, что когда операция имеет и унарную (работающую с одним операндом), и бинарную (с двумя операндами) формы, то унарная форма всегда имеет более высокий приоритет, а потому выполняется первой.
Всегда можно изменить приоритеты операций в выражении с помощью скобок. Поскольку в C++ очень много операций, иногда бывает затруднительно понять, каким должен порядок вычисления сложного выражения. Поэтому хорошей идеей будет применять скобки, чтобы обрести уверенность. Дополнительная выгода от них проявляется в том, что это часто облегчает чтение кода.
Альтернативная запись оператора =
В языке С существует разновидности оператора присваивания, используемые в случаях когда необходимо изменить значение то же переменной куда заносится результат изменений.
Следующие операторы эквивалентны по результату, работы. Они различаются лишь формой записи.
a=a+5; a+=5;
a=a-5; a-=5;
a=a*5; a*=5;
a=a/5; a/=5;
a=a%5; a%=5;
Нельзя записать, используя эти операции: a=5-a; и a=5/a;
В общем случае, можно писать операторы в форме:
переменная знак= выражение;
где знак – любая из следующих операций:
+ - * / %
<< >> & ^ |
Эта форма эквивалентна выражению
переменная = переменная знак (выражение) ;
Наличие () указывает на то, что вначале вычисляется выражение, а потом уже переменная изменяется.