Swift – Advanced Operators

A máme tu poslední kapitolu z knihy The Swift Programming Language, která se jmenuje Advanced Operators. Jak z názvu plyne, mrknem na operátory a co se s tím dá všechno dělat.

V jednom z předchozích článků jsme probrali základní operátory. Dnes to povídání trochu rozšíříme a mrknem na pár složitějších věcí – bitové operátory, vlastní operátory, overflow operátory, …

Bitové operátory

~  = NOT – invertuje bity

let b: UInt8 =    0b00001111
let ib = ~b // == 0b11110000

&  = AND – 1 pokud jsou oba bity 1, jinak 0

let i1: UInt8       = 0b11111100
let i2: UInt8       = 0b00111111
let r = i1 & i2 // == 0b00111100

|  = OR – 1 pokud alespoň jeden bit je 1, jinak 0

let i1: UInt8       = 0b10110010
let i2: UInt8       = 0b01011110
let r = i1 | i2 // == 0b11111110

^  = XOR – 1 pokud se bity liší, jinak 0

let i1: UInt8       = 0b00010100
let i2: UInt8       = 0b00000101
let r = i1 ^ i2 // == 0b00010001

Bitový posun & unsigned integer

Operátory jsou dva – «  a » – bitový posun doleva a doprava. K čemu to slouží? Jde o násobení nebo dělení celým číslem, které je mocninou dvojky.

UInt8( 128 ) >> 1   //    /2 = 64
UInt8( 128 ) >> 2   //    /4 = 32
UInt8( 128 ) >> 3   //    /8 = 16
UInt8( 128 ) >> 4   //   /16 =  8
UInt8( 128 ) >> 5   //   /32 =  4
UInt8( 128 ) >> 6   //   /64 =  2
UInt8( 128 ) >> 7   //  /128 =  1

Analogicky funguje bitový posun doleva, ale s tím rozdílem, že jde o násobení. Pravidla bitového posunu jsou následující:

  • bity jsou posunuty o zvolený počet míst doleva nebo doprava,
  • bity, které se dostanou mimo hranici úložiště se zahazují,
  • na prázdná místa jsou vloženy nuly.

Bitový posun se například používá pro extrakci jednotlivých komponent barvy.

let color: UInt32 = 0xAABBCCDD // RRGGBBAA

let red   = ( color & 0xFF000000 ) >> 24  // == 0xAA
let green = ( color & 0x00FF0000 ) >> 16  // == 0xBB
let blue  = ( color & 0x0000FF00 ) >> 8   // == 0xCC
let alpha = ( color & 0x000000FF )        // == 0xDD

Bitový posun & signed integer

Tady platí jiná pravidla, protože signed integer je uložen trochu jinak. Ukážeme si to na Int8 , ale jinak níže uvedené platí pro Int16 , Int32 , …

První bit Int8  nám říká, zda-li je číslo kladné (0) a nebo záporné (1). Ve zbývajících sedmi bytech je uložena hodnota samotného čísla. U kladného čísla se hodnota počítá stejně jako u unsigned integeru. Záporné číslo se ukládá jinak: 2^n – absolutní hodnota čísla  kde n  je počet bitů pro hodnotu. Což u Int8  znamená, že n = 7 .

Kladná čísla se tedy ukládají takto:

Int8(   0 ) == 0b00000000
Int8(   1 ) == 0b00000001
Int8(   4 ) == 0b00000100
Int8(  20 ) == 0b00010100
Int8( 127 ) == 0b01111111

A záporná:

// 1. bit == 1 => zaporne cislo
-1 // == 11111111  2.-8. bit == 127 == 2^7 - abs( -1 )
-4 // == 11111100  2.-8. bit == 124 == 2^7 - abs( -4 )
-5 // == 11111011  2.-8. bit == 123 == 2^7 - abs( -5 )

Při posunu doleva se nic nemění a funguje to stejně jako u unsigned integeru, tzn.

var num = Int8( 127 )
num == 0b01111111 // true
num = num << 1    // == -2 == 11111110

Při posunu doprava to funguje stejně jako u unsigned integeru s jednou změnou – prázdná místa po bitech (vlevo) se nevyplňují nulou, ale aktuální hodnotou znaménkového (prvního) bitu. Kladné číslo je pořád kladné, záporné je pořád záporné.

var num: Int8 = 127 // == 01111111 == 127
num = num >> 1      // == 00111111 == 63

num = -4       // == 11111100 == -4
num = num >> 2 // == 11111111 == -1

Overflow, undeflow

Pokud dojde k přetečení / podtečení při aritmetické operaci, dojde k chybě:

var i: Int8 = Int8.max + 1

Arithmetic operation ‘127 + 1’ (on type ‘Int8’) results in an overflow.

Nezoufejte, máme tu _opt-in _operátory pro přetečení / podtečení a to &+ , &- , &* , &/  , &% . Fungují stejně jako klasické verze operátorů, ale přetečení / podtečení už není chybou.

var i: Int8 = Int8.max &+ 1 // == -128

