Swift 程式語言 — Advanced Operators (1)

讓我們一起來看看什麼是高級運算符,以及了解位元運算符和溢運算符。

Jeremy Xue
Jeremy Xue ‘s Blog
11 min readMar 13, 2020

--

Photo by Mika Baumeister on Unsplash

前言:

除了基本運算符以外,Swift 還提供了一些高級運算符來執行更複雜的值操作。這些包括你將在 C 和 Objective-C 中熟悉的位元和移位運算符。

與 C 中的算術運算符不同,Swift 中的算術運算符默認情況下不會溢位(overflow)。溢位行為被捕獲及回報為錯誤。要選擇溢位行為,請使用默認情況下會溢位的 Swift 第二組算術運算符,例如溢位加法運算符(&+)。所有這些溢位運算符皆以 & 為開頭。

當定義自己的 struct、class 和 enum 時,為這些自定義類型提供標準 Swift 標準運算符的實現可能很有用。Swift 使其可以輕鬆地為這些運算符提供量身打造的實現,並且明確確定你創建的每種類型的行為。

你不僅限於預先定義的運算符。Swift 讓你可以自由定義你所自定義的前綴、中綴、後綴以及賦值運算符,以及自定義優先級和關聯性值。這些運算符可以像任何預先定義的運算符一樣在程式碼中使用和採用,勝制可以擴展現有類型來支持你定義的自定義運算符。

|位元運算符

位元運算符使你可以操作資料結構中的各個原始資料位元。他們通常用於底層編成,像是圖形編成和設備驅動創建。當你使用外部來源的原始資料,像是編碼(encoding)和解碼(decoding)資料為自定義協議進行構通,位元運算符也很有用。

Swift 支持 C 中找到的所有位元運算符,如下所述。

|位元 NOT 運算符

位元 NOT 運算符(~)將數字中的所有位元相反(inverts):

位元 NOT 運算符是一個前綴運算符,並且它緊接在其運算值之前出現,沒有任何空格:

UInt8 整數具有 8 位並且可以存儲 0 到 255 之間的任何值。此範例使用二進制值 00001111 初始化 UInt8 整數,該二進制值的前四個位設置為 0,後四位原設置為 1,與十進制的 15 是相等的。

然後用位元 NOT 運算符創建一個稱為 invertedBits 的新常數,該常數等於 intialBits,但所有位都相反。0 變為 1,而 1 變為 0。invertedBits 的值是 11110000,與十進制值 240 相等。

|位元 AND 運算符

位元 AND 運算符(&)組合兩個數字的位。只有當兩個輸入數字的為均等於 1 時,它才會返回一個新的數字,其位設置為 1:

下面的範例中,firstSixBitslastSixBits 都具有中間四個位等於 1 的值。位元 AND 運算符將他們結合在一起,來生成 00111100 的數字,該數字等於十進制值 60。

|位元 OR 運算符

位元 OR 運算符(|)比較兩個數字的位。如果任一輸入數字的位等於 1,則運算符返回一個新的數字,其位設置為 1:

在下面的範例中,someBitsmoreBits 的值將不得的位設置為 1。位元 OR 運算符將他們組合起來以生成 11111110 的數字,該數字等於十進制值 254:

|位元 XOR 運算符

位元 XOR 運算符(^),或 “互斥的 OR 運算符”,其比較兩個數字的為,運算符返回一個新的數字,如果輸入的位不同,則將其位設置為 1,如果輸入的位相同,則將其位設置為 0:

下面的範例中,firstBitsotherBits 的值都在另一個位置沒有的位置上設置為 1。位元 XOR 運算符將這兩個位的輸出值都設置為 1。firstBitsotherBits 中的所有其他位都匹配,並且輸出值中將其設置為 0:

|位元左移和右移運算符

位元左移運算符(<<)和位元右移運算符(>>)將數字中的所有位元向左或向右移動特定的位數,根據以下所定義的規則。

位元左移與右移具有將整數乘以或除以 2 的作用。將整數左移一位則其值加倍,將整數向右移一位則其值減半。

|無符號整數的移位行為

無符號整數的位移行為如下:

  1. 現有為向左或向右移動所請求的位數。
  2. 任何位移超出存儲範圍的位都將被丟棄。
  3. 再將原始位向左或向右移動後,0 被插入到留在後面的空間中。

這種方法被稱為邏輯轉換。

下圖展示了 11111111 << 1(將 11111111 左移 1 位),以及 11111111(將 11111111 右移一位)的結果。藍色數字將被移位,灰色數字將被丟棄,而橘色 0 被插入:

這是 Swift 程式碼移位看起來的樣子:

你可以使用位元移位來編碼和解碼其他資料類型中的值:

這個範例使用一個名為 pink 的 UInt32 常數來存儲粉紅色的 CSS 顏色值。CSS 顏色值 #CC6699 在 Swfit 的十六進制數字表示形式被編寫 0xCC6699。然後,位元 AND 運算符(&)和位元右移運算符(>>)將此顏色分解為紅色(CC)、綠色(66)和藍色(99)三個組件。

