Видеокурсы по изучению языка программирования Swift. Подробнее

Побитовые операторы

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

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

Swift поддерживает все побитовые операторы, которые были основаны в C и которых мы поговорим далее.

Побитовый оператор NOT

Побитовый оператор NOT (~) инвертирует все битовые числа:

Побитовый оператор NOT является префиксным оператором и ставится прямо перед значением (без пробела), над которым он оперирует.

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // равен 11110000

Целые числа типа UInt8 имеют восемь бит и могут хранить значения от 0 до 255. В этом примере инициализируем число типа UInt8, которое имеет бинарное значение 00001111, которое имеет первые четыре бита равные 0, а вторая четверка битов равна 1. Это эквивалент числа 15.

Далее используем побитовый оператор NOT для создания новой константы invertedBits, которая равна initialBits, но только с перевернутыми битами. То есть теперь все единицы стали нулями, а нули единицами. Значение числа invertedBits равно 11110000, что является эквивалентом 240.

Побитовый оператор AND

Побитовый оператор AND (&) комбинирует два бита двух чисел. Он возвращает новое число, чье значение битов равно 1, если только оба бита из входящих чисел были равны 1:

В примере ниже, значения firstSixBits и lastSixBits имеют четыре бита по середине равными 1. Побитовый оператор AND комбинирует их для создания числа 001111100, которое равно беззнаковому целому числу 60:

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // равен 00111100

Побитовый оператор OR

Побитовый оператор OR (|) сравнивает биты двух чисел. Оператор возвращает новое число, чьи биты устанавливаются на 1, если один и пары битов этих двух чисел имеет бит равный 1:

В примере ниже значения someBits и moreBits имеют разные биты со значениями 1. Побитовый оператор OR комбинирует их для создания числа 11111110, что равно беззнаковому целому числу 254:

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits  // равен 11111110

Побитовый оператор XOR

Побитовый оператор XOR или “оператор исключающего OR” (^), который сравнивает биты двух чисел. Оператор возвращает число, которое имеет биты равные 1, когда биты входных чисел разные, и возвращает 0, когда биты одинаковые:

В примере ниже, значения firstBits и otherBits каждый имеет один бит в том месте, где у другого 0. Побитовый оператор XOR устанавливает оба этих бита в качестве выходного значения. Все остальные биты повторяются, поэтому оператор возвращает 0:

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits  // равен 00010001

Операторы побитового левого и правого сдвига

Оператор побитового левого сдвига (<<) и оператор побитового правого сдвига (>>) двигают все биты числа влево или вправо на определенное количество мест, в зависимости от правил, которые определены ниже.

Побитовые операторы левого и правого сдвига имеют эффект умножения или деления числа на 2. Сдвигая биты целого числа влево на одну позицию, мы получаем удвоенное первоначальное число, в то время как, двигая его вправо на одну позицию, мы получаем первоначальное число поделённое на 2.

Поведение сдвига для беззнаковых целых чисел

Поведение побитового сдвига имеет следующие правила:

  • Существующие биты сдвигаются направо или налево на требуемое число позиций.
  • Любые биты, которые вышли за границы числа, отбрасываются.
  • На пустующие позиции сдвинутых битов вставляются нули.

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

Иллюстрация внизу отображает результат смещения 11111111 << 1 (что означает 11111111 сдвинутые влево на 1), и 11111111 >> 1 (что означает 11111111 сдвинутые на 1 вправо). Голубые цифры - сдвинутые, серые - отброшенные, оранжевые - вставленные:

Вот как выглядит побитовый сдвиг в виде Swift кода:

let shiftBits: UInt8 = 4   // 00000100 бинарный вид
shiftBits << 1       // 00001000
shiftBits << 2       // 00010000
shiftBits << 5       // 10000000
shiftBits << 6       // 00000000
shiftBits >> 2       // 00000001

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

let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent равен 0xCC, или 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent равен 0x66, или 102
let blueComponent = pink & 0x0000FF // blueComponent равен 0x99, или 153

Этот пример использует UInt32, который называется pink, для хранения значение розового цвета из файла CSS. Значение розового цвета #CC6699, что записывается в виде шестнадцатеричном представлении Swift как 0xCC6699. Этот цвет затем раскладывается на его красный(CC), зеленый (66) и голубой (99) компоненты при помощи побитового оператора AND (&) и побитового оператора правого сдвига (>>).

Красный компонент получен с помощью побитового оператора AND между числами 0xCC6699 и 0xFF0000. Нули в 0xFF0000 фактически являются “маской” для третьего и четвертого бита в 0xCC6699, тем самым заставляя игнорировать 6699, и оставляя 0xCC0000 в качестве результата.

После этого число сдвигается на 16 позиций вправо (>> 16). Каждая пара символов в шестнадцатеричном числе использует 8 битов, так что сдвиг вправо на 16 позиций преобразует число 0xCC0000 в 0x0000CC. Это то же самое, что и 0xCC, которое имеет целое значение равное 204.

Аналогично с зеленым компонентом, который получается путем использования побитового оператора AND между числами 0xCC6699 и 0x00FF00, который в свою очередь дает нам выходное значение 0x006600. Это выходное значение затем сдвигается на восемь позиций вправо, давая нам значение 0x66, что имеет целое значение равное 102.

Ну а теперь последний синий компонент, который получается при использовании побитового оператора AND между числами 0xCC6699 и 0x0000FF, что в свою очередь дает нам выходное значение равное 0x000099. Таким образом, нам не нужно сдвигать это вправо, так как 0x000099 уже равно 0x99, что имеет целое значение равное 153.

Поведение побитового сдвига для знаковых целых чисел

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

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

Остальные биты (известные как биты значения) хранят фактическое значение. Положительные числа хранятся в точности так же как и беззнаковые целые числа, считая от 0. Вот как выглядят биты внутри Int8 для числа 4:

Знаковый бит равен 0 (число положительное), остальные семь битов означают число 4, записанное в бинарной форме.

Однако отрицательные числа хранятся иначе. Они хранятся путем вычитания их абсолютного значения из 2 в степени n, где n - количество битов значения.

Вот как выглядит биты внутри Int8 для числа -4:

В этот раз, знаковый бит равен 1 (число отрицательное), а остальные семь знаковых бита имеют бинарное значение числа 124 (что означает 128 - 4):

Шифрование отрицательных чисел известно под названием дополнение до двух. Это может показаться необычным способом для отображения отрицательных чисел, но в нем есть несколько преимуществ.

Первое. Вы можете добавить -1 к -4, просто выполняя стандартное сложение всех восьми битов (включая и восьмой бит), и отбрасывая все, что не поместится в ваши восемь бит:

Второе. Представление “дополнения до 2” так же позволяет вам сдвигать биты отрицательных чисел влево и вправо, как в случае с положительными, и все так же умножая их при сдвиге влево или уменьшая их в два раза, при сдвиге на 1 место вправо. Для того чтобы обеспечить такое поведение при движении знаковых чисел вправо, мы должны применить дополнительное правило:

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

Эти действия гарантируют вам, что знаковые числа имеют тот же знак, после того как они сдвинуты вправо, и эти действия известны как арифметический сдвиг.

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

Swift: 
3.0