A co 0? Těch pádů aplikace, co? 🙂

// nelze, chyba
1 / 0 // Division by zero
1 % 0 // Division by zero

// lze, vzdy vrati 0
1 &/ 0 == 0  // true
1 &% 0 == 0  // true

Priorita operátorů

V aritmetických operacích mají operátory různou prioritu – tj. vyhodnocují se v určitém pořadí. Tomu se říká operator precedence. Operátory se stejnou prioritou se následně vyhodnocují zleva nebo zprava. Tomu se pro změnu říká operator associativity. Detailnější informace si nastudujte v kapitole Expressions. Vřele doporučuji, protože se liší od C a Objective-C. Tak ať si do kódu nezačnete zanášet chyby už od začátku 🙂

Přetěžování existujících operátorů

U struktur a tříd můžeme přetížit existující operátory pomocí globálních funkcí. Příklad:

struct Point {
  var x = 0.0
  var y = 0.0
}

@infix func + ( left: Point, right: Point ) -> Point {
  return Point( x: left.x + right.x, y: left.y + right.y )
}

let p = Point( x: 10, y: 20 ) + Point( x: 30, y: 40 )
// p == Point( x: 40, y: 60 )

Tím jsme si definovali binární infixový operátor. Prefixový operátor se definuje takto.

@prefix func - ( point: Point ) -> Point {
  return Point( x: -point.x, y: -point.y )
}

let p = -Point( x: 10, y: 20 )
// p == Point( x: -10, y: -20 )

Compound Assignment

Netuším jak to přeložit, ale složené, sloučené, … přiřazení zní dost divně. Tak to nebudu překládat. O co jde? Je to operátor přiřazení (= ), který je kombinován s jiným operátorem. Například + , tj. += .

@assignment func += ( inout left: Point, right: Point ) {
  left = left + right
}

var p1 = Point( x: 10, y: 20 )
p1 += Point( x: 30, y: 40 )
// p1 == Point( x: 40, y: 60 )

Od předchozích ukázek se to liší ve dvou částech:

  • funkce nemá návratovou hodnotu,
  • první parametr je inout , protože operátor ho přímo modifikuje.

@assignment  se dá kombinovat s @prefix  / @postfix  a tak si můžeme přetížit třeba prefixový operátor ++ .

@prefix @assignment func ++ ( inout left: Point ) -> Point {
  left += Point( x: 1, y: 1 )
  return left
}

var p1 = Point( x: 10, y: 20 )
++p1 // p1 == Point( x: 11, y: 21 )

Možná by to chtělo i nějaké to porovnávání.

@infix func == ( left: Point, right: Point ) -> Bool {
  return ( left.x == right.x ) && ( left.y == right.y )
}

@infix func != ( left: Point, right: Point ) -> Bool {
  return !( left == right )
}

Vlastní operátory

Pomocí znaků / = – + * % < > ! & ^ . ~  si můžeme implementovat vlastní operátory – prefixové, infixové a postfixové. Slouží k tomu klíčové slovo operator . Tak si vytvoříme infixové operátory na přibližné porovnávání dvou našich bodů. A to ~=  a !~= . První bude true  v případě, že vzdálenost obou bodů je rovná nebo menší jak 1.0 . Druhý operátor bude jenom negace toho prvního.
// deklarace operatoru
operator infix ~= {}
operator infix !~= {}

// implementace operatoru
@infix func ~= ( left: Point, right: Point ) -> Bool {
  let distance = sqrt( pow( ( left.x - right.x ), 2 ) + pow( ( left.y - right.y ), 2 ) )
  return distance <= 1.0
}

@infix func !~= ( left: Point, right: Point ) -> Bool {
  return !( left ~= right )
}

// pouziti operatoru
Point( x: 10, y: 10 )  ~= Point( x:11, y:10 )         // true
Point( x: 10, y: 10 )  ~= Point( x:11.000001, y:10 )  // false
Point( x: 10, y: 10 ) !~= Point( x:11.000001, y:10 )  // true

Proč ty {}  u deklarace operátorů? Protože si u nich můžeme definovat vlastní prioritu a asociativitu. Pokud tak neučiníme, je to by default 100  (priorita) a none  (asociativita).

operator infix ~= { associativity left precedence 140}
operator infix !~= { associativity right precedence 140 }

Závěrem

Více ukázek najdete třeba v projektu Cartography, který používá operátory pro Auto Layout. Na jednu stranu pěkné, na druhou stranu nevím, nevím. Ukázka, která vzbuzuje rozporuplné pocity. Zda-li touto cestou jít nebo ne. Je to podobné jako s generikama. Myslete na to jak se bude daný kód udržovat, že ho budou po vás číst další lidé, … A tak se budu opěkovat opakovat – s rozmyslem, s rozmyslem … 🙂

Tímto končím sérii článků, která se zabývala Swiftem, resp. částí knihy The Swift Programming Language – Language Guide. Ale nebojte, není to poslední článek na téma Swift 🙂