紅色組件是透過在數字 0xCC6699 和 0xFF0000 之間執行位元 AND 運算獲得的。而 0xFF0000 中的 0 有效的 “遮蔽(mask)” 0xCC6699 的第二個和第三個字節,導致 6699 被忽略,並且留下 0xCC0000 作為結果。

然後此數字向右移動 16 位(>>16)。十六進制數字中的每對字符使用 8 位,因此向右移動 16 位會將 0xCC0000 轉換為 0x0000CC。這與 0xCC 相同,其十進制值為 204。

同樣的,綠色組件是透過數字 0xCC6699 和 0x00FF00 之間執行位元 AND 運算而獲得的,其輸出值為 0x006600。然後,此輸出值向右移動 8 位,得到的值為 0x66,其十進制值為 102。

最後藍色組件透過數字 0xCC6699 和 0x00FF00 之間執行位元 AND 運算,其輸出值 0x000099。無需將其向右移動,因為 0x000099 已經等於 0x99,十進制值為 153。

|有符號整數的移位行為

有符號整數的移位行為比無符號整數的移位行為更加複雜,因為有符號整數使用二進制表示。(為簡單起見,下列範例基於 8 位有符號整數,但是相同的原理是用於任何大小的有符號整數)

有符號整數使用其第一位(稱為符號位)來表示整數是正數還是負數。符號位 0 為正數,符號位 1 為負數。

其餘為(稱為數值位)存儲實際值。正數的存儲方式與無符號整數的存儲方式完全相同,從 0 開始向上計數。這是 Int8 對於數字 4 樣子:

符號位是 0(表示為 “正數”),而七個數值位只是數字 4,以二進制表示。

但是,負數的存儲方式不同。透過將其絕對值減去 2 的 n 次方來存儲。其中 n 為數值位數。一個 8 位元具有七個數值位,因此它意味著 2 的 7 次方,128。

這是 Int8 對於數字 -4 的樣子:

這次,符號位為 1(表示為 “負數”),而七個數值位的二進制為 124(即為 128–4):

這種負數編碼稱為二進制補碼表示。它可能看起來是一種不尋常的方式來表示負數,但是他具有幾個優點。

首先,只需所有 8 位(包含符號位)執行標準二進制加法,然後所有不適合這 8 位元的東西丟棄,就可以將 -1 增加到 -4:

第二,二進制補碼表示還使你可以向正數一樣向左和向右移動負數的位,並且仍會在每次向左移時將它們加倍,或者在向右移的每一位時減半。為此,將有符號整數向右移動時會使用一條額外的規則:將有符號整數向右移動時,應該使用與無符號整數相同的規則,但用符號位填充左側的任何空位,而不是 0。

此操作可以確保有符號整數向右移位後具有相同的符號,這稱為算術移位。

因為存儲正負和負數的特殊方式,將他們中的任意一個向右移都會使他們更接近於 0。在此移位過程將符號位保持不變意味著負整數在其值接近於 0 時仍為負數。

|溢位運算符

如果嘗試將數字插入不能容納該值的整數常數或變數中,默認情況下,Swift 會報錯,而不是允許創建一個無效的值。當使用太大或太小的數字時,此行為可以提供額外的安全性。

例如,Int16 整數類型可以包含 -32768 到 32768 之間的任何有符號整數。嘗試將 Int16 常數或變數設置為該範圍之外的數字會導致錯誤:

當值太大或太小時提供錯誤處理,讓在處理邊界值時可以有更多的靈活性。

然而,當你特別想要溢位條件來截斷可用位數時,可以選擇採用這個行為,而不是觸發錯誤。Swift 提供了三種算數溢位符號,它們採用溢位行為進行整數計算。這些運算符都以 & 符號作為開頭:

  • 溢位加法(&+
  • 溢位減法(&-
  • 溢位乘法(&*

|值溢位

數字可以在正數或負數的方向溢位。

這是使用溢位加法運算符(&+)來允許無符號整數在正數方向溢位:

變數 unsignedOverflow 被初始化為 UInt8 可容納的最大值(255 或二進制的 11111111)。然後使用溢位加法運算符(&+)將其加 1。這使其二進制表示形式推到 UInt8 可容納的大小以上,導致其溢位超出其範圍,如下圖所示。溢位相加後,保留在 UInt8 範圍內的值為 00000000 或 0。

UInt8 可以容納的最小值為 0,即二進制的 00000000。如果使用溢位減法運算符(&+)從 00000000 中減 1,則數字將溢位迴繞 11111111,即十進制的 255。

溢位也會發生在有符號整數上。有符號整數的所有加法和減法都是位元方法方式執行的,其中符號位作為要添加或減去的數字的一部分包括在內,如同位元左移右移運算符中所述:

Int8 可以容納的最小值為 -128,即二進制 10000000。用溢位運算符從該二進制減 1 得到二進制 01111111,該二進制值將切換符號位並給出正數 127,即 Int8 可以保持的最大正數值。

對於有符號和無符號整數,正數方向上的溢位會從最大有效的正數值迴繞到最小值,負數方向上的溢位會從最小值迴繞到最大值。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]