Swift 運算符重載簡介
在任何一門計算機編程語言中,運算符重載都是非常強大的特性之一,因此蘋果決定為 Swift 也提供這一機制。然而,”能力越強責任越大”。利用運算符重載你很容易實現一些奇怪的場景,例如用減法運算符實現兩數相加,或者用乘法運算符實現兩數相除,但這顯然都不是你希望出現的。
在任何一門計算機編程語言中,運算符重載都是非常強大的特性之一,因此蘋果決定為 Swift 也提供這一機制。然而,”能力越強責任越大”。利用運算符重載你很容易實現一些奇怪的場景,例如用減法運算符實現兩數相加,或者用乘法運算符實現兩數相除,但這顯然都不是你希望出現的。
好了,閑話少敘 —— 讓我們看看運算符重載究竟是怎么一回事。
挑戰
這一小節的任務很簡單:擴展乘法運算符的標準功能,使其適用于字符串。你將會用到字符串拼接運算符,想象一下這種用法:
"abc" * 5 = "abc" + "abc" + "abc" + "abc" + "abc" = "abcabcabcabcabc"
正式編碼之前,思考一下應該怎么做,分幾步來實現。我的做法是這樣的:
- 定義變量
result
并進行初始化 —— 默認字符串。 - 從 2 開始循環,一直到待拼接的字符串數目終止,每次迭代只做一件事 —— 把字符串拼接到
result
末尾。 - 打印
result
。
算法大致就是這樣,接下來讓我們付諸實踐。
基本運算符重載
啟動 Xcode 并新建一個 playground 文件。刪除原有代碼,添加乘法運算符的函數原型:
func *(lhs: String, rhs: Int) -> String {
}</code></pre>
函數有兩個參數 —— 左操作數是 String
類型,右操作數是 Int
類型,函數返回類型為 String
。
函數體內應該完成三件事。首先,定義 result
變量并初始化為函數的 String
參數 —— 這是一個變量,稍后會修改它的值。
var result = lhs
接下來使用 for in
控制流語句及閉區間運行符從 2 開始循環,直到函數的 Int
參數時為止:
for _ in 2...rhs {
}</code></pre>
注意:這里使用了 _
作為通配符,因為我們希望忽略序列的具體值 —— 關于循環的更多說明可以看這里。
循環體內只有一個任務 —— 更新 result
:
result += lhs
注意:你也可以按如下方式來寫 —— 上邊這種寫法更短,是因為用了加法復合運算符。
result = result + lhs
最后返回 result:
return result
現在我們直接使用運算符:
let u = "abc"
let v = u * 5
搞定了!只是還有一個問題 —— 你只能將其用于字符串,那其它類型的數據怎么辦?我們使用范型運算符來完善。
泛型運算符

泛型默認是不支持運算符的,所以需要協議來支持。向 playground 中添加協議原型:
protocol Type {
}</code></pre>
現在向協議中添加加法復合運算符函數的原型:
func +=(inout lhs: Self, rhs: Self)
函數擁有左右操作數,并且都設置為 Self
類型 —— 這是一種巧妙的方式,說明二者的類型都是實現了該協議的類。左操作數標記為inout
,因為它的值是要被修改并且最后被函數返回的。
或者,你也可以定義加法運算符的函數原型:
func +(lhs: Self, rhs: Self) -> Self
函數擁有 Self
類型的左右操作數,并且加法運算的返回結果也是 Self
。這種情況下就不需要使用 inout
參數了。
接下來,為 String
, Int
, Double
, Float
等實現了 Type
協議的類型創建擴展。
extension String: Type {}
extension Int: Type {}
extension Double: Type {}
extension Float: Type {}
注意:這些擴展的實現是空的,因為你并不打算為默認類型添加任何東西,僅僅是要讓他們遵循 Type
協議。
現在向 playground 中添加乘法操作符函數原型:
func *<T: Type>(lhs: T, rhs: Int) -> T {
}</code></pre>
函數有兩個參數,左操作數是 T
類型,右操作數是 Int
類型,函數返回類型為 T
。利用類型約束使 T
類型遵循Type
協議,這樣它就可以使用加法復合運算符了。
注意:你可以使用 where
關鍵字定義類型約束——盡管上邊的方法更簡短:
func *<T where T: Type>(lhs: T, rhs: Int) -> T
函數的實現跟之前一樣:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result</code></pre>
注意:可以使用加法操作符替代,但要確保它的函數原型添加到了協議中。
測試一下:
let x = "abc"
let y = x * 5
let a = 2
let b = a * 5
let c = 3.14
let d = c * 5
let e: Float = 4.56
let f = e * 5</code></pre>
搞定了!不過有一個問題:你使用的是標準乘法運算符,這個可能造成歧義。如果換成其它運算符會更好。接下來我們試著用自定義運算符解決這個問題。
自定義運算符

首先添加下面一行到 playground:
infix operator ** {associativity left precedence 150}
一步一步解釋:
- 自定義乘法運算符的名稱是 **。
- 類型是
中綴運算符(infix)
因為它有兩個操作數。
- 運算順序從左至右,因此是左結合。
- 優先級設置為 150 —— 與標準乘法運算符相同,因為它是高優先級運算符。
注意:關于運算符優先級和結合性的更多說明可以看這里。
自定義運算符的函數原型與標準運算符類似 —— 只有函數名不同:
func **<T: Type>(lhs: T, rhs: Int) -> T {
}</code></pre>
函數實現跟之前完全一樣:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result</code></pre>
測試一下:
let g = "abc"
let h = g ** 5
let i = 2
let j = i ** 5
let k = 3.14
let l = k ** 5
let m: Float = 4.56
let n = m 5</code></pre>
搞定了!還有一個問題——運算符的復合類型還沒有定義,接下來我們解決這個問題:
復合運算符

復合運算符的類型、優先級和結合性和之前一樣 —— 只有名稱不同:
infix operator
= {associativity left precedence 150}</code></pre>
接著向 playground 添加復合運算符的函數原型:
func **=<T: Type>(inout lhs: T, rhs: Int) {
}</code></pre>
函數沒有返回類型,因為左操作數被標記為 inout
。
函數體只做一件事 —— 運用之前的自定義運算符返回乘法結果:
lhs = lhs ** rhs
測試一下:
var o = "abc"
o **= 5
var q = 2
q **= 5
var s = 3.14
s **= 5
var w: Float = 4.56
w **= 5</code></pre>
搞定了!這已經是最簡版本了!
總結
如果謹慎地使用運算符重載,它便能夠發揮強大的功能 —— 我希望你能在自己的項目中找到合適使用的方法。
作為參考,這里有一個完整版的 Playground,我已經在 Xcode 7.3 用 Swift 2.2 測試過了。
如果你對本教程或者運算符重載有什么看法的話可以給我留言。
本文作者:Cosmin Pup?z? 本文譯者:zltunes
外文鏈接:An Introduction to Operator Overloading in Swift
本文轉自:SwiftGG翻譯組
致謝:連環畫是在 MakeBeliefsComix.com 制作的。