作者:COSMIN PUP?Z?,原文鏈接涝滴,原文日期:2016-03-29
譯者:zltunes涮瞻;校對(duì):shanks鲤拿;定稿:小鍋
在任何一門計(jì)算機(jī)編程語言中,運(yùn)算符重載都是非常強(qiáng)大的特性之一饲宛,因此蘋果決定為 Swift 也提供這一機(jī)制皆愉。然而,"能力越強(qiáng)責(zé)任越大"。利用運(yùn)算符重載你很容易實(shí)現(xiàn)一些奇怪的場(chǎng)景幕庐,例如用減法運(yùn)算符實(shí)現(xiàn)兩數(shù)相加久锥,或者用乘法運(yùn)算符實(shí)現(xiàn)兩數(shù)相除,但這顯然都不是你希望出現(xiàn)的异剥。
好了瑟由,閑話少敘 —— 讓我們看看運(yùn)算符重載究竟是怎么一回事。
挑戰(zhàn)
這一小節(jié)的任務(wù)很簡(jiǎn)單:擴(kuò)展乘法運(yùn)算符的標(biāo)準(zhǔn)功能冤寿,使其適用于字符串歹苦。你將會(huì)用到字符串拼接運(yùn)算符,想象一下這種用法:
"abc" * 5 = "abc" + "abc" + "abc" + "abc" + "abc" = "abcabcabcabcabc"
正式編碼之前督怜,思考一下應(yīng)該怎么做殴瘦,分幾步來實(shí)現(xiàn)。我的做法是這樣的:
- 定義變量
result
并進(jìn)行初始化 —— 默認(rèn)字符串号杠。 - 從 2 開始循環(huán)蚪腋,一直到待拼接的字符串?dāng)?shù)目終止,每次迭代只做一件事 —— 把字符串拼接到
result
末尾姨蟋。 - 打印
result
屉凯。
算法大致就是這樣,接下來讓我們付諸實(shí)踐眼溶。
基本運(yùn)算符重載
啟動(dòng) Xcode 并新建一個(gè) playground 文件悠砚。刪除原有代碼,添加乘法運(yùn)算符的函數(shù)原型:
func *(lhs: String, rhs: Int) -> String {
}
函數(shù)有兩個(gè)參數(shù) —— 左操作數(shù)是 String
類型堂飞,右操作數(shù)是 Int
類型灌旧,函數(shù)返回類型為 String
。
函數(shù)體內(nèi)應(yīng)該完成三件事酝静。首先节榜,定義 result
變量并初始化為函數(shù)的 String
參數(shù) —— 這是一個(gè)變量,稍后會(huì)修改它的值别智。
var result = lhs
接下來使用 for in
控制流語句及閉區(qū)間運(yùn)行符從 2 開始循環(huán)宗苍,直到函數(shù)的 Int
參數(shù)時(shí)為止:
for _ in 2...rhs {
}
注意:這里使用了 _
作為通配符,因?yàn)槲覀兿M雎孕蛄械木唧w值 —— 關(guān)于循環(huán)的更多說明可以看這里薄榛。
循環(huán)體內(nèi)只有一個(gè)任務(wù) —— 更新 result
:
result += lhs
注意:你也可以按如下方式來寫 —— 上邊這種寫法更短讳窟,是因?yàn)橛昧思臃◤?fù)合運(yùn)算符。
result = result + lhs
最后返回 result:
return result
現(xiàn)在我們直接使用運(yùn)算符:
let u = "abc"
let v = u * 5
搞定了敞恋!只是還有一個(gè)問題 —— 你只能將其用于字符串丽啡,那其它類型的數(shù)據(jù)怎么辦?我們使用范型運(yùn)算符來完善硬猫。
泛型運(yùn)算符
泛型默認(rèn)是不支持運(yùn)算符的补箍,所以需要協(xié)議來支持改执。向 playground 中添加協(xié)議原型:
protocol Type {
}
現(xiàn)在向協(xié)議中添加加法復(fù)合運(yùn)算符函數(shù)的原型:
func +=(inout lhs: Self, rhs: Self)
函數(shù)擁有左右操作數(shù),并且都設(shè)置為 Self
類型 —— 這是一種巧妙的方式坑雅,說明二者的類型都是實(shí)現(xiàn)了該協(xié)議的類辈挂。左操作數(shù)標(biāo)記為inout
,因?yàn)樗闹凳且恍薷牟⑶易詈蟊缓瘮?shù)返回的裹粤。
或者终蒂,你也可以定義加法運(yùn)算符的函數(shù)原型:
func +(lhs: Self, rhs: Self) -> Self
函數(shù)擁有 Self
類型的左右操作數(shù),并且加法運(yùn)算的返回結(jié)果也是 Self
遥诉。這種情況下就不需要使用 inout
參數(shù)了拇泣。
接下來,為 String
, Int
, Double
, Float
等實(shí)現(xiàn)了 Type
協(xié)議的類型創(chuàng)建擴(kuò)展矮锈。
extension String: Type {}
extension Int: Type {}
extension Double: Type {}
extension Float: Type {}
注意:這些擴(kuò)展的實(shí)現(xiàn)是空的霉翔,因?yàn)槟悴⒉淮蛩銥槟J(rèn)類型添加任何東西,僅僅是要讓他們遵循 Type
協(xié)議愕难。
現(xiàn)在向 playground 中添加乘法操作符函數(shù)原型:
func *<T: Type>(lhs: T, rhs: Int) -> T {
}
函數(shù)有兩個(gè)參數(shù)早龟,左操作數(shù)是 T
類型惫霸,右操作數(shù)是 Int
類型猫缭,函數(shù)返回類型為 T
。利用類型約束使 T
類型遵循 Type
協(xié)議壹店,這樣它就可以使用加法復(fù)合運(yùn)算符了猜丹。
注意:你可以使用 where
關(guān)鍵字定義類型約束——盡管上邊的方法更簡(jiǎn)短:
func *<T where T: Type>(lhs: T, rhs: Int) -> T
函數(shù)的實(shí)現(xiàn)跟之前一樣:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
注意:可以使用加法操作符替代,但要確保它的函數(shù)原型添加到了協(xié)議中硅卢。
測(cè)試一下:
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
搞定了射窒!不過有一個(gè)問題:你使用的是標(biāo)準(zhǔn)乘法運(yùn)算符,這個(gè)可能造成歧義将塑。如果換成其它運(yùn)算符會(huì)更好脉顿。接下來我們?cè)囍米远x運(yùn)算符解決這個(gè)問題。
自定義運(yùn)算符
首先添加下面一行到 playground:
infix operator ** {associativity left precedence 150}
一步一步解釋:
- 自定義乘法運(yùn)算符的名稱是 **点寥。
- 類型是
中綴運(yùn)算符(infix)
因?yàn)樗袃蓚€(gè)操作數(shù)艾疟。 - 運(yùn)算順序從左至右,因此是左結(jié)合敢辩。
- 優(yōu)先級(jí)設(shè)置為 150 —— 與標(biāo)準(zhǔn)乘法運(yùn)算符相同蔽莱,因?yàn)樗歉邇?yōu)先級(jí)運(yùn)算符。
注意:關(guān)于運(yùn)算符優(yōu)先級(jí)和結(jié)合性的更多說明可以看這里戚长。
自定義運(yùn)算符的函數(shù)原型與標(biāo)準(zhǔn)運(yùn)算符類似 —— 只有函數(shù)名不同:
func **<T: Type>(lhs: T, rhs: Int) -> T {
}
函數(shù)實(shí)現(xiàn)跟之前完全一樣:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
測(cè)試一下:
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
搞定了盗冷!還有一個(gè)問題——運(yùn)算符的復(fù)合類型還沒有定義,接下來我們解決這個(gè)問題:
復(fù)合運(yùn)算符
復(fù)合運(yùn)算符的類型同廉、優(yōu)先級(jí)和結(jié)合性和之前一樣 —— 只有名稱不同:
infix operator **= {associativity left precedence 150}
接著向 playground 添加復(fù)合運(yùn)算符的函數(shù)原型:
func **=<T: Type>(inout lhs: T, rhs: Int) {
}
函數(shù)沒有返回類型仪糖,因?yàn)樽蟛僮鲾?shù)被標(biāo)記為 inout
柑司。
函數(shù)體只做一件事 —— 運(yùn)用之前的自定義運(yùn)算符返回乘法結(jié)果:
lhs = lhs ** rhs
測(cè)試一下:
var o = "abc"
o **= 5
var q = 2
q **= 5
var s = 3.14
s **= 5
var w: Float = 4.56
w **= 5
搞定了!這已經(jīng)是最簡(jiǎn)版本了锅劝!
總結(jié)
如果謹(jǐn)慎地使用運(yùn)算符重載帜羊,它便能夠發(fā)揮強(qiáng)大的功能 —— 我希望你能在自己的項(xiàng)目中找到合適使用的方法。
作為參考鸠天,這里有一個(gè)完整版的 Playground讼育,我已經(jīng)在 Xcode 7.3 用 Swift 2.2 測(cè)試過了。
如果你對(duì)本教程或者運(yùn)算符重載有什么看法的話可以給我留言稠集。
致謝:連環(huán)畫是在 MakeBeliefsComix.com 制作的奶段。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)剥纷,最新文章請(qǐng)?jiān)L問 http://swift.gg痹籍。