Разбираем побитовые операторы...
Побитовые операторы - это те странно-выглядящие операторы, которые, как вам кажется сложно понять... теперь это в прошлом! Эта простая статья поможет вам разобраться, что они из себя представляют и как их стоит использовать, также я покажу на практических примерах, когда и в каких случаях их стоит применять.
Вступление
Побитовые операторы (+, *, && и так далее), работают с целыми числами (ints)
и единицами (units)
на бинарном уровне. Это значит, что данные операторы работают с бинарными числами или битами целых чисел. Всё это звучит страшновато, но правда в том что побитовые операторы довольно легко использовать и они действительно полезны!
Тем не менее стоит упомянуть, вам нужно понимать как работают бинарные и шестнадцатеричные числа. Если вы с этим не знакомы, пожалуйста взгляните на данную статью - она вам поможет! Ниже вы найдёте приложение, которые позволит вам проверить работу побитовых операторов.
Не бойтесь если на данный момент что-то не понятно, скоро всё встанет на свои места.
Что из себя представляют побитовые операторы
Давайте посмотрим на побитовые операторы, которые предоставляет AS3. Другие языки довольно похожи (к примеру JavaScript и Java обладают практически схожими операторами):
- & (побитовое AND)
- | (побитовое OR)
- ~ (побитовое NOT)
- ^ (побитовый XOR)
- << (побитовый сдвиг влево)
- >> (побитовый сдвиг вправо)
- >>> (побитовый правый сдвиг с заполнением нулями)
- &= (побитовое AND присваивание)
- |= (побитовое OR присваивание)
- ^= (побитовое XOR присваивание)
- <<= (побитовый левый сдвиг и присваивание)
- >>= (побитовый правый сдвиг и присваивание)
- >>>= (побитовый правый сдвиг с заполнением нулями и присваивание)
Из всего этого вам следует запомнить несколько вещей: Сперва, некоторые побитовые операторы, похожи на те операторы, которые вам доводилось использовать ранее (& и &&, | и ||). Всё это потому что принцип их работы относительно похож.
Во вторых, большинство побитовых операторов обладают составными формами с присваиванием. Это тоже самое, как мы используем + и +=, - и -=, и так далее.
Оператор &
Сначала мы рассмотрим AND оператор, &. Вам следует знать: обычно, ints
и units
занимают 4 байта или 32 бита. Это означает что каждое int
и unit
хранится как 32 битное число. Для целей нашего туториала, мы будем считать, что ints
и units
занимают лишь один байт и имеют только 8 битные числа.
Оператор & сравнивает каждое бинарное число (бит) двух целых чисел и возвращает новое целое число, со значением 1 если оба числа являются 1, в противном случае получим 0. Диаграмма лучше тысячи слов, так что вот вам одна из них, призванная прояснить ситуацию. Она представляет собой операцию 37 & 23
, результат будет равен 5
.

Обратите внимание, как бинарное представление чисел 37 и 23 сравниваются, результат равен 1, когда оба 37 и 23 обладают 1, в другом случае мы получаем 0.
Обычно, считается что каждое бинарное число представляет собой true
, либо false
. Тем самым, 1 равн true
, 0 равен false
. Это делает принцип работу оператора & более понятным.
Когда мы сравниваем два булевых значения, мы делаем что-то вроде boolean1 && boolean2
. Это выражение будет равно true, только в том случае если оба boolean1
и boolean2
равны true. Таким же образом, integer1 & integer2
равны, в том случае если оператор & выдаёт 1 для каждого из двух битов наших целых чисел, опять же результат будет равен 1.
Вот таблица, которая показывает что я имел ввиду:

Небольшая хитрость использования & оператора, чтобы проверить чётное число или нечётное. Для целых чисел мы просто может проверить самый правый бит (также называемый младшим разрядом), чтобы определить чётное число или нечётное. Это потому что конвертируя к основанию 10, самый правый бит представляет 20 или 1. В том случае когда самый правый бит равен 1, нам известно что число нечётное так как мы добавляем 1 к степени двойки, которая всегда будет чётная. Когда самый правый бит равен 0, мы знаем что число будет чётное, поскольку нам известно что мы просто добавляем к набору чётных чисел.
Вот вам пример:
var randInt:int = int(Math.random()*1000);if(randInt & 1){ trace("Odd number.");}else{trace("Even number.");}
На моём компьютере, данный метод был на 66% быстрее, чем randInt % 2
, для проверки чётности и нечётности чисел. Существенное улучшение производительности!
Оператор |
Далее речь пойдёт об операторе OR, |. Так как вы наверно уже догадались, | оператор похож на оператор ||, также как & оператор похож && оператор. Оператор | сравнивает каждый бит двух целых числ и возвращает 1, если хотя бы один из них равен 1. Опять же, это похоже на работу || оператора для булевых.

