一、位運算符
位操作符通常在諸如圖像處理和創建設備驅動等底層開發中使用,使用它可以單獨操作數據結構中原始數據的比特位。在使用一個自定義的協議進行通信的時候,運用位運算符來對原始數據進行編碼和解碼也是非常有效的。
1、按位取反運算符
-
~
: 按位取反運算符, 對一個操作數的每一位都取反
- 這個運算符是前置的,所以請不加任何空格地寫著操作數之前。
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000
2、按位與運算符
-
&
: 按位與運算符對兩個數進行操作,然后返回一個新的數,這個數的每個位都需要兩個輸入數的同一位都為1
時才為1
。
- 以下代碼,firstSixBits和lastSixBits中間4個位都為1。對它倆進行按位與運算后,就得到了00111100,即十進制的60。
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
3、按位或運算
-
|
: 按位或運算符, 比較兩個數,然后返回一個新的數,這個數的每一位設置1
的條件是兩個輸入數的同一位都不為0
(即任意一個為1
,或都為1
)。
- 如下代碼,someBits和moreBits在不同位上有1。按位或運行的結果是11111110,即十進制的254。
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
4、按位異或運算符
-
^
: 按位異或運算符, 比較兩個數,然后返回一個數,這個數的每個位設為1的條件是兩個輸入數的同一位不同,如果相同就設為0。
- 以下代碼,firstBits和otherBits都有一個1跟另一個數不同的。所以按位異或的結果是把它這些位置為1,其他都置為0。
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
5、按位左移/右移運算符
左移運算符
<<
和右移運算符>>
會把一個數的所有比特位按以下定義的規則向左或向右移動指定位數。按位左移和按位右移的效果相當把一個整數乘于或除于一個因子為2的整數。向左移動一個整型的比特位相當于把這個數
乘于2
,向右移一位就是除于2
。
6、無符整型的移位操作
- 對無符整型的移位的效果如下:
已經存在的比特位向左或向右移動指定的位數。
被移出整型存儲邊界的的位數直接拋棄,移動留下的空白位用零0來填充。這種方法稱為邏輯移位。
- 以下這張把展示了 11111111 << 1(11111111向左移1位),和 11111111 >> 1(11111111向右移1位)。藍色的是被移位的,灰色是被拋棄的,橙色的0是被填充進來的
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中粉色的顏色值,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,無需向右移位了,所以結果就是0x99,即十進制的153。
7、有符整型的移位操作
- 有符整型的移位操作相對復雜得多,因為正負號也是用二進制位表示的
- 有符整型通過第1個比特位(稱為符號位)來表達這個整數是正數還是負數。0代表正數,1代表負數。
- 其余的比特位(稱為數值位)存儲其實值。有符正整數和無符正整數在計算機里的存儲結果是一樣的,下來我們來看+4內部的二進制結構。
- 符號位為0,代表正數,另外7比特位二進制表示的實際值就剛好是4。
- 負數呢,跟正數不同。負數存儲的是
2
的n
次方減去它的絕對值,n為數值位的位數。一個8
比特的數有7
個數值位,所以是2
的7
次方,即128
。 - 我們來看-4存儲的二進制結構。
- 現在符號位為
1
,代表負數,7
個數值位要表達的二進制值是124
,即128 - 4
。
負數的編碼方式稱為二進制補碼表示。這種表示方式看起來很奇怪,但它有幾個優點。
首先,只需要對全部8個比特位(包括符號)做標準的二進制加法就可以完成 -1 + -4 的操作,忽略加法過程產生的超過8個比特位表達的任何信息。
- 第二,由于使用二進制補碼表示,我們可以和正數一樣對負數進行按位左移右移的,同樣也是左移1位時乘于2,右移1位時除于2。要達到此目的,對有符整型的右移有一個特別的要求:
對有符整型按位右移時,使用符號位(正數為0,負數為1)填充空白位。
這就確保了在右移的過程中,有符整型的符號不會發生變化。這稱為算術移位。
正因為正數和負數特殊的存儲方式,向右移位使它接近于0。移位過程中保持符號會不變,負數在接近0的過程中一直是負數。
二、溢出運算符
- 默認情況下,當你往一個整型常量或變量賦于一個它不能承載的大數時,Swift不會讓你這么干的,它會報錯。這樣,在操作過大或過小的數的時候就很安全了。
var potentialOverflow = Int16.max
// potentialOverflow 等于 32767, 這是 Int16 能承載的最大整數
potentialOverflow += 1
// 噢, 出錯了
- 當然,你有意在溢出時對有效位進行截斷,你可采用溢出運算,而非錯誤處理。Swfit為整型計算提供了5個&符號開頭的溢出運算符。
溢出加法 &+
溢出減法 &-
溢出乘法 &*
溢出除法 &/
溢出求余 &%
1、值的上溢出
- 下面例子使用了溢出加法
&+
來解剖的無符整數的上溢出
var willOverflow = UInt8.max
// willOverflow 等于UInt8的最大整數 255
willOverflow = willOverflow &+ 1
// 這時候 willOverflow 等于 0
- willOverflow用Int8所能承載的最大值255(二進制11111111),然后用&+加1。然后UInt8就無法表達這個新值的二進制了,也就導致了這個新值上溢出了,大家可以看下圖。溢出后,新值在UInt8的承載范圍內的那部分是00000000,也就是0。
2、值的下溢出
- 數值也有可能因為太小而越界。舉個例子:
- UInt8的最小值是0(二進制為00000000)。使用
&-
進行溢出減1,就會得到二進制的11111111即十進制的255。
- Swift代碼是這樣的:
var willUnderflow = UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &- 1
// 此時 willUnderflow 等于 255
- 有符整型也有類似的下溢出,有符整型所有的減法也都是對包括在符號位在內的二進制數進行二進制減法的,這在 "按位左移/右移運算符" 一節提到過。最小的有符整數是-128,即二進制的10000000。用溢出減法減去去1后,變成了01111111,即UInt8所能承載的最大整數127。
- 對應Swift代碼
var signedUnderflow = Int8.min
// signedUnderflow 等于最小的有符整數 -128
signedUnderflow = signedUnderflow &- 1
// 如今 signedUnderflow 等于 127
3、除零溢出
- 一個數除于0
i / 0
,或者對0求余數i % 0
,就會產生一個錯誤。
let x = 1
let y = x / 0
- 使用它們對應的可溢出的版本的運算符
&/
和&%
進行除0操作時就會得到0值。
let x = 1
let y = x &/ 0
// y 等于 0
三、優先級和結合性
- 與數學中的優先級和結合性相同
四、運算符函數
讓已有的運算符也可以對自定義的類和結構進行運算,這稱為運算符重載。
例子中定義了一個名為Vector2D的二維坐標向量 (x,y) 的結構,然后定義了讓兩個Vector2D的對象相加的運算符函數。
struct Vector2D {
var x = 0.0, y = 0.0
}
func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
在這個代碼實現中,參數被命名為了left和right,代表+左邊和右邊的兩個Vector2D對象。函數返回了一個新的Vector2D的對象,這個對象的x和y分別等于兩個參數對象的x和y的和。
這個函數是全局的,而不是Vector2D結構的成員方法,所以任意兩個Vector2D對象都可以使用這個中置運算符。
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
print(v1 + v2) // 打印: Vector2D(x: 4.0, y: 6.0)
2、前置和后置運算符
上個例子演示了一個雙目中置運算符的自定義實現,同樣我們也可以玩標準單目運算符的實現。單目運算符只有一個操作數,在操作數之前就是前置的,如-a; 在操作數之后就是后置的,如i++。
實現一個前置或后置運算符時,在定義該運算符的時候于關鍵字func之前標注
prefix
或postfix
屬性。下面實現
-
取反運算符
prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
- 調用
print(-v1) // 打印: Vector2D(x: -1.0, y: -2.0)
3、組合賦值運算符
- 組合賦值是其他運算符和賦值運算符一起執行的運算。如+=把加運算和賦值運算組合成一個操作
func += (inout left: Vector2D, right: Vector2D) {
left = left + right
}
- 因為加法運算在之前定義過了,這里無需重新定義。所以,加賦運算符函數使用已經存在的高級加法運算符函數來執行左值加右值的運算。
var a = Vector2D(x: 1.0, y: 2.0)
let b = Vector2D(x: 3.0, y: 4.0)
a += b
// a 現在為 (4.0, 6.0)
注意:默認的賦值符(
=
)是不可重載的。只有組合賦值符可以重載。三目條件運算符 (a ? b : c
) 也是不可重載。
4、等價運算符
定義的類和結構體沒有對等價運算符進行默認實現,等價運算符通常被稱為“相等”運算符(
==
)與“不等”運算符(!=
)。對于自定義類型,Swift 無法判斷其是否“相等”,因為“相等”的含義取決于這些自定義類型在你的代碼中所扮演的角色。為了使用等價運算符能對自定義的類型進行判等運算,需要為其提供自定義實現,實現的方法與其它中綴運算符一樣, 并且增加對標準庫
Equatable
協議的遵循:
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}
- 上述代碼實現了“相等”運算符(
==
)來判斷兩個 Vector2D 實例是否相等。對于 Vector2D 類型來說,“相等”意味著“兩個實例的 x 屬性和 y 屬性都相等”,這也是代碼中用來進行判等的邏輯。
示例里同時也實現了“不等”運算符(
!=
),它簡單地將“相等”運算符的結果進行取反后返回。
- 現在我們可以使用這兩個運算符來判斷兩個 Vector2D 實例是否相等:
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印 “These two vectors are equivalent.”
- Swift 為以下自定義類型提等價運算符供合成實現:
只擁有遵循 Equatable 協議存儲屬性的結構體;
只擁有遵循 Equatable 協議關聯類型的枚舉;
沒有關聯類型的枚舉。
在類型原本的聲明中聲明遵循
Equatable
來接收這些默認實現。下面為三維位置向量
(x, y, z)
定義的Vector3D
結構體,與Vector2D
類似,由于x
,y
和z
屬性都是Equatable
類型,Vector3D
就收到默認的等價運算符實現了。
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
// Prints "These two vectors are also equivalent."
五、自定義運算符
- Swift 中還可以聲明和實現自定義運算符。
注意
以下這些標記=
、->
、//
、/*
、*/
、.
、<
(前綴運算符)、&
、?
、?
(中綴運算符)、>
(后綴運算符)、!
、?
是被系統保留的。這些符號不能被重載,也不能用于自定義運算符。
- 新的運算符要使用
operator
關鍵字在全局作用域內進行定義,同時還要指定prefix
、infix
或者postfix
修飾符:
prefix: 前置運算符
infix: 中置運算符
postfix: 后置運算符
prefix operator +++
- 上面的代碼定義了一個新的名為
+++
的前綴運算符。對于這個運算符,在 Swift 中并沒有意義,因此我們針對Vector2D
的實例來定義它的意義。
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 現在的值為 (2.0, 8.0)
// afterDoubling 現在的值也為 (2.0, 8.0)
1、自定義中綴運算符的優先級
每個自定義中綴運算符都屬于某個優先級組。這個優先級組指定了這個運算符和其他中綴運算符的優先級和結合性。
而沒有明確放入優先級組的自定義中綴運算符會放到一個默認的優先級組內,其優先級高于三元運算符。
以下例子定義了一個新的自定義中綴運算符 +-,此運算符屬于 AdditionPrecedence 優先組:
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一個 Vector2D 實例,并且它的值為 (4.0, -2.0)
2、定義優先級別名
- 可以使用下面的方式, 自定義操作符的優先級, 這依賴于已有優先級, 比如下面的
AdditionPrecedence
和MultiplicationPrecedence
// >>>操作符, 優先級別名
infix operator >>> : ATPrecedence
precedencegroup ATPrecedence { //定義運算符優先級ATPrecedence
associativity: left
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
}
- 直接指定操作符的類型,對這個類型進行定義
associativity: left 表示左結合
higherThan 優先級高于 AdditionPrecedence 這個是加法的類型
lowerThan 優先級低于 MultiplicationPrecedence 乘除
- 這里給出常用類型對應的group
infix operator || : LogicalDisjunctionPrecedence
infix operator && : LogicalConjunctionPrecedence
infix operator < : ComparisonPrecedence
infix operator <= : ComparisonPrecedence
infix operator > : ComparisonPrecedence
infix operator >= : ComparisonPrecedence
infix operator == : ComparisonPrecedence
infix operator != : ComparisonPrecedence
infix operator === : ComparisonPrecedence
infix operator !== : ComparisonPrecedence
infix operator ~= : ComparisonPrecedence
infix operator ?? : NilCoalescingPrecedence
infix operator + : AdditionPrecedence
infix operator - : AdditionPrecedence
infix operator &+ : AdditionPrecedence
infix operator &- : AdditionPrecedence
infix operator | : AdditionPrecedence
infix operator ^ : AdditionPrecedence
infix operator * : MultiplicationPrecedence
infix operator / : MultiplicationPrecedence
infix operator % : MultiplicationPrecedence
infix operator &* : MultiplicationPrecedence
infix operator & : MultiplicationPrecedence
infix operator << : BitwiseShiftPrecedence
infix operator >> : BitwiseShiftPrecedence
infix operator ..< : RangeFormationPrecedence
infix operator ... : RangeFormationPrecedence
infix operator *= : AssignmentPrecedence
infix operator /= : AssignmentPrecedence
infix operator %= : AssignmentPrecedence
infix operator += : AssignmentPrecedence
infix operator -= : AssignmentPrecedence
infix operator <<= : AssignmentPrecedence
infix operator >>= : AssignmentPrecedence
infix operator &= : AssignmentPrecedence
infix operator ^= : AssignmentPrecedence
infix operator |= : AssignmentPrecedence