Swift編程二十八(高級運算符)

案例代碼下載

高級運算符

除了基本運算符中描述的運算符之外,Swift還提供了幾個執行更復雜值操作的高級運算符。這些包括C和Objective-C中熟悉的所有按位和位移運算符。

與C中的算術運算符不同,Swift中的算術運算符默認不會溢出。溢出行為被捕獲并報告為錯誤。要選擇溢出行為,請使用Swift默認溢出的第二組算術運算符,例如溢出加法運算符(&+)。所有這些溢出運算符都以&符號開頭。

定義自己的結構,類和枚舉時,為這些自定義類型提供自己的標準Swift運算符實現會很有用。Swift可以輕松地為這些運算符提供定制的實現,并確切地確定它們對創建的每種類型的行為。

不僅限于預定義的運算符。Swift為提供了自定義中綴,前綴,后綴和賦值運算符的自由,具有自定義優先級和關聯性。這些運算符可以像任何預定義的運算符一樣在代碼中使用和采用,甚至可以擴展現有類型以支持定義的自定義運算符。

按位運算符

按位運算符可以處理數據結構中的各個原始數據位。它們通常用于低級編程,例如圖形編程和設備驅動程序創建。當使用來自外部源的原始數據時,按位運算符也很有用,例如編碼和解碼數據以通過自定義協議進行通信。

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

按位NOT運算符

該位NOT運算符(~)反轉數所有位:


image

按位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:


image

在下面的例子中,firstSixBits和lastSixBits兩個值具有四個中間位等于1。按位AND運算符將它們組合起來以產生數字00111100,該數字等于無符號十進制值60:

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits

按位OR運算符

按位或運算符(|)對兩個數的二進制位進行比較。運算符返回一個新數字,如果任一輸入數的位等于1其位設置為1:


image

在下面的例子中,someBits和moreBits的值具有不同的位設置為1。按位OR運算符將它們組合起來以產生數字11111110,該數字等于無符號數254:

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits

按位異或運算符

的按位異或運算符,或“異或運算符”( ^),比較兩個數的位。運算符返回一個新數字,輸入位不同其位設置為1,輸入位相同其位設置為0:


image

在下面的示例中,firstBits和otherBits一個值位為1,而另一個不是,按位異或運算符這個位設置1為其輸出值。firstBits和otherBits所有其他位相同在輸出值中設置為0:

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits

按位左右移位運算符

在按位左移位運算符(<<)和按位右移位運算符(>>)中的數向左或向移動一個數的所有特定數量的位,根據下面定義的規則。

按位左右移位具有將整數乘以或除以因子2的效果。將整數位向左移動一個位置會使其值加倍,而將其向右移動一個位置會使其值減半。

無符號整數的移位行為

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

  1. 現有位按所請求的位數向左或向右移動。
  2. 移除超出整數存儲邊界的任何位都將被丟棄。
  3. 在原始位向左或向右移動之后,將零插入到留下的空間中。

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

下圖顯示了11111111 << 1(11111111按位向左移動1)和11111111 >> 1(11111111按位向右移動1)的結果。移動藍色數字,丟棄灰色數字,并插入橙色零:


image

以下是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位數:


image

符號位是0(意思是“正”),七個值位是用二進制表示法寫的數字4。

但是,負數以不同方式存儲。它們的存儲方式是將它們的絕對值減去2的n次方,其中n是值的位數。一個八比特數有七個值的位,所以這意味著2的7次方,即128。

以下是Int8查找數字的位數-4:


image

這一次,符號位是1(意思是“負”),七個值位的二進制值為124(即128 - 4):


image

負數的這種編碼稱為二進制補碼表示。這似乎是一種代表負數的不尋常方式,但它有幾個優點。

首先,可以-1加-4,簡單地通過執行一個標準二進制加法全部八個位(包括符號位),并丟棄任何不適合8位,一旦完成:


image

其次,二進制補碼表示還可以將負數位向左和向右移動,就像正數一樣,并且對于向左移動的每一個移位最終都會將它們加倍,或者對于向右移動的每個位將它們減半。 。當有符號整數右移,適用無符號整數相同的規則,但在剩下的填充任何空位:要做到這一點,整數右移使用符號位補位,而不是零。


image

此操作可確保有符號整數在向右移動后具有相同的符號,并稱為算術移位。

由于存儲正數和負數的特殊方式,將它們中的任何一個向右移動都會使它們接近零。在此移位期間保持符號位相同意味著負整數向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為零。


image

當允許無符號整數向負方向溢出時會發生類似情況。這是使用溢出減法運算符(&-)的示例:

var unsignedOverflow = UInt8.min
unsignedOverflow = unsignedOverflow &- 1

UInt8可容納的最小值為零或00000000二進制。如果使用溢出減法運算符(&-)從00000000減去1,這個數字將溢出并環繞到11111111,或十進制255。


image

對于有符號整數,也會發生溢出。有符號整數的所有加法和減法以按位方式執行,符號位作為加數或減數的一部分包括在內,如按位左右移位運算符中所述。

var signedOverflow = Int8.min
signedOverflow = signedOverflow &- 1

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


image

對于有符號和無符號整數,正方向上的溢出從最大有效整數值回到最小值,而負方向上的溢出從最小值回到最大值。

優先級和相關性

運算符優先級使某些運算符的優先級高于其他, 那么首先計數優先級高的運算符。

運算符關聯性定義了相同優先級的運算符如何組合在一起 - 從左側分組,或從右側分組。可以把它想象成“他們與左邊的表達聯系起來”或“他們將表達聯系到他們的右邊”。

在計算復合表達式的順序時,考慮每個運算符的優先級和關聯性非常重要。例如,運算符優先級解釋了以下表達式等于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)


image

前綴和后綴運算符

上面顯示的示例演示了二元中綴運算符的自定義實現。類和結構還可以提供標準一元運算符的實現。一元運算符在單個目標上運行。如果它們位于其目標(如)之前,則它們是前綴,如果它們在目標之后(例如-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標準庫提供的運算符的信息,包括運算符優先級組和關聯性設置的完整列表,請參閱運算符聲明。有關優先級組的更多信息以及定義自己的運算符和優先級組的語法,請參閱“ 運算符聲明”。

注意

定義前綴或后綴運算符時,不指定優先級。但是,如果將前綴和后綴運算符同時應用于同一操作數,則首先應用后綴運算符。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容