Давайте рассмотрим такой же пример, как раньше, за исключением того, что на этот раз мы воспользуемся оператором |, вместо &. На этот раз, это будет 37 | 23
, что будет равно 55:
Использование & и | операторов
Мы можем воспользоваться операторами & и |, чтобы передать несколько опций функции в одном, единственном int
.
Давайте взглянем на возможную ситуацию. Мы создаём класс всплывающего окна. Снизу будут варианты Yes, No, Okay, или кнопка Cancel, или любая другая комбинация вариантов - как нам стоит поступить? Вот сложное решение:
public class PopupWindow extends Sprite{// Variables, Constructor, etc...public static void showPopup(yesButton:Boolean, noButton:Boolean, okayButton:Boolean, cancelButton:Boolean){if(yesButton){// add YES button}if(noButton){// add NO Button}// and so on for the rest of the buttons}}
Ужасно-ли это? Нет. Но это довольно плохо, вы программист, взгляните на порядок аргументов, каждый раз при вызове функции. Также раздражает - к примеру, если вы хотите показать только кнопку Cancel, вам придётся задать остальным Booleans
false
.
Давайте используем то что нам известно о & и | для создания лучшего решения:
public class PopupWindow extends Sprite{public static const YES:int = 1;public static const NO:int = 2;public static const OKAY:int = 4;public static const CANCEL:int = 8;public static void showPopup(buttons:int){if(buttons & YES){// add YES button}if(buttons & NO){// add NO button}}}
Как программист вызовет функцию, чтобы кнопка Yes, кнопка No, и кнопка Cancel показывались? Вот так:
PopupWindow.show(PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL);
Что же происходит? Важно отметить что все наши константы из второго примера являются степенью двойки. Если мы посмотрим на бинарную форму, мы заметим, что все они обладают одним числом равным 1, а остальные равны 0. По факту, каждое из них обладает отличным числом равным 1. Это значит, что не важно как мы будем комбинировать их с |, каждая комбинация будет давать нам уникальное число. Посмотрите на это с другой стороны, наш результат выражения с | будет равен бинарному числу с 1, каждый раз когда опция обладает 1.
В текущем примере у нас есть PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL
, что, в свою очередь является эквивалентом 1 | 2 | 8
, что в бинарном формате будет выглядеть как 00000001 | 00000010 | 00001000
и в результате даст нам 00001011
.
Теперь, в нашей функции showPopup()
, мы используем & для проверки, какие опции были переданы внутрь функции. Для примера, когда мы проверяем buttons & YES
, все биты в YES равны 0, за исключением крайнего справа. По сути мы проверяем самый правый бит у кнопок, равен он 1 или нет. Если это так, buttons & YES
будут равны 0 и всё в выражении if будет выполнено. И наоборот, если самый правый бит кнопок равен 0, buttons & YES
будет равно 0 и код в выражении if не будет выполнен.
Оператор ~
Побитовый оператор NOT немного отличается, чем два оператора, которые нам довелось изучить прежде. Вместо того, чтобы работать с целыми числами с каждой стороны, он работает только с целым числом, которые идёт сразу после самого оператора. Тоже самое что ! оператор, и, что неудивительно, делает похожую работу. По факту, также как и ! меняет булево значение с true
на false
, или наоборот, оператор ~ меняет значение каждого бита на противоположное: с 0 на 1 и с 1 на 0:

Быстрый пример. Допустим у нас есть целое число 37, или 00100101. ~37 сделает из него 11011010. Какое десятичное значение этого? Что же...
Дополнительный код (представление числа two’s complement), unit
против int
, и даже больше!
Теперь, будет весело! Мы посмотрим на бинарные числа на компьютере. Давайте начнём с unit
. Как говорилось ранее unit
представляет обычно из себя 4 байта или 32 бита, что означает что в нём 32 бинарных числа. Это легко понять: чтобы получить десятичное значение мы просто конвертируем число используя основание 10. Мы всегда получим положительное число.
Но что насчёт int
? Это также использует 32 бита, но как же хранятся отрицательные числа? Если вы подумали, что первое число хранит информацию о знаки, вы на правильном пути. Давайте взглянем на две системы хранения бинарных чисел. Хотя мы не будем рассматривать все детали, две системы используются для того, чтобы сделать арифметические операции над бинарными числами легче.
Чтобы найти представление бинарного числа, нам просто нужно перевернуть все биты (то есть тоже самое, что делает оператор ~) и прибавить к получившемуся результату единицу. Давайте попробуем это сделать, один раз:

Мы получим результат -37. Но зачем весь этот сложный процесс, почему просто не перевернуть только первый бит и назвать это -37?
Давайте представим простое выражение 37 + -37
. Мы все знаем, что результат должен получится равный 0, когда мы прибавляем 37 к его дополнительному коду (представлению числа), вот что мы получаем:

Обратите внимание наши целые числа состоят только из восьми бинарных чисел, мы отбрасываем 1 в нашем результате, и получаем 0, как и должно было быть.
Подытожим, чтобы найти отрицательное число, мы просто берём его дополнительный код. Мы можем сделать это перевернув все биты и добавив единицу.
Хотите сами это попробовать? Добавьте trace(~37+1)
; в файл AS3, затем скомпилируйте его и запустите. Вы увидите как будет напечатано -37, что мы и хотели.
Также для этого имеется небольшая хитрость: начиная справа, и двигаясь влево до тех пор, пока не доберётесь до 1. Переверните все биты налево начиная с первой единицы.

Когда мы имеем дело с отмеченным бинарным числом (другими словами, тем которое может быть отрицательным, int
не unit
), мы смотрим на самое левое число, чтобы определить положительное или отрицательное оно. Если это 0, тогда число положительное и мы можем конвертировать его в десятичную систему, вычислив его согласно основе 10. Если левый крайний бит равен 1, тогда число отрицательное, мы возьмём дополнительный код числа, чтобы получить положительное значение и затем просто добавим ему отрицательный знак.
К примеру, если у нас есть 11110010, мы знаем что это отрицательное число. Мы можем найти его дополнительный код, перевернув все числа налево до крайнего правого 1, что даст нам 00001110. Это будет равно 13, так что мы знаем 11110010 равен -13.
Оператор ^
Давайте вернёмся к побитовым операторам, на этот раз мы рассмотрим оператор XOR. Для данного оператора нет эквивалента при работе с булевыми значениями.
Оператор ^ похож на операторы & и | так как принимает с обеих сторон int
или unit
. Когда дело доходит до вычисления результата, опять же сравниваются бинарные числа данного числа. Если один или другой бит равен 1, в результат добавиться 1, в другом случае 0. Вот почему этот оператор носит название XOR, или "exclusive or".

Давайте посмотрим на обычный пример:

Оператор ^ имеет свои области применения - особенно хорошо подходит для переключения бинарных чисел - но в данной статье мы не будем разбирать способы его применения на практике.
Оператор <<
Разберём операторы со сдвигом битов, остановимся на операторе левого сдвига.
Работа данного оператора немного отличается. Вместо того чтобы сравнивать два целых числа, как &, | и ^, операторы о которых пойдёт речь сдвигают целое число. Слева от оператора находится целое число, которые будет сдвинуто, а справа значение насколько стоит сдвигать. К примеру 37 << 3 сдвигает число 37 влево на 3. Конечно мы работаем с бинарным представлением 37.
Давайте посмотрим на примеры (запомните, только в этом туториале, мы решили что целые числа обладают только 8 битами вместо 32). Далее изображено число 37 в 8 битном представлении.

Отлично, давайте сдвинем все числа налево на 3, в случае 37 << 3
:

У нас есть небольшая проблема - что нам делать с тремя пустыми битами памяти, в том месте где мы сдвинули числа?

Все пустые места будут заменены нулями. У нас будет 00101000. На этом всё о левом побитовом сдвиге. Однако не забывайте, что Flash всегда считает, что результат левого побитового сдвига является int
, а не unit
. Если вдруг вам понадобится unit
, вы можете превратить это в unit
, следующим образом: unit(37 << 3)
. В этом случае не меняется никакая бинарная информация, только то как будет интерпретировать это Flash (штука с дополнительным кодом).
Интересная функция левого сдвига, так это то что это тоже самое что умножение на два возведённое в степень, равную количеству сдвига. То есть 37 << 3 == 37 * Math.pow(2,3) == 37 * 8
. Если использовать левый сдвиг вместо Math.pow
, вы получите ощутимое улучшение производительности.
Должно быть вы заметили, что полученное бинарное число не равно 37 * 8. Это всё потому что мы используем только 8 битов памяти для целых чисел; если вы попробуете пример в ActionScript, вы получите правильный результат. Можете попробовать в демонстрационном приложении наверху страницы!
Оператор >>
Теперь когда мы поняли как работает левый сдвиг, пришло время для следующего, правый побитовый сдвиг, думаю тут не должно возникнуть сложностей. Все биты сдвигаются вправо на указанное нами количество. Единственная разница чем заполняются пустые биты.
Если мы работаем с отрицательным числом (в бинарном представлении самый левый бит равен 1), все пустые места будут заполнены 1. Если же мы работаем с положительным числом (самый левый, крайний левый бит равен 0), в этом случае все пустые места заполняются 0. Всё также речь идёт о дополнительном коде, который был рассмотрен ранее.
Может показаться, что это звучит сложно, всё что происходит сохраняется знак числа, с которым мы начинали работать. Так что -8 >> 2 == -2
, а 8 >> 2 == 2
. Я рекомендую попробовать данные примеры на бумаге.
Так как >> противоположность <<, неудивительно, что сдвигая число вправо, тоже самое что деление на 2 возведённое в степень равную количеству сдвигов. Возможно вы заметили в примере выше. Опять же, если избежать использования Math.pow
, получите лучшую производительность.
Оператор >>>
Последний побитовый оператор - побитовый правый сдвиг с заполнением нулями. Очень похоже на обычный побитовый правый сдвиг, за исключением того, что пустые биты слева заполняются нулями. Это значит, что результат после работы оператора всегда будет положительным целым числом и данное целое число будет всего представлять из себя неотмеченное целое число. Мы не будем смотреть на пример, но вскоре мы увидим пример использования оператора.
Использование побитовых операторов для работы с цветами
Самый популярный на практике пример использования побитовых операторов в Actionscript 3 - работа с цветами, которые обычно представляют из себя units
.
Стандартный формат для цветов записывается, как шестнадцатеричное значение: 0xAARRGGBB - каждая буква представляет шестнадцатеричное число. В данном цвете, первые шестнадцатеричные числа, которые равны первым восьми битам, представляют альфа канал или прозрачность. Следующие восемь битов представляют количество красного в нашем цвете (целое число от 0 до 255), следующие восемь - количество зелёного и последние восемь - количество голубого цвета.
Без побитовых операторов, невероятно сложно работать с цветами в таком формате - но с ними данная задача будет очень простой!
Испытание 1: найдите количество голубого в цвете: Используйте оператор &, попытайтесь найти количество голубого в случайно - выбранном цвете.
public function findBlueComponent(color:uint):uint{// Your code here!}
Нам нужен способ "удалить" или создать маску для всех данных в color
и получить только голубой компонент. Вообще это просто! если мы возьмём color & 0x000000FF
- или если записать проще, color & 0xFF
- в результате мы получим голубой компонент.

Как вы могли заметить мы научились применять оператор &, и бинарное число & 0 всегда будет равно 0, в то время как бинарное число & 1 всегда будет сохранять свое значение. Создав маску 0xFF для нашего цвета, в котором есть только единицы, мы добились результата и получили только голубой компонент цвета.
Испытание 2: Найдите количество красного в цвете: Используя два побитовых оператора, найти количество красного нужно в случайном цвете.
public function findRedComponent(color:uint):uint{// Your code here!}
Для решения подобной задачи есть два метода. Первый - return (color & 0xFF0000) >> 16;
а другой будет return (color >> 16) & 0xFF;
Очень похоже на испытание 1, за исключением что позже нам придётся сдвинуть наш результат.
Испытание 3: найдите прозрачность цвета: Используя только один побитовый оператор, попытайтесь найти альфа канал цвета (целое число от 0 до 255).
public function findAlphaComponent(color:uint):uint{// Your code here!}
Это испытание хитрое. Мы должны быть осторожны с выбором оператора правого сдвига, какой именно стоит выбрать. Так как мы работает с крайними левыми числами unit
, нам понадобится оператор >>>. Тем самым ответ будет return color >>> 24;
.
Последнее испытание: создайте цвет из компонентов: Используя << и | операторы, возьмите компоненты цвета, соедините их в unit
.
public function createColor(a:uint, r:uint, g:uint, b:uint):uint{// Your code here!}
Здесь, мы должны сдвинуть компонент на соответствующую позицию, затем соединить их. Мы хотим, чтобы Flash расценивал это как неотмеченное целое число, тем самым мы сделаем из него unit
: return uint((a << 24) | (r << 16) | (g << 8) | b);
Составные операторы
Должно быть вы заметили, что я пропустил составные побитовые операторы. Представьте у нас есть целое число x. Следовательно, x = x & 0xFF
, тоже самое что x &= 0xFF
, x = x | 256
тоже самое что x |= 256
, и так далее, тоже самое для остальных составных операторов.
Заключение
Спасибо что прочитали эту статью! Надеюсь теперь вы понимаете побитовые операторы и как их можно использовать в AS3 коде (или других языках!). Как всегда, если у вас есть вопросы или комментарии, оставляйте их ниже.