Swift學習筆記 (四十) 高級運算符(上)

除了之前介紹過的《基本運算符》,Swift 還提供了數種可以對數值進⾏複雜運算的高級運算符。它們包含了在 C 和 Objective-C

中已經被大家所熟知的位運算符和移位運算符。

與 C 語言中的算術運算符不同,Swift 中的算術運算符默認是不會溢出的。所有溢出行爲都會被捕獲並報告爲錯誤。如果想讓系

統允許溢出⾏爲,可以選擇使用 Swift 中另一套默認支持溢出的運算符,比如溢出加法運算符( &+ )。所有的這些溢出運算符都

是以 & 開頭的。

自定義結構體、類和枚舉時,如果也爲它們提供標準 Swift 運算符的實現,將會非常有用。在 Swift 中爲這些運算符提供自定義

的實現⾮常簡單,運算符也會針對不同類型使用對應實現。

我們不用被預定義的運算符所限制。在 Swift 中可以自由地定義中綴、前綴、後綴和賦值運算符,它們具有自定義的優先級與關

聯值。這些運算符在代碼中可以像預定義的運算符一樣使用,你甚⾄可以擴展已有的類型以支持自定義運算符。

 

位運算符

位運算符可以操作數據結構中每個獨立的比特位。它們通常被用在底層開發中,比如圖形編程和創建設備驅動。位運算符在處理

外部資源的原始數據時也十分有用,⽐如對自定義通信協議傳輸的數據進⾏編碼和解碼。

Swift 支持 C 語言中的全部位運算符,接下來會⼀一介紹。

 

Bitwise NOT Operator(按位取反運算符)

按位取反運算符( ~ )對一個數值的全部比特位進行取反:

按位取反運算符是一個前綴運算符,直接放在運算數之前,並且它們之間不能添加任何空格:

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits                    // 等於 0b11110000

UInt8 類型的整數有 8 個比特位,可以存儲 0 ~ 255 之間的任意整數。這個例子初始化了一個 UInt8 類型的整數,並賦值爲二進

制的 00001111 ,它的前 4 位爲 0 ,後 4 位爲 1 。這個值等價於十進制的 15 。

接着使用按位取反運算符創建了一個名爲 invertedBits 的常量,這個常量的值與全部按位取反後的 initialBits 相等。即所有的 0 

都變成了 1 ,同時所有的 1 都變成 0 。 invertedBits 的二進制值爲 11110000 ,等價於無符號十進制數的 240 。

 

Bitwise AND Operator(按位與運算符) 

按位與運算符( & ) 對兩個數的比特位進⾏合併。它返回一個新的數,只有當兩個數的對應位都爲 1 的時候,新數的對應位才爲 1 :

在下面的示例當中, firstSixBits 和 lastSixBits 中間 4 個位的值都爲 1 。使用按位與運算符之後,得到二進制數值 00111100 ,等

價於無符號十進制數的 60 :

let firstSixBits: UInt8 = 0b11111100

let lastSixBits: UInt8 = 0b00111111

let middleFourBits = firstSixBits & lastSixBits                  // 等於 00111100

 

Bitwise OR Operator(按位或運算符)

按位或運算符( | )可以對兩個數的比特位進⾏比較。它返回一個新的數,只要兩個數的對應位中有任意一個爲 1 時,新數的對應

位就爲 1 :

在下⾯的示例中, someBits 和 moreBits 存在不同的位被設置爲 1 。使用按位或運算符之後,得到二進制數值 11111110 ,等價於

無符號十進制數的 254 :

let someBits: UInt8 = 0b10110010

let moreBits: UInt8 = 0b01011110

let combinedbits = someBits | moreBits                    // 等於 11111110

 

Bitwise XOR Operator(按位異或運算符)

按位異或運算符,或稱“排外的或運算符”( ^ ),可以對兩個數的比特位進⾏比較。它返回一個新的數,當兩個數的對應位不相同時,新數的對應位就爲 1 ,並且對應位相同時則爲 0 :

在下面的示例當中, firstBits 和 otherBits 都有一個⾃己爲 1 ,⽽對方爲 0 的位。按位異或運算符將新數的這兩個位都設置爲 1 

。在其餘的位上 firstBits 和 otherBits 是相同的,所以設置爲 0 :

