Основы Swift / 19.2. Продвинутые операторы


Видео


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

В отличие от арифметических операторов в C арифметические операторы в Swift не вызывают переполнения по-умолчанию. Переполнения отлавливается и отмечается как ошибка. Чтобы добиться поведения переполнения используйте второй набор арифметических операций Swift, которые имеют переполнение по-умолчанию, как например переполняющийся оператор сложения (&+). Все эти переполняющие операторы начинаются со знака амперсанда.
Побитовые операторы
Побитовые операторы позволяют Вам манипулировать чистыми данными битов в структуре данных. Они часто используются в низкоуровневом программировании, таком как графическое программирование и создание драйверов устройств. Побитовые операторы так же могут быть полезные, когда Вы работаете с сырыми данными из внешних источников, как например кодирование и декодирование данных для коммуникации через специальные протоколы.
Побитовый оператор НЕ
Побитовый оператор НЕ (~) инвертирует все биты в числе. Побитовый оператор НЕ - это префиксный оператор и он пишется сразу перед значением, над которым он работает, без пробелов.
@{19.2\1}
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // равно 11110000
Целые числа типа UInt8 имеет восемь битов и может хранить любое значение от 0 до 255.

Этот пример инициализирует целое UInt8 двоичным значением 00001111, которое имеет первые 4 бита, заданные 0, а вторые четыре бита, заданные в 1. Это эквивалентно десятичному числу 15.

Оператор побитового НЕ затем используется для создания новой константы с названием invertedBits, которая равна initialBits, но все её биты инвертированы. Нули становятся единицами, а единицы - нулями. Значение invertedBits равно 11110000, что равно беззнаковому десятичному числу 240.
Побитовый оператор И
Побитовый оператор И (&) объединяет биты двух чисел. Он возвращает новое число, чьи биты равны 1, только если биты на этой позиции были равны 1 в обоих входных числах:
@{19.2\2}
В примере ниже значения firstSixBits и lastSixBits оба имеют четыре средних бита, равных 1. Оператор побитового И объединяет их для создания числа 00111100, что равно беззнаковому десятичному числу 60:
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // равно 00111100
Побитовый оператор ИЛИ
Побитовый оператор ИЛИ (|) сравнивает биты двух чисел. Оператор возвращает новое число, чьи биты установлены в 1, если хотя бы один из битов на данной позиции у двух входных чисел равен 1:
@{19.2\3}
В примере ниже значения someBits и moreBits имеют различные биты, заданные в 1. Оператор побитового ИЛИ объединяет их для создания числа 11111110, что равно беззнаковому десятичному числа 254:
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits  // равно 11111110
Побитовый оператор XOR
Побитовый оператор XOR или "оператор исключающего ИЛИ" (^) сравнивает биты двух чисел. Оператор возвращает новое число, чьи биты установлены в 1 там, где входные биты различны, и в 0, где входные биты идентичны:
@{19.2\4}
В примере ниже значения firstBits и otherBits каждое имеют установленные в 1 биты, где другое этого не делает. Оператор XOR устанавливает эти биты в 1 в выходном значении. Все остальные биты в firstBits и otherBits совпадают, так что они будут заданы в 0 в выходном числе:
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits  // равно 00010001
Побитовые оператор левого и правого сдвигов
Побитовый оператор левого сдвига (<<) и побитовый оператор правого сдвига (>>) сдвигают все биты в числе влево или вправо на заданное число позиций в соответствии с правилами, которые будут описаны ниже.

Побитовые левый и правый сдвиги имеют эффект умножения или деления делового числа на фактор двойки. Смещение целых битов влево на одну позицию удваивает его значение, тогда как сдвиг вправо на одну позицию делит его пополам.
Поведение сдвига для беззнаковых целых
Поведение побитового сдвига для беззнаковых целых следующее:
  1. Существующие биты сдвигаются влево или вправо на запрашиваемое число позиций
  2. Все биты, что вышли за пределы границ хранилища целого числа, отбрасываются.
  3. Нули вставляются в места, оставшиеся после смещения изначальных бит вправо или влево.
Такое поведение известно как логический сдвиг.

