Swift教程之運算子多載

來源:互聯網
上載者:User

標籤:

http://blog.csdn.net/mengxiangyue/article/details/43437797

原文地址:http://www.raywenderlich.com/80818/operator-overloading-in-swift-tutorial Corinne Krych  譯者:孟祥月 blog:http://blog.csdn.net/mengxiangyue

這篇文章是本人第一次翻譯,難免有錯誤,翻譯的時候使用的是txt,所以格式上面有些不太好。


在早前的IOS 8盛宴系列的教程裡,你已經瞭解到,Swift提供了許多強大的、現代的編程特性,比如泛型、函數式編程、一等類型(first class)的枚舉、結構體等特性。

但是現在還有另外一個Swift的特性,你應該知道並且會愛上它,它就是運算子多載。

這是一個很好的方法,你能使用+、-、*、/等操作符作用在你喜歡的任何類型上面。如果你有一定的創造性,你甚至可以定義屬於你自己的操作符。

例如:我們在Swift Sprite Kit utility library(https://github.com/raywenderlich/SKTUtils/tree/swift)代碼中使用運算子多載去講多個CGPoints對象相加,例如下面代碼:
let pt1 = CGPoint(x: 10, y: 20)
let pt2 = CGPoint(x: -5, y: 0)
let pt3 = pt1 + pt2
let pt4 = pt3 * 100

方便吧?那就馬上開始重載吧,增強你的Swift開發的能力吧。


注意:這個Swift的教程是假設你已經具備了基礎的Swift開發能力。如果你是新接觸Swift,我們建議你先去學習我們的其他的Swift教程(http://www.raywenderlich.com/tutorials#swift).


運算子:概述
注意:這一部分的內容是可選的,如果你想回顧一下運算子及其優先順序,還是可以看這部分內容的。如果你已經對這些很熟悉了,可以直接建立一個空的playground,進行下一部分內容:重載(Overloading)。


首先我們建立一個新的playground來協助你去瞭解運算子。
添加如下的代碼在你的playground中:
var simpleSum = 1 + 3
你能看到我們希望的結果:
4
這裡有兩個我們熟悉的操作符:
1 首先,你定義了一個叫做simpleSum的變數,並且使用賦值操作符(=)設定了它的值。
2 然後,你使用加操作符(+)計算了兩個整數的和。


在這篇教程裡,你將像這樣重載操作符。但是首先,你需要理解優先順序的概念。
優先順序
你可能還記得在學校裡的數學課上學過的關於操作符的優先順序的規則。這些規則使某些操作符比其他得操作符有一個更高的優先順序,高優先順序的操作符被優先計算。例如乘會在加或者減之前計算。
在你的playground中輸入以下的代碼,驗證在Swift中是否也遵循這些規則。
var sumWithMultiplication = 1 + 3 - 3 * 2
你能看到如下的結果:
-2
當算數操作符有相同的優先順序的時候,Swift從左至右去計算這些操作符。在這個例子中,運算子按照如下的順序計算的:
1.3 * 2:減去(譯者註:這個減去可以忽略,主要是為了對應第三步)
2.1 + 3:因為在操作符優先順序一樣得情況下,優先計算最左邊得操作符。
3.4 - 6:這個運算完全依賴於前面高優先順序的運算子的運算結果。


注意:如果你想瞭解Swift中優先順序的列表,你能在這裡(https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)找到完成的運算子優先順序的列表。


加不是僅僅能夠使用在數字上(Adding Isn’t Just for Ints)
整數運算會按照我們希望的運行,但是你能將+使用到其他的類型上嗎?
下面代碼證明了,你可以!在你的playground裡面添加如下的代碼試一試:
var sumArray = [1, 2] + [1, 2]
在這種情況下,Swift將+解釋成為append指令。但是如果你是想把每一個位置的元素相加怎麼辦呢?我們都知道這個叫做向量加法(vector addition)。
當然,你能自己定義一個方法去實現這個功能,在你的playground添加如下的代碼再試一試:
func add(left: [Int], right: [Int]) -> [Int] {
    var sum = [Int]() 
    assert(left.count == right.count, "vector of same length only") 
    for (key, v) in enumerate(left) {
        sum.append(left[key] + right[key]) 
    }
    return sum
}
這樣你就定義了一個全域的方法,這個方法實現了計算輸入的兩個數組的相加,首先檢測兩個輸入的數組的長度是否一致,然後將兩個數組每一個位置上的元素相加並且儲存到一個新的數組裡面。
現在添加下面的代碼,驗證一下你的新方法是否工作正常:
var arr1 = [1, 1]
var arr2 = [1, 1]
var arr3 = add(arr1, arr2)
你將在控制台看到如下的輸出:
[2, 2]
它很棒!但是我們必須去調用一個方法去做這件事,為什麼我們不可以使用+運算子代替呢?

運算子多載
運算子多載允許你改變現在的作用在特定在的結構體和類上的已經存在的操作符的工作方式(譯者註:可能有點亂)。這個不正是你想要的嗎--改變+操作符作用在int數組上的方式。
因為運算子多載是作用在playground的全域中的,所以建立一個playground,防止影響你原來寫的例子。然後添加如下的代碼到你的playground:
func +(left: [Int], right: [Int]) -> [Int] { // 1
    var sum = [Int]() // 2
    assert(left.count == right.count, "vector of same length only")  // 3
    for (key, v) in enumerate(left) {
      sum.append(left[key] + right[key]) // 4
    }
    return sum
}
你已經定義了一個全域的函數,叫做+,它將兩個int數組相加然後返回一個int數組。下面分解一下它是怎麼工作的:
1.注意這個方法定義沒有什麼特殊。它是一個普通的方法定義,除了你使用了+作為它的函數名。
2.你建立了一個空的Int數組。
3.這個例子只能工作在兩個數組是相同的情況上,所以這裡使用assert保證它是這樣。
4.然後你枚舉了左側的數組,並且加上了右邊的數組在相同位置的值。
在你的playground添加如下的代碼,測試一下這個方法:
var sumArray1 = [1, 2, 3] + [1, 2, 3]
最終--你期望的向量相加操作符結果出現了!你將看到如下的結果:
[2, 4, 6]
當然,運算子多載並不都是愉快的。當一個人查看你的代碼,他們希望操作符的預設行為,這時候運算子多載會使他們迷惑。雖然這樣,但是還是不能阻止你重寫+運算子讓它去執行數位減法,當然這樣的風險是明顯的。
image http://cdn4.raywenderlich.com/wp-content/uploads/2014/09/OperatorRage.png
記住運算子多載的原則:能力越大責任越大(with great power comes great responsibility)。
典型的,當你在一個新的對象上重載運算子的時候,需要保持它原始的語義,而不是定義不同(和讓人費解)的行為。
在這個例子中,重載的行為還是保持了原始的語義:向量加法仍然是一種加法。但是當你覆蓋了Int數組預設的加行為的時候,過了幾個月你可能想要使用Int數組加得預設行為,這個將會使用感到很困惑。
幸運的是Swift讓你能夠定義屬於你自己的自訂的運算子。
定義自訂運算子
這裡有三個步驟去定義一個自訂動作符:
1.命名你的運算子
2.選擇一種類型
3.設定它的優先順序和結合性
定義你的運算子
現在你必須選擇一個字元作為你的運算子。自訂運算子可以以/、=、-、+、!、*、%、<、>、&、|、^、~或者Unicode字元開始。這個給了你一個很大的範圍去選擇你的運算子。但是別太高興,選擇的時候你還必須考慮重複輸入的時候更少的鍵盤鍵入次數。
在這種情況下,你可以複製粘貼Unicode字元⊕作為很好適應你例子裡面加法的實現。
選擇一種類型
在Swift中你能定義一元、二元和三元的操作符。他們表明了運算子操作的數位數目。
一元操作符與一個運算元相關,比如後置++(i++)或者前置++(++i),他們依賴於運算子與運算元出現的位置。
二元操作符是插入的,因為它出現在兩個操作符中間,比如1 + 1。
三元操作符有三個運算元。在Swift中,?:條件操作符是唯一一個三目運算子,比如a?b:c。
你應該基於你的運算子的運算元的個數選擇合適得類型。你想要實現兩個數組相加,那就定義二元運算子。
設定它的優先順序和結合性
由於運算子定義是全域的,所以你要小心的選擇你的自訂運算子的優先順序和結合性。
這個是十分棘手的,所以有一個比較好的方法,在Swift language reference(https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)中找到一個類似的標準的運算子,然後使用相同的語義。例如,在定義向量加的時候,可以使用與+運算子相同的優先順序和結合性。
編寫你的自訂運算子
回到你的playground,輸入下面代碼去定義你的自訂運算子。為了簡單,你可能想去複製粘貼⊕。(譯者註:這裡可能指的是在使用的過程中去複製這個字元)
infix operator ⊕ { associativity left precedence 140 } // 1
func ⊕(left: [Int], right: [Int]) -> [Int] { // 2
    var sum = [Int](count: left.count, repeatedValue: 0)
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        sum[key] = left[key] + right[key]
    }
    return sum
}
這段代碼與你前面在第一部分中的重載類似,這段代碼主要做了以下幾個步驟:
* 定義一個中綴/二元操作符,它有兩個運算元並且位於操作符兩側。
* 命名操作符為⊕。
* 設定結合性為left,表明該操作符在相同優先順序時候,將使用操作符的順序從左至右結合。
* 設定優先權為140,這個是和Int加法有相同的優先順序,這些優先順序可以在Swift language reference(https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-XID_720)查看。
在第二部分的代碼和你在前面看到的類似,它按照兩個數組的順序將其一個一個的相加。在你的playground中添加下面的代碼,測試這個新的運算子:
var sumArray = [1, 2, 3] ⊕ [1, 2, 3]
你將看到和前面重載方法一樣的結果,但是這次你有了一個擁有不同語義的操作符。
Bonus Round!
現在你已經知道了怎麼去建立一個自訂的運算子,是時候挑戰一下你自己了。你已經建立了一個⊕運算子去執行向量的相加,所以使用現在的知識去建立一個?操作符,使用相似的方法實現兩個數組的減法。盡你最大的努力,然後再去查看下面的答案。
infix operator  ? { associativity left precedence 140 }
func ?(left: [Int], right: [Int]) -> [Int] {
  var minus = [Int](count: left.count, repeatedValue: 0)
  assert(left.count == right.count, "vector of same length only")
  for (key, v) in enumerate(left) {
    minus[key] = left[key] - right[key]
  }
  return minus
}
測試:
var subtractionArray = [1, 2, 3] ? [1, 2, 3]


記住相似的操作符
如果你定義了一個新的操作符,不要忘了定義任何相關得運算子。
例如,加等運算子(+=)組合了加和賦值兩個運算子成為了一個運算子。由於你的新的運算子語義上是跟加是一樣的,一個好的方法是也定義一個加等於運算子。
添加下面得代碼到Operator2.playground:
infix operator  ⊕= { associativity left precedence 140 } // 1
func ⊕=(inout left: [Int], right: [Int]) { // 2
    left = left ⊕ right
}
第一行是與⊕運算子一樣得聲明,它使用了一個組合運算子。
需要注意第二行,聲明這個組合運算子的左側輸入參數為inout,這個表示這個參數的值,將會在運算子方法內部直接被修改。作為一個結果,這個運算子不用返回一個值,它直接修改了你的輸入值。
在你的playground添加如下的代碼,測試這個運算子是否按照你想的方法運行。
你將在控制台看到如下輸出:
[3, 5, 7]
現在看看,定義你自己的運算子一點也不難。


為不僅僅是一種類型定義運算子
現在想象你也想為小數定義一個向量加運算子。
一種方式是你按照為Int重載運算子的方式,為Double和Float重載一個新的運算子。它僅僅是幾行的代碼,但是你必須使用複製/粘貼。如果你像我一樣--有代碼潔癖--複製代碼不是你的第一選擇,它會使你的代碼很難維護。
使用泛型來解救
幸運的,Swift泛型能協助你實現這個功能。如果你需要複習一下Swift的泛型,可以找到我們之前發布的文章Swift Generics Tutorial(http://www.raywenderlich.com/82572/swift-generics-tutorial)。
為了有一個乾淨的上下文環境,我們建立一個playground。添加如下的代碼到你的playground中:
infix operator ⊕ { associativity left precedence 140 }
func ⊕<T>(left: [T], right: [T]) -> [T] { // 1
    var minus = [T]()
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        minus.append(left[key] + right[key]) // 2
    }
    return minus
}
在第一行,你定義了一個泛型型別得函數⊕,它有一個類型預留位置T。到這裡playground不高興了。你能看到一個編譯錯誤:Could not find an overload for ‘+‘ that accepts the supplied arguments.
這個錯誤來源於第二行,當我們嘗試使用+運算子作用在兩個類型為T得left和right兩個參數上的時候發生錯誤。Swift並不知道它應該怎麼使用+運算子作用在這些參數上,因為它不知道這些參數是什麼類型。
擴充一個協議
去掉你的代碼,並且用下面的代碼代替:
protocol Number {  // 1
    func +(l: Self, r: Self) -> Self // 2
}
 
extension Double : Number {} // 3
extension Float  : Number {}
extension Int    : Number {}
 
infix operator ⊕ { associativity left precedence 140 }
func ⊕<T: Number>(left: [T], right: [T]) -> [T] { // 4
    var minus = [T]()
    assert(left.count == right.count, "vector of same length only")
    for (key, v) in enumerate(left) {
        minus.append(left[key] + right[key])
    }
    return minus
}
你在這裡做了許多的事情,我們回過頭來分解一下這些步驟:
1. 你定義了一個協議Number
2. 這個Number定義了一個運算子+
3. 你為Double、Float和Int建立了一個擴充,使它們能夠實現Number協議
4. 你使用了一個類型約束去要求T必須實現Number協議
最後,你告訴編譯器,T應該怎麼去處理+運算子。既然你已經修複了編譯錯誤,那就使用下面得代碼分別使用Double數組和Int數組測試一下吧:
var doubleArray = [2.4, 3.6] ⊕ [1.6, 2.4]
var intArray = [2, 4] ⊕ [1, 2]
你將在控制台看到如下輸出:
[4.0, 6.0]
[3, 6]
現在這個運算子能夠正常在多種資料類型下面工作,並且沒有複製代碼。如果你想添加更多得數字類型,你只需要簡單的生命其實現Number協議就可以了。


在真實得生活中我還能怎麼使用運算子多載
難道你就沒有想過,如果它沒有作用,我會讓你浪費這麼多的時間在這篇教程上嗎?這一部分將要展示給你一個真實得例子,讓你瞭解怎麼樣在你的項目中更好得使用運算子多載。
運算子和CGPoints
對於這個Demo,你將使用SKTUtils library(https://github.com/raywenderlich/SKTUtils/tree/swift),它是一個方便得Sprite Kit協助類的集合,當時是為了 iOS Games by Tutorials(http://www.raywenderlich.com/store/ios-games-by-tutorials)這本書的第二版而寫的。
你能在github上找到這個架構的倉庫。在你命令列介面輸入如下的代碼,可以Clone一份這個倉庫的分支:
git clone https://github.com/raywenderlich/SKTUtils.git --branch swift
你在github上下載下來的是該倉庫分支的壓縮包zip。
注意:從Xcode6 beta 5開始,在playground中引入你自己的library成為了可能。你需要做的就是將架構和playground綁定在一個workspace中。如果你想知道更多關於這些的內容,請閱讀這篇文章Playground has never been so fun(http://corinnekrych.blogspot.fr/2014/08/playground-has-never-been-so-fun.html)。
開啟SKUTils/Examples/Playground/SKUTils.xcodeworkspace,並且編譯這個項目。
然後從項目導航裡面開啟MyPlayground.playground。刪除現在裡面的內容並且添加如下的代碼:
import SKTUtils 
 
let pt1 = CGPoint(x: 10, y: 20)
let pt2 = CGPoint(x: -5, y: 0)
let pt3 = pt1 + pt2 
let pt4 = pt3 * 100
你可能很驚訝,你已經在CGPoint上成功的使用+、*運算子,並且編譯器並沒有出現錯誤。
{x 10 y 20}
{x -5 y 0}
{x 5 y 20}
{x 500 y 2,000}
這個魔法來自於你在頭部引入的SKTUtils。讓我們仔細的看一下。


在SKTUtils中的重載
在項目導航中開啟SKTUtils/CGPoint+Extension.swift檔案。你將看到為CGPoint定義了一個擴充,重載了+和+=運算子。
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
 
public func += (inout left: CGPoint, right: CGPoint) {
  left = left + right
}
這段代碼跟你前面寫的類似,只是把存取控制符設定成了public。存取控制符約束著在其他得源檔案和模組中能否訪問到你的代碼。由於SKTUtils是一個架構,所以它需要能夠被它自己模組之外訪問到,所以定義為了public。
這個戲法解釋清楚了,它沒有一點魔力,只是一個聰明得編碼。
當然,在遊戲中CGPoint加法和乘法也是一個很普通得運算,所以在那本書中,重載了CGPoint的運算子簡化了代碼,使它簡潔、易讀。我相信你能在你的項目中發現類似的例子。
運算子多載是Swift的一個強大得特性,如果你小心的使用它,它會使你的開發更加高效。


接下來該做什嗎?
你已經到了這個教程的結尾,我希望你喜歡它!你能在這裡(http://cdn5.raywenderlich.com/wp-content/uploads/2014/09/OperatorsPlaygrounds.zip)找到最終的playground。
如果你想學習更多關於運算子多載和Swift的知識,可以查看我們的新書Swift by Tutorials(http://www.raywenderlich.com/store/swift-tutorials-bundle)。
我希望你找到一種方法,在你的項目中使用運算子多載!但是記住:with great power comes great responsibility(能力越大責任越大) – don’t be Troll Dev! ;]
如果你對於這個教程或者運算子多載有什麼問題,可以在下面加入我們的論壇討論。

Swift教程之運算子多載

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.