Swift 4.0 編程語言(九)

139.優先級和關聯性


運算符優先級給一些運算符提供更高的優先級; 這些運算符首先起作用。

運算符關聯性確定相同優先級的運算符分組的行為—或者從左邊分組, 或者從右邊分組。可以這樣考慮 “它們關聯左邊的表達式,” 或者 “它們關聯右邊的表達式。”

在使用復合表達式計算時,考慮每個運算符的優先級和關聯性非常重要。例如, 運算符優先級解釋了為什么下面的表達式結果是 17.

2 + 3 % 4 * 5

// 等于 17

如果從左到右嚴格讀, 你可能期望表達式按照如下執行:

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 標準庫運算符參考。

備注 Swift 的運算符優先級和關聯性,比 C 和 Objective-C 中的更加簡單和可預測。不過, 這個意味著它們和基于C語言的語言有所不同。在移植代碼到 Swift 時,謹慎的確保運算符行為要和你想的一樣。

140.運算符方法

類和結構體可以為已存在的運算符提供自己的實現。這就是運算符的重載。

下面的例子展示如何給一個自定義的結構體,實現一個算術加法運算符(+). 算術加法運算符是一個二目運算符,因為它操作兩個目標。因為它在兩個目標之間,它又是一個中綴運算符。

下面的例子為一個二維位置向量(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 類型的值。

在這個實現里, 輸入參數名 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 關鍵字前寫上前后綴修飾符,就可以定義前后綴一元運算符:

extension Vector2D {

static prefix func - (vector: Vector2D) -> Vector2D {

return Vector2D(x: -vector.x, y: -vector.y)

?}

}

上面的例子給Vector2D 實例實現了一元-運算符。一元-運算符是一個前綴運算符, 所以這個方法使用了前綴修飾符。

對于簡單的數值, 一元-運算符把正數變成負數,反之亦然。Vector2D 實例的實現就是對 x 和 y 同時進行操作:

let positive = Vector2D(x: 3.0, y: 4.0)

let negative = -positive

// negative is a Vector2D instance with values of (-3.0, -4.0)

let alsoPositive = -negative

// alsoPositive is a Vector2D instance with values of (3.0, 4.0)

復合賦值運算符

復合賦值運算符把=與其他運算合并。例如, 加法賦值運算符合并了加法和賦值到一個單獨的操作中。把復合賦值運算符的左邊輸入參數標記為 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) 也不能重載。

等式運算符

自定義的類和結構體沒有默認的等式運算符的實現, 也就是(==) 和 (!=). Swift 猜不出來你的自定義類型是否有資格 “等于”, 因為這取決于這些類型在你代碼里扮演的角色。

使用等式運算符來判斷自定類型的相等, 像其他中綴運算符方法一樣提供它的實現即可:

extension Vector2D {

static func == (left: Vector2D, right: Vector2D) -> Bool {

return (left.x == right.x) && (left.y == right.y)

?}

static func != (left: Vector2D, right: Vector2D) -> Bool {

return !(left == right)

?}

}

上面的例子實現了“等于” 運算符 (==)來判斷兩個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 提供的標準運算符,你也可以聲明和實現自定義的運算符。

使用 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 是一個值為 (4.0, -2.0)的 Vector2D 實例

這個運算符把兩個向量的x值相加, 然后把第二個向量的兩個y值相減。因為它實際上是一個 “加法的” 運算符, 它跟其他加法中綴運算符,例如 + 和 - , 有相同的優先級集。

備注 定義一個前綴或者后綴運算符的時候,你不要指定優先級。不過, 如果你在相同的操作數上使用前綴和后綴運算符, 后綴運算符會首先發生作用。

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

推薦閱讀更多精彩內容