Иллюстрация ниже показывает результаты 11111111 << 1 (смещение 11111111 на одну позицию влево) и 11111111 >> 1 (смещение 11111111 на одну позицию вправо). Голубые номера смещаются, серые числа отбрасываются, а оранжевые нули вставляются:
@{19.2\5}
Здесь представлено побитовое смещение в коде Swift:
let shiftBits: UInt8 = 4   // 00000100 в двоичной системе
shiftBits << 1             // 00001000
shiftBits << 2             // 00010000
shiftBits << 5             // 10000000
shiftBits << 6             // 00000000
shiftBits >> 2             // 00000001
@{19.2\6}
Вы можете использовать побитовый сдвиг для кодирования и декодирования значений внутри других типов данных:
let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16    // redComponent is 0xCC, or 204
let greenComponent = (pink & 0x00FF00) >> 8   // greenComponent is 0x66, or 102
let blueComponent = pink & 0x0000FF           // blueComponent is 0x99, or 153»
Этот пример использует константу типа UInt32 с названием pink/ для хранения значения цвета CSS для розового цвета. Значение цвета CSS #CC6699 записывает как 0xCC6699 в шестнадцатиричном представлении Swift. Этот цвет затем раскладывается в красную (CC), зелёную (66) и голубую (99) компоненты с помощью побитового оператора И (&) и побитового оператора правого сдвига (>>).

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

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

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

В конце концов, голубая компонента получается путём применения побитового оператора к числам 0xCC6699 и 0x0000FF, что даст выходное значение 0x000099. Нет необходимости сдвигать это число вправо, так как 0x000099 уже равен 0x99, что имеет десятичное значение 153.
Поведение смещения для знаковых целых
Для знаковых чисел поведение сдвига более сложное, чем для беззнаковых ввиду способа представления целых знаковых в двоичной системе. (Примеры ниже используют 8-битные знаковые числа для простоты, но те же правила применимы для знаковых чисел любого размера.)

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

Оставшиеся биты (известные как биты данных) хранят само значение. Положительные числа хранятся точно так же, как и в случае с беззнаковыми целыми, считая вверх от нуля. Здесь представлено, как выглядят биты внутри Int8 числа 4: Бит знака равен 0 (что значит "положительное"), а семь битов значения равны числу 4 в двоичной нотации.

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

