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

優先級和結合性

運算符的優先級使得一些運算符優先於其他運算符;它們會先被執行。

結合性定義了相同優先級的運算符是如何結合的,也就是說,是與左邊結合爲一組,還是與右邊結合爲一組。可以將其理解

爲“它們是與左邊的表達式結合的”,或者“它們是與右邊的表達式結合的”。

當考慮一個複合表達式的計算順序時,運算符的優先級和結合性是非常重要的。舉例來說,運算符優先級解釋了爲什麼下面這個

表達式的運算結果會是 17 。

2+3%4*5                // 結果是 17

如果你直接從左到右進⾏運算,你可能認爲運算的過程是這樣的:

2 +3 =5

5 %4 = 1

1 * 5 =5

但是正確答案是 17 而不是 5 。優先級高的運算符要先於優先級低的運算符進⾏計算。與 C 語言類似,在 Swift 中, 乘法運算符

( * )與取餘運算符( % )的優先級高於加法運算符( + )。因此,它們的計算順序要先於加法運算。

⽽乘法運算與取餘運算的優先級相同。這時爲了得到正確的運算順序,還需要考慮結合性。乘法運算與取餘運算都是左結合的。可

以將這考慮成,從它們的左邊開始爲這兩部分表達式都隱式地加上括號:

2 + ((3 % 4) * 5)

(3 % 4) 等於 3 ,所以表達式相當於:

2 + (3 * 5)

3 * 5 等於 15 ,所以表達式相當於:

2 + 15

因此計算結果爲 17 。

有關 Swift 標準庫提供的操作符信息,包括操作符優先級組和結核性設置的完整列表,請參見《操作符聲明》。

注意

相對 C 語⾔和 Objective-C 來說,Swift 的運算符優先級和結合性規則更加簡潔和可預測。但是,這也意味着它們相較於 C 語⾔

及其衍生語言並不是完全一致。在對現有的代碼進⾏移植的時候,要注意確保運算符的⾏爲仍然符合你的預期。

 

運算符函數

類和結構體可以爲現有的運算符提供自定義的實現。這通常被稱爲運算符重載。

下⾯的例子展示了如何讓自定義的結構體支持加法運算符( + )。算術加法運算符是一個二元運算符,因爲它是對兩個值進⾏運

算,同時它還可以稱爲中綴運算符,因爲它出現在兩個值中間。

例子中定義了一個名爲 Vector2D 的結構體用來表示二維座標向量 (x, y) ,緊接着定義了一個可以將兩個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 分別等於作爲參數的兩個實例的 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

// combinedVector 是一個新的 Vector2D 實例,值爲 (5.0, 5.0)

這個例子實現兩個向量 (3.0,1.0) 和 (2.0,4.0) 的相加,並得到新的向量 (5.0,5.0) 。這個過程如下圖示:

 

前綴和後綴運算符

上個例子演示了一個二元中綴運算符的自定義實現。類與結構體也能提供標準⼀元運算符的實現。⼀元運算符只運算⼀個值。當

運算符出現在值之前時,它就是前綴的(例如 -a ),而當它出現在值之後時,它就是後綴的(例如 b! )。

要實現前綴或者後綴運算符,需要在聲明運算符函數的時候在 func 關鍵字之前指定 prefix 或者 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

// negative 是一個值爲 (-3.0, -4.0) 的 Vector2D 實例

let alsoPositive = -negative

// alsoPositive 是一個值爲 (3.0, 4.0) 的 Vector2D 實例

 

複合賦值運算符

複合賦值運算符將賦值運算符( = )與其它運算符進⾏結合。例如,將加法與賦值結合成加法賦值運算符( += )。在實現的時候,需

要把運算符的左參數設置成 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

// original 的值現在爲 (4.0, 6.0)

注意

不能對默認的賦值運算符( = )進⾏重載。只有複合賦值運算符可以被重載。同樣地,也無法對三元條件運算符 ( a ? b :c ) 進行重

載。

 

等價運算符

通常情況下,自定義的類和結構體沒有對等價運算符進⾏默認實現,等價運算符通常被稱爲相等運算符( == )與不等運算符( != 

)。

爲了使用等價運算符對自定義的類型進⾏判等運算,需要爲“相等”運算符提供自定義實現,實現的方法與其它中綴運算符一樣,

並且增加對標準庫 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 爲您提供的等價運算符默認實現。Swift 爲以下數種自定義類型提供等價運算符的默認實現:

    1.只擁有存儲屬性,並且它們全都遵循 Equatable 協議的結構體

    2.只擁有關聯類型,並且它們全都遵循 Equatable 協議的枚舉

    3.沒有關聯類型的枚舉

在類型原始的聲明中聲明遵循 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.")

}

// 打印“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

// toBeDoubled 現在的值爲 (2.0, 8.0)

// afterDoubling 現在的值也爲 (2.0, 8.0)

 

自定義中綴運算符的優先級

每個自定義中綴運算符都屬於某個優先級組。優先級組指定了這個運算符相對於其他中綴運算符的優先級和結合性。《優先級和

結合性》中詳細闡述了這兩個特性是如何對中綴運算符的運算產生影響的。

而沒有明確放入某個優先級組的自定義中綴運算符將會被放到一個默認的優先級組內,其優先級高於三元運算符。 以下例子定義

了一個新的自定義中綴運算符 +- ,此運算符屬於 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)

這個運算符把兩個向量的 x 值相加,同時從第一個向量的 y 中減去第二個向量的 y 。因爲它本質上是屬於“相加型”運算符,所以

將它放置在 + 和 - 等默認中綴“相加型”運算符相同的優先級組中。關於 Swift 標準庫提供的運算符,以及完整的運算符優先級組

和結合性設置,請參考《運算符聲明》。⽽更多關於優先級組以及自定義操作符和優先級組的語法,請參考《運算符聲明》。

注意

當定義前綴與後綴運算符的時候,我們並沒有指定優先級。然而,如果對同一個值同時使⽤前綴與後綴運算符,則後綴運算符會

先參與運算。

 

 

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