let firstBits: UInt8 = 0b00010100

let otherBits: UInt8 = 0b00000101

let outputBits = firstBits ^ otherBits              // 等於 00010001

 

Bitwise Left and Right Shift Operators(按位左移、右移運算符)

按位左移運算符( << ) 和 按位右移運算符( >> )可以對一個數的所有位進⾏指定位數的左移和右移,但是需要遵守下面定義的規

則。

對一個數進⾏按位左移或按位右移,相當於對這個數進⾏乘以 2 或除以 2 的運算。將一個整數左移一位,等價於將這個數乘以

2,同樣地,將一個整數右移一位,等價於將這個數除以 2。

 

無符號整數的移位運算

對無符號整數進⾏移位的規則如下:

    1. 已存在的位按指定的位數進⾏左移和右移。

    2. 任何因移動而超出整型存儲範圍的位都會被丟棄。

    3. ⽤ 0 來填充移位後產生的空白位。

這種方法稱爲邏輯移位。

以下這張圖展示了了 11111111 << 1 (即把 11111111 向左移動 1 位),和 11111111 >> 1 (即把 11111111向右移動 1 位)的結果。藍色的

數字是被移位的,灰色的數字是被拋棄的,橙色的 0 則是被填充進來的:

下⾯面的代碼演示了了 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

 

這個示例使用了一個命名爲 pink 的 UInt32 型常量來存儲 Cascading Style Sheets(CSS)中粉色的顏色值。該 CSS 的顏色值 

#CC6699 ,在 Swift 中表示爲十六進制的 0xCC6699 。然後利用按位與運算符( & )和按位右移運算符( >> )從這個顏色值中分解

出紅( CC )、綠( 66 )以及藍( 99 )三個部分。

紅色部分是通過對 0xCC6699 和 0xFF0000 進⾏按位與運算後得到的。 0xFF0000 中的 0 部分“掩蓋”了OxCC6699 中的第⼆

二、第三個字節,使得數值中的 6699 被忽略,只留下 0xCC0000 。

然後,將這個數向右移動 16 位( >> 16 )。十六進制中每兩個字符佔用 8 個比特位,所以移動 16 位後 0xCC0000 就變爲 

0x0000CC 。這個數和 0xCC 是等同的,也就是⼗進制數值的 204 。

同樣的,綠色部分通過對 0xCC6699 和 0x00FF00 進⾏按位與運算得到 0x006600 。然後將這個數向右移動 8 位, 得到 0x66 

,也就是十進制數值的 102 。

最後,藍色部分通過對 0xCC6699 和 0x0000FF 進⾏按位與運算得到 0x000099 。這⾥不需要再向右移位,而 0x000099 也就

是 0x99 ,也就是十進制數值的 153 。

 

有符號整數的移位運算

對比無符號整數,有符號整數的移位運算相對複雜得多,這種複雜性源於有符號整數的二進制表現形式。(爲了簡單起見,以下的

示例都是基於 8 比特的有符號整數,但是其中的原理對任何位數的有符號整數都是通用的。)

有符號整數使用第 1 個比特位(通常被稱爲符號位)來表示這個數的正負。符號位爲 0 代表正數,爲 1 代表負數。

其餘的比特位(通常被稱爲數值位)存儲了實際的值。有符號正整數和無符號數的存儲⽅式是一樣的,都是從 0 開始算起。這是值

爲 4 的 Int8 型整數的二進制位表現形式:

符號位爲 0 (代表這是一個“正數”),另外 7 位則代表了十進制數值 4 的二進制表示。 負數的存儲方式略有不同。它存儲 2 的 n 

次方減去其實際值的絕對值,這里的 n 是數值位的位數。一個 8 比特位的數有 7 個比特位是數值位,所以是 2 的 7 次方,即 128 

這是值爲 -4 的 Int8 型整數的二進制表現形式:

這次的符號位爲 1 ,說明這是⼀個負數,另外 7 個位則代表了數值 124 (即 128 - 4 )的二進制表示:

負數的表示通常被稱爲二進制補碼。用這種方法來表示負數乍看起來有點奇怪,但它有⼏個優點。

首先,如果想對 -1 和 -4 進行加法運算,我們只需要對這兩個數的全部 8 個比特位執⾏標準的二進制相加(包括符號位),並且將