Здесь представлено, как хранятся биты внутри Int8 для числа -4: На этот раз знаковый бит равен 1 (что значит "отрицательное) и семь битов значений содержат двоичное представление 124 (что равно 128-4): Это может казаться необычным способом для представления отрицательных чисел, но это имеет несколько достоинств.

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

Ввиду специального способа хранения положительных и отрицательных чисел сдвиг их вправо делает их ближе к нулю. Сохранение бита знака в процессе их сдвига обозначает, что отрицательные числа остаются отрицательными, пока их значения становятся ближе к нулю.
Операторы с переполнением
Если Вы вставите число в целую константу или переменную, которые не могут хранить это значение, по-умолчанию Swift сообщит об ошибке вместо позволения создать некорректное значение. Это поведение даёт Вам больше безопасности, когда Вы работаете с числами, которые слишком велики или слишком малы.
@{19.2\7}
var potentialOverflow = Int16.max
// potentialOverflow equals 32767, which is the maximum value an Int16 can hold
potentialOverflow += 1
// это вызовет ошибку
Например, целое число Int16 может хранить любое знаковое целое между -32768 и 32767. Попытка задать константе или переменной типа Int16 число за пределами этого диапазона вызовет ошибку: Предоставление обработки ошибок, когда значения слишком велики или слишком малы, даёт Вам больше гибкости при работе с граничными значениями. Однако, когда Вы специально хотите использовать состояние переполнения для обрезки числа до дозволенных бит, Вы можете добиться этого поведения вместо вызова ошибки. Swift предоставляет три арифметических оператор с переполнением, что используют поведение переполнения для целочисленных вычислений. Эти операторы все начинаются с амперсанда (&):
  • Сложение с переполнением (&+)
  • Вычитание с переполнением (&-)
  • Умножение с переполнением (&*)
@{19.2\8}
Числа могут переполняться как в положительное, так и отрицательное направление.
var unsignedOverflow = UInt8.max
// unsignedOverflow equals 255, which is the maximum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &+ 1
// unsignedOverflow is now equal to 0
Здесь приведён пример того, что произойдёт если беззнаковое целое будет иметь возможность переполниться в положительное направление, используя оператор сложения с переполнением (&+):
Переменная unsignedOverflow инициализирована максимальным значением, которое может хранить UInt8 (255 или 11111111 в двоичной нотации). Затем она увенчивается на 1 с использованием оператора сложения с переполнением (&+). Это сдвигает её двоичное представление за пределы хранимых размеров UInt8, что вызовет переполнение границ, как показано на рисунке ниже. Значение, что останется внутри границ типа UInt8 после сложения с переполнением равно 00000000 или нуль.
@{19.2\9}
Нечто похожее происходит, если беззнаковому целому разрешить переполнение в отрицательном направлении. Здесь приведён пример использования оператора переполняющего вычитания (&-):
var unsignedOverflow = UInt8.min
// unsignedOverflow equals 0, which is the minimum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &- 1
// unsignedOverflow is now equal to 255
Минимальное значение, которое может храниться в UInt8 равно 0 или 00000000 в двоичной системе. Если Вы вычтете число 1 из 0000000 с использованием оператора переполняющегося вычитания (&-), то это число переполнится и обернётся в 11111111, или 255 в десятичной системе.
@{19.2\10}
Переполнение также происходит для знаковых целых. Всё сложение и вычитание для знаковых целых происходит как в случае побитовыми операциями с битом знака, включённым в числа в процессе сложения или вычитания.
var signedOverflow = Int8.min
// signedOverflow equals -128, which is the minimum value an Int8 can hold
signedOverflow = signedOverflow &- 1
// signedOverflow is now equal to 127
Минимальное значение Int8 равно -128 или 10000000 в двоичной нотации. Вычтя 1 из этого двоичного числа получим двоичное значение 01111111, что меняет бит знака и даёт положительное значение 127, что есть максимальное положительное число, которое может хранить тип Int8.



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

Ассоциативность определяет, как операторы одной прецедентности работают вместе - группируются ли слева, или справа. Думайте об этом, как "они связаны с выражением
@{19.2\11}
Важно определить прецедентность и ассоциативность каждого оператора при выяснении порядка, в котором будет вычислено составное выражение. Например, прецедентность оператора объясняет собой, почему следующее выражение равно 17.
2 + 3 % 4 * 5
Если Вы прочитаете его строго слева направо, то Вы можете ожидать, что выражение будет вычислено следующим образом:
  1. 2 плюс 3 равно 5
  2. 5 в остатке от деления на 4 равно 1
  3. 1 умножить на 5 равно 5
Однако настоящий ответ равен 17, а не 5. Более-Прецедентных операторы вычисляются перед операторами с меньшей прецедентностью. В Swift, как и в C, оператор остатка от деления (%) и оператор умножения (*) имеют большую прецедентность чем оператор сложения (+). В результате они оба будут вычислены перед сложением.
@{19.2\12}
Однако оператор остатка от деления и умножения имеют одинаковую прецеденость. Для того, что выяснить, какой порядок вычислений использовать, Вам так же нужно знать их ассоциативность. Остаток от деления и умножение оба ассоциированы с их левым выражением. Думайте об этом, как о добавлении явных скобок вокруг частей выражения, начиная слева:
2 + ((3 % 4) * 5)
@{19.2\13}
(3 % 4) равно 3, так что это эквивалентно:
2 + (3 * 5)
@{19.2\14}
(3 * 5) равно 15, так что это эквивалентно:
2 + 15
Это вычисление в итоге даст ответ 17.

Прецедентность и ассоциативность операторов в Swift проще и предсказуемее чем в C и Objective-C. Однако это значит, что они работают не так же, как в C-подобных языках. Будьте осторожны, чтобы убедиться, что взаимодействия операторов ведут себя так же, как Вы ожидаете при портировании существующего кода на Swift.