案例代碼下載
高級運算符
除了基本運算符中描述的運算符之外,Swift還提供了幾個執行更復雜值操作的高級運算符。這些包括C和Objective-C中熟悉的所有按位和位移運算符。
與C中的算術運算符不同,Swift中的算術運算符默認不會溢出。溢出行為被捕獲并報告為錯誤。要選擇溢出行為,請使用Swift默認溢出的第二組算術運算符,例如溢出加法運算符(&+)。所有這些溢出運算符都以&符號開頭。
定義自己的結構,類和枚舉時,為這些自定義類型提供自己的標準Swift運算符實現會很有用。Swift可以輕松地為這些運算符提供定制的實現,并確切地確定它們對創建的每種類型的行為。
不僅限于預定義的運算符。Swift為提供了自定義中綴,前綴,后綴和賦值運算符的自由,具有自定義優先級和關聯性。這些運算符可以像任何預定義的運算符一樣在代碼中使用和采用,甚至可以擴展現有類型以支持定義的自定義運算符。
按位運算符
按位運算符可以處理數據結構中的各個原始數據位。它們通常用于低級編程,例如圖形編程和設備驅動程序創建。當使用來自外部源的原始數據時,按位運算符也很有用,例如編碼和解碼數據以通過自定義協議進行通信。
Swift支持C中的所有按位運算符,如下所述。
按位NOT運算符
該位NOT運算符(~)反轉數所有位:
按位NOT運算符是一個前綴運算符,它出現在它操作的值之前,沒有任何空格:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits
UInt8整數有八位,可以存儲0和255之間的任何值。此示例使用二進制值初始化一個UInt8整數,該二進制值00001111的前四位設置為0,其后四位設置為1。這相當于十進制值15。
然后使用按位NOT運算符創建一個新的常量invertedBits,該常量等于initialBits,但所有位都被反轉。0成為1,1成為0。invertedBitsis的值11110000等于無符號十進制值240。
按位AND運算符
按位AND運算符(&)結合了兩個數字的位數。它返回一個新的號碼,僅當位兩個輸入數字在其位等于1才被設置為1:
在下面的例子中,firstSixBits和lastSixBits兩個值具有四個中間位等于1。按位AND運算符將它們組合起來以產生數字00111100,該數字等于無符號十進制值60:
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits
按位OR運算符
按位或運算符(|)對兩個數的二進制位進行比較。運算符返回一個新數字,如果任一輸入數的位等于1其位設置為1:
在下面的例子中,someBits和moreBits的值具有不同的位設置為1。按位OR運算符將它們組合起來以產生數字11111110,該數字等于無符號數254:
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits
按位異或運算符
的按位異或運算符,或“異或運算符”( ^),比較兩個數的位。運算符返回一個新數字,輸入位不同其位設置為1,輸入位相同其位設置為0:
在下面的示例中,firstBits和otherBits一個值位為1,而另一個不是,按位異或運算符這個位設置1為其輸出值。firstBits和otherBits所有其他位相同在輸出值中設置為0:
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits
按位左右移位運算符
在按位左移位運算符(<<)和按位右移位運算符(>>)中的數向左或向移動一個數的所有特定數量的位,根據下面定義的規則。
按位左右移位具有將整數乘以或除以因子2的效果。將整數位向左移動一個位置會使其值加倍,而將其向右移動一個位置會使其值減半。
無符號整數的移位行為
無符號整數的位移行為如下:
- 現有位按所請求的位數向左或向右移動。
- 移除超出整數存儲邊界的任何位都將被丟棄。
- 在原始位向左或向右移動之后,將零插入到留下的空間中。
這種方法被稱為邏輯轉換。
下圖顯示了11111111 << 1(11111111按位向左移動1)和11111111 >> 1(11111111按位向右移動1)的結果。移動藍色數字,丟棄灰色數字,并插入橙色零:
以下是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
此示例使用一個UInt32常量叫做pink來存儲顏色層疊樣式表的粉紅色顏色值。CSS顏色值#CC6699寫0xCC6699在Swift的十六進制數字表示中。然后通過按位AND運算符(&)和按位右移運算符(>>)將此顏色分解為其紅色(CC),綠色(66)和藍色(99)分量。
通過在數字0xCC6699和0xFF0000之間執行按位AND來獲得紅色分量。0xFF0000的0有效地“掩蓋”0xCC6699第二個和第三個字節,導致6699被忽略返回0xCC0000。
然后將此數字向右移動16位(>> 16)。十六進制數中的每對字符使用8位,因此向右移動16位將0xCC0000轉換為0x0000CC。這與0xCC相同十進制值204。
類似地,綠色成分通過0xCC6699和0x00FF00之間執行按位與數字獲得,其給出的輸出值0x006600。然后將此輸出值向右移動8位,給出一個值0x66,其值為十進制值102。
最后,將藍色成分是通過數字0xCC6699和0x0000FF之間執行按位與獲得,其給出的輸出值0x000099。沒有必要將它向右移動,因為0x000099等于0x99,其十進制值為153。
有符號整數的移位行為
對于有符號整數而言,移位行為比無符號整數更復雜,因為有符號整數用二進制表示。(為簡單起見,下面的示例基于8位有符號整數,但相同的原則適用于任何大小的有符號整數。)
有符號整數使用它們的第一位(稱為符號位)來指示整數是正還是負。符號位0表示正數,符號1表示負數。
其余位(稱為值位)存儲實際值。正數以與無符號整數完全相同的方式存儲,從0向上計數。以下是數字4的Int8位數:
符號位是0(意思是“正”),七個值位是用二進制表示法寫的數字4。
但是,負數以不同方式存儲。它們的存儲方式是將它們的絕對值減去2的n次方,其中n是值的位數。一個八比特數有七個值的位,所以這意味著2的7次方,即128。
以下是Int8查找數字的位數-4:
這一次,符號位是1(意思是“負”),七個值位的二進制值為124(即128 - 4):
負數的這種編碼稱為二進制補碼表示。這似乎是一種代表負數的不尋常方式,但它有幾個優點。
首先,可以-1加-4,簡單地通過執行一個標準二進制加法全部八個位(包括符號位),并丟棄任何不適合8位,一旦完成:
其次,二進制補碼表示還可以將負數位向左和向右移動,就像正數一樣,并且對于向左移動的每一個移位最終都會將它們加倍,或者對于向右移動的每個位將它們減半。 。當有符號整數右移,適用無符號整數相同的規則,但在剩下的填充任何空位:要做到這一點,整數右移使用符號位補位,而不是零。
此操作可確保有符號整數在向右移動后具有相同的符號,并稱為算術移位。
由于存儲正數和負數的特殊方式,將它們中的任何一個向右移動都會使它們接近零。在此移位期間保持符號位相同意味著負整數向0移動保持為負值。
溢出運算符
如果嘗試將數字插入到不能保存該值的整數常量或變量中,默認情況下Swift會報告錯誤,而不是允許創建無效值。當處理太大或太小的數字時,此行為可提供額外的安全性。
例如,Int16整數類型可以包含-32768和32767之間的任何有符號整數。嘗試將Int16常量或變量設置為此范圍之外的數字會導致錯誤:
var potentialOverflow = Int16.max
potentialOverflow += 1
當值變得太大或太小時提供錯誤處理,在編碼邊界值條件時提供更大的靈活性。
但是,如果特別希望溢出條件截斷可用位數,則可以選擇此行為而不是觸發錯誤。Swift提供了三個算術溢出運算符,它們選擇加入整數計算的溢出行為。這些運算符都以&符號開頭&:
- 溢出加法(&+)
- 溢出減法(&-)
- 溢出乘法(&*)
值溢出
數字可以在正方向和負方向上溢出。
下面是使用overflow溢出加法(&+)允許無符號整數向正方向溢出時會發生什么的示例:
var unsignedOverflow = UInt8.max
unsignedOverflow = unsignedOverflow &+ 1
變量unsignedOverflow初始化為UInt8可以容納的最大值(255或二進制11111111)。然后使用溢出加法運算符(&+)遞增1。這使得它的二進制表示超出了UInt8可以容納的大小,導致它溢出超出其邊界,如下圖所示。UInt8溢出添加后保留在范圍內的值00000000為零。
當允許無符號整數向負方向溢出時會發生類似情況。這是使用溢出減法運算符(&-)的示例:
var unsignedOverflow = UInt8.min
unsignedOverflow = unsignedOverflow &- 1
UInt8可容納的最小值為零或00000000二進制。如果使用溢出減法運算符(&-)從00000000減去1,這個數字將溢出并環繞到11111111,或十進制255。
對于有符號整數,也會發生溢出。有符號整數的所有加法和減法以按位方式執行,符號位作為加數或減數的一部分包括在內,如按位左右移位運算符中所述。
var signedOverflow = Int8.min
signedOverflow = signedOverflow &- 1
Int8最小值為-128或二進制10000000。使用溢出運算符從此二進制數中減去1得到二進制01111111,該值將切換符號位得出正數127,Int8即可容納的最大正值。
對于有符號和無符號整數,正方向上的溢出從最大有效整數值回到最小值,而負方向上的溢出從最小值回到最大值。
優先級和相關性
運算符優先級使某些運算符的優先級高于其他, 那么首先計數優先級高的運算符。
運算符關聯性定義了相同優先級的運算符如何組合在一起 - 從左側分組,或從右側分組。可以把它想象成“他們與左邊的表達聯系起來”或“他們將表達聯系到他們的右邊”。
在計算復合表達式的順序時,考慮每個運算符的優先級和關聯性非常重要。例如,運算符優先級解釋了以下表達式等于17的原因。
2 + 3 % 4 * 5
如果從左到右嚴格閱讀,可能表達式計算如下:
- 2加上3等于5
- 5余數4等于1
- 1時間5等于5
但是,實際答案17不是5。優先級較高的運算符在優先級較低的運算符之前進行求值。在Swift中,與在C中一樣,余數運算符(%)和乘法運算符(*)的優先級高于加法運算符(+)。結果是在加法之前計數它們。
但是,余數和乘法具有相同的優先級。要得出確切計算順序,還需要考慮它們的相關性。余數和乘法都與左邊的表達式相關聯。可以想象這是在表達式的這些部分周圍添加隱式括號,從左邊開始:
2 + ((3 % 4) * 5)
(3 % 4)是的3,所以這相當于:
2 + (3 * 5)
(3 * 5)是的15,所以這相當于:
2 + 15
這個計算得出了最終答案17。
有關Swift標準庫提供的運算符的信息,包括運算符優先級組和相關性設置的完整列表,請參閱運算符聲明。
注意
Swift的運算符優先級和關聯性規則比C和Objective-C中的更簡單,更可預測。但是,這意味著它們與基于C的語言不完全相同。在將現有代碼移植到Swift時,請務必確保運算符交互的行為方式仍然符合預期。
運算符方法
類和結構可以提供自己的現有運算符實現。這稱為重載現有運算符。
下面的示例顯示了如何為自定義結構實現算術加法運算符(+)。算術加法運算符是一個二元運算符,因為它在兩個目標上運行,并且被稱為中綴,因為它出現在這兩個目標之間。
該示例定義了二維位置向量(x, y)的結構Vector2D,然后定義了將Vector2D結構實例相加的運算符方法:
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
運算符方法被定義為Vector2D的一個類型方法,其方法名稱與要重載的運算符(+)匹配。因為加法不是向量的基本行為的一部分,所以類型方法是在擴展Vector2D而不是在主結構聲明中定義的Vector2D。因為算術加法運算符是二元運算符,所以此運算符方法接受兩個Vector2D類型的輸入參數,并返回單個輸出值,也是類型Vector2D。
在此實現中,輸入參數被命名left和right表示將位于+運算符左側和右側的Vector2D實例。該方法返回一個新Vector2D實例,其x和y屬性被從兩個Vector2D實例的x和y屬性加到一起的總和初始化。
類型方法可以用作現有Vector2D實例之間的中綴運算符:
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
此示例將向量加在一起并生成向量,如下所示。(3.0, 1.0)(2.0, 4.0)(5.0, 5.0)
前綴和后綴運算符
上面顯示的示例演示了二元中綴運算符的自定義實現。類和結構還可以提供標準一元運算符的實現。一元運算符在單個目標上運行。如果它們位于其目標(如)之前,則它們是前綴,如果它們在目標之后(例如-a),則它們是后綴運算符(如b!)。
通過在聲明運算符方法時在func前寫入關鍵字prefixor、postfix修飾符來實現前綴或后綴一元運算符:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
上面的示例為Vector2D實例實現了一元負運算符(-a)。一元負運算符是前綴運算符,因此必須使用prefix修飾符限定此方法。
對于簡單的數值,一元負運算符將正數轉換為負數,反之亦然。Vector2D實例的相應實現對x和y屬性執行此操作:
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
let alsoPositive = -negative
復合賦值運算符
復合賦值運算符將(=)與另一個運算符相結合。例如,加法賦值運算符(+=)將加法和賦值組合到單個操作中。將復合賦值運算符的左輸入參數類型標記為inout,因為參數的值將直接從運算符方法中修改。
下面的示例為Vector2D實例實現了一個加法賦值運算符方法:
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
由于之前已定義了加法運算符,因此無需在此處重新實現加法過程。相反,加法賦值運算符方法利用現有的加法運算符方法,并使用它將左值設置為左值加右值:
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
注意
無法重載默認賦值運算符(=)。只有復合賦值運算符才能重載。類似地,三元條件運算符(a ? b : c)不能重載。
等價運算符
默認情況下,自定義類和結構沒有等價運算符的實現,稱為等于運算符( == )和不等于 運算符( != )。通常實現運算符==,并使用標準庫的運算符 != 來否定運算符 == 的結果的默認實現。有兩種方法可以實現==運算符:可以自己實現它,或者對于許多類型,可以讓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.")
}
在許多簡單的情況下,可以讓Swift為提供等價運算符的綜合實現。Swift為以下類型的自定義類型提供了綜合實現:
- 僅存儲遵守Equatable協議的屬性的結構
- 只包含遵守Equatable協議的關聯類型的枚舉
- 沒有關聯類型的枚舉
要接收綜合實現的==,在包含原始聲明的文件中聲明遵守Equatable,而不是自己實現==操作符。
下面的示例定義了三維位置矢量(x, y, z)的結構Vector3D,類似于結構Vector2D。因為x,y和z屬性都是Equatable類型,所以接收綜合的等價運營商的實現。
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.")
}
自定義運算符
除了Swift提供的標準運算符之外,還可以聲明并實現自己的自定義運算符。有關可用于定義自定義運算符的字符列表,請參閱運算符。
新的運算符使用operator關鍵字全局聲明,并且都標有prefix,infix或postfix修飾:
prefix operator +++
上面的示例定義了一個名為的新前綴運算符+++。此運算符在Swift中沒有現有含義,因此在使用Vector2D實例的特定上下文中給出了它自己的自定義含義。出于此示例的目的,+++將其視為新的“前綴加倍”運算符。它通過使用前面定義的加法賦值運算符將向量與自身相加,使Vector2D實例的值x和y值加倍。為了實現+++運算符,添加一個名為+++的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
自定義中綴運算符的優先級
自定義中綴運算符均需指定優先級組。優先級組指定運算符相對于其他中綴運算符的優先級,以及運算符的關聯性。有關這些特征如何影響中綴運算符與其他中綴運算符的交互的說明,請參閱優先級和關聯性。
未明確放入優先級組的自定義中綴運算符將被賦予默認優先級組,其優先級即高于三元條件運算符的優先級。
以下示例定義了一個名為+-的新自定義中綴運算符,該運算符屬于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
該運算符將兩個向量的x值相加,并從第一個向量中減去第二個向量的y值。因為它本質上是一個“加法”運算符,所以它被賦予了與加法中綴運算符相同的優先級組,例如+和-。有關Swift標準庫提供的運算符的信息,包括運算符優先級組和關聯性設置的完整列表,請參閱運算符聲明。有關優先級組的更多信息以及定義自己的運算符和優先級組的語法,請參閱“ 運算符聲明”。
注意
定義前綴或后綴運算符時,不指定優先級。但是,如果將前綴和后綴運算符同時應用于同一操作數,則首先應用后綴運算符。