計算結果中超出 8 位的數值丟棄:

其次,使用二進制補碼可以使負數的按位左移和右移運算得到跟正數同樣的效果,即每向左移一位就將⾃身的數值乘以 2,每向

右一位就將自身的數值除以 2。要達到此目的,對有符號整數的右移有一個額外的規則:當對有符號整數進⾏按位右移運算時,遵

循與⽆符號整數相同的規則,但是對於移位產生的空白位使用符號位進⾏填充,⽽不是用 0 。

這個⾏爲可以確保有符號整數的符號位不會因爲右移運算而改變,這通常被稱爲算術移位。 由於正數和負數的特殊存儲方式,在

對它們進⾏右移的時候,會使它們越來越接近 0 。在移位的過程中保持符號位不變,意味着負整數在接近 0 的過程中會一直保持

爲負。

 

溢出運算符

當向⼀個整數類型的常量或者變量賦予超過它容量的值時,Swift 默認會報錯,⽽不是允許生成⼀個⽆效的數。這個⾏爲爲我們

在運算過大或者過小的數時提供了額外的安全性。

例如, Int16 型整數能容納的有符號整數範圍是 -32768 到 32767 。當爲一個 Int16 類型的變量或常量賦予的值超過這個範圍

時,系統就會報錯:

var potentialOverflow = Int16.max       // potentialOverflow 的值是 32767,這是 Int16 能容納的最大整數

potentialOverflow += 1                         // 這⾥裏里會報錯

在賦值時爲過大或者過小的情況提供錯誤處理,能讓我們在處理邊界值時更加靈活。

然而,當有時候你也希望可以選擇讓系統在數值溢出的時候採取截斷處理,⽽非報錯。Swift 提供的三個溢出運算符來讓系統支

持整數溢出運算。這些運算符都是以 & 開頭的:

    溢出加法 &+

    溢出減法 &-

    溢出乘法 &*

 

數值溢出

數值有可能出現上溢或者下溢。

這個示例演示了當我們對一個無符號整數使用溢出加法( &+ )進⾏上溢運算時會發⽣什麼:

var unsignedOverflow = UInt8.max                                      // unsignedOverflow 等於 UInt8 所能容納的最大整數 

255unsignedOverflow = unsignedOverflow &+ 1              // 此時 unsignedOverflow 等於 0

unsignedOverflow 被初始化爲 UInt8 所能容納的最大整數( 255 ,以二進制表示即 11111111 )。然後使用溢出加法運算符( &+ )對

其進⾏加 1 運算。這使得它的二進制表示正好超出 UInt8 所能容納的位數,也就導致了數值的溢出,如下圖所示。數值溢出後,

仍然留在 UInt8 邊界內的值是 00000000 ,也就是十進制數值的 0 。

當允許對一個無符號整數進⾏下溢運算時也會產⽣類似的情況。這⾥有一個使⽤溢出減法運算符( &- )的例子:

var unsignedOverflow = UInt8.min                                 // unsignedOverflow 等於 UInt8 所能容納的最小整數 

0unsignedOverflow = unsignedOverflow &- 1              // 此時 unsignedOverflow 等於 255

UInt8 型整數能容納的最小值是 0 ,以二進制表示即 00000000 。當使用溢出減法運算符對其進⾏減 1 運算時, 數值會產生下溢

並被截斷爲 11111111 , 也就是十進制數值的 255 。

溢出也會發生在有符號整型上。針對有符號整型的所有溢出加法或者減法運算都是按位運算的方式執⾏的,符號位也需要參與計

算,正如《按位左移、右移運算符》所描述的。

var signedOverflow = Int8.min                             // signedOverflow 等於 Int8 所能容納的最小整數 -128

signedOverflow = signedOverflow &- 1             // 此時 signedOverflow 等於 127

Int8 型整數能容納的最小值是 -128 ,以二進制表示即 10000000 。當使用溢出減法運算符對其進⾏減 1 運算時,符號位被翻

轉,得到二進制數值 01111111 ,也就是十進制數值的 127 ,這個值也是 Int8 型整所能容納的最大值。


對於⽆符號與有符號整型數值來說,當出現上溢時,它們會從數值所能容納的最大數變成最小數。同樣地,當發生下溢
時,它們會從所能容納的最小數變成最大數。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章