Swift 運算符重載簡介

aoc_sheng 8年前發布 | 8K 次閱讀 Apple Swift開發 Swift

在任何一門計算機編程語言中,運算符重載都是非常強大的特性之一,因此蘋果決定為 Swift 也提供這一機制。然而,”能力越強責任越大”。利用運算符重載你很容易實現一些奇怪的場景,例如用減法運算符實現兩數相加,或者用乘法運算符實現兩數相除,但這顯然都不是你希望出現的。

Swift 運算符重載簡介

 

 

在任何一門計算機編程語言中,運算符重載都是非常強大的特性之一,因此蘋果決定為 Swift 也提供這一機制。然而,”能力越強責任越大”。利用運算符重載你很容易實現一些奇怪的場景,例如用減法運算符實現兩數相加,或者用乘法運算符實現兩數相除,但這顯然都不是你希望出現的。

好了,閑話少敘 —— 讓我們看看運算符重載究竟是怎么一回事。

挑戰

這一小節的任務很簡單:擴展乘法運算符的標準功能,使其適用于字符串。你將會用到字符串拼接運算符,想象一下這種用法:

"abc" * 5 = "abc" + "abc" + "abc" + "abc" + "abc" = "abcabcabcabcabc"

正式編碼之前,思考一下應該怎么做,分幾步來實現。我的做法是這樣的:

  • 定義變量 result 并進行初始化 —— 默認字符串。
  • 從 2 開始循環,一直到待拼接的字符串數目終止,每次迭代只做一件事 —— 把字符串拼接到 result 末尾。
  • 打印 result

算法大致就是這樣,接下來讓我們付諸實踐。

基本運算符重載

Swift 運算符重載簡介
啟動 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

搞定了!只是還有一個問題 —— 你只能將其用于字符串,那其它類型的數據怎么辦?我們使用范型運算符來完善。

泛型運算符

Swift 運算符重載簡介
泛型默認是不支持運算符的,所以需要協議來支持。向 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>

搞定了!不過有一個問題:你使用的是標準乘法運算符,這個可能造成歧義。如果換成其它運算符會更好。接下來我們試著用自定義運算符解決這個問題。

自定義運算符

Swift 運算符重載簡介

首先添加下面一行到 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>

搞定了!還有一個問題——運算符的復合類型還沒有定義,接下來我們解決這個問題:

復合運算符

Swift 運算符重載簡介
復合運算符的類型、優先級和結合性和之前一樣 —— 只有名稱不同:

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 制作的。

 本文由用戶 aoc_sheng 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!