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值相減。因為它實際上是一個 “加法的” 運算符, 它跟其他加法中綴運算符,例如 + 和 - , 有相同的優先級集。
備注 定義一個前綴或者后綴運算符的時候,你不要指定優先級。不過, 如果你在相同的操作數上使用前綴和后綴運算符, 后綴運算符會首先發生作用。