本章將會介紹
模塊和源文件
訪問級別
訪問控制語法
自定義類型
子類
常量、變量眷蚓、屬性鼻种、下標
構造器
協(xié)議
擴展
泛型
類型別名
位運算符
溢出運算符
優(yōu)先級和結合性
運算符函數
自定義運算符
訪問控制
訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別反番。這個特性可以讓我們隱藏代碼的一些實現(xiàn)細節(jié)沙热,并且可以為其他人可以訪問和使用的代碼提供接口叉钥。
你可以明確地給單個類型(類、結構體篙贸、枚舉)設置訪問級別投队,也可以給這些類型的屬性、方法爵川、構造器敷鸦、下標等設置訪問級別。協(xié)議也可以被限定在一定的范圍內使用寝贡,包括協(xié)議里的全局常量扒披、變量和函數。
Swift 不僅提供了多種不同的訪問級別圃泡,還為某些典型場景提供了默認的訪問級別碟案,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實颇蜡,如果只是開發(fā)一個單一目標的應用程序价说,我們完全可以不用顯式聲明代碼的訪問級別。
注意
為了簡單起見风秤,對于代碼中可以設置訪問級別的特性(屬性鳖目、基本類型、函數等)缤弦,在下面的章節(jié)中我們會稱之為“實體”领迈。
1.模塊和源文件
Swift 中的訪問控制模型基于模塊和源文件這兩個概念。
模塊指的是獨立的代碼單元碍沐,框架或應用程序會作為一個獨立的模塊來構建和發(fā)布惦费。在 Swift 中,一個模塊可以使用 import 關鍵字導入另外一個模塊抢韭。
在 Swift 中薪贫,Xcode 的每個目標(例如框架或應用程序)都被當作獨立的模塊處理。如果你是為了實現(xiàn)某個通用的功能刻恭,或者是為了封裝一些常用方法而將代碼打包成獨立的框架瞧省,這個框架就是 Swift 中的一個模塊。當它被導入到某個應用程序或者其他框架時鳍贾,框架內容都將屬于這個獨立的模塊鞍匾。
源文件就是 Swift 中的源代碼文件,它通常屬于一個模塊骑科,即一個應用程序或者框架橡淑。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個源文件也可以包含多個類型咆爽、函數之類的定義梁棠。
2.訪問級別
Swift 為代碼中的實體提供了五種不同的訪問級別置森。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關符糊。
- 開放訪問和公開訪問可以訪問同一模塊源文件中的任何實體凫海,在模塊外也可以通過導入該模塊來訪問源文件里的所有實體。通常情況下男娄,框架中的某個接口可以被任何人使用時行贪,你可以將其設置為開放或者公開訪問。
- 內部訪問可以訪問同一模塊源文件中的任何實體模闲,但是不能從模塊外訪問該模塊源文件中的實體建瘫。通常情況下,某個接口只在應用程序或框架內部使用時尸折,你可以將其設置為內部訪問暖混。
- 文件私有訪問限制實體只能被所定義的文件內部訪問。當需要把這些細節(jié)被整個文件使用的時候翁授,使用文件私有訪問隱藏了一些特定功能的實現(xiàn)細節(jié)拣播。
- 私有訪問限制實體只能在所定義的作用域內使用。需要把這些細節(jié)被整個作用域使用的時候收擦,使用文件私有訪問隱藏了一些特定功能的實現(xiàn)細節(jié)贮配。
開放訪問為最高(限制最少)訪問級別,私有訪問為最低(限制最多)訪問級別塞赂。
開放訪問只作用于類類型和類的成員泪勒,它和公開訪問的區(qū)別如下:
- 公開訪問或者其他更嚴訪問級別的類,只能在它們定義的模塊內部被繼承宴猾。
- 公開訪問或者其他更嚴訪問級別的類成員圆存,只能在它們定義的模塊內部的子類中重寫。
- 開放訪問的類仇哆,可以在它們定義的模塊中被繼承沦辙,也可以在引用它們的模塊中被繼承。
- 開放訪問的類成員讹剔,可以在它們定義的模塊中子類中重寫油讯,也可以在引用它們的模塊中的子類重寫正塌。
- 把一個類標記為開放矛洞,顯式地表明藕咏,你認為其他模塊中的代碼使用此類作為父類庆尘,然后你已經設計好了你的類的代碼了。
訪問級別基本原則
Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更低(更嚴格)的實體适掰。
例如:
- 一個公開訪問級別的變量割去,其類型的訪問級別不能是內部只泼,文件私有或是私有類型的。因為無法保證變量的類型在使用變量的地方也具有訪問權限软驰。
- 函數的訪問級別不能高于它的參數類型和返回類型的訪問級別涧窒。因為這樣就會出現(xiàn)函數可以在任何地方被訪問,但是它的參數類型和返回類型卻不可以的情況碌宴。
默認訪問級別
如果你不為代碼中的實體顯式指定訪問級別,那么它們默認為 internal 級別(有一些例外情況蒙畴,稍后會進行說明)贰镣。因此,在大多數情況下膳凝,我們不需要顯式指定實體的訪問級別碑隆。
單目標應用程序的訪問級別
當你編寫一個單目標應用程序時上煤,應用的所有功能都是為該應用服務劫狠,而不需要提供給其他應用或者模塊使用独泞,所以我們不需要明確設置訪問級別懦砂,使用默認的訪問級別 internal 即可荞膘。但是羽资,你也可以使用文件私有訪問或私有訪問級別削罩,用于隱藏一些功能的實現(xiàn)細節(jié)费奸。
框架的訪問級別
當你開發(fā)框架時愿阐,就需要把一些對外的接口定義為開放訪問或公開訪問級別缨历,以便使用者導入該框架后可以正常使用其功能。這些被你定義為對外的接口丛肮,就是這個框架的 API宝与。
注意
框架依然會使用默認的內部訪問級別习劫,也可以指定為文件私有訪問或者私有訪問級別诽里。當你想把某個實體作為框架的 API 的時候谤狡,需顯式為其指定開放訪問或公開訪問級別墓懂。
單元測試目標的訪問級別
當你的應用程序包含單元測試目標時,為了測試宛徊,測試模塊需要訪問應用程序模塊中的代碼闸天。默認情況下只有開放訪問或公開訪問級別級別的實體才可以被其他模塊訪問苞氮。然而瓤逼,如果在導入應用程序模塊的語句前使用 @testable 特性霸旗,然后在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程序模塊诱告,單元測試目標就可以訪問應用程序模塊中所有內部級別的實體。
3.訪問控制語法
訪問控制語法
通過修飾符 open潜必,public磁滚,internal垂攘,fileprivate搜贤,private 來聲明實體的訪問級別:
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
除非專門指定钝凶,否則實體默認的訪問級別為內部訪問級別耕陷。這意味著在不使用修飾符顯式聲明訪問級別的情況下据沈,SomeInternalClass 和 someInternalConstant 仍然擁有隱式的內部訪問級別:
class SomeInternalClass {} // 隱式內部訪問級別
var someInternalConstant = 0 // 隱式內部訪問級別
4.自定義類型
如果想為一個自定義類型指定訪問級別锌介,在定義類型時進行指定即可孔祸。新類型只能在它的訪問級別限制范圍內使用崔慧。例如,你定義了一個文件私有級別的類惶室,那這個類就只能在定義它的源文件中使用悼泌,可以作為屬性類型券躁、函數參數類型或者返回類型,等等以舒。
一個類型的訪問級別也會影響到類型成員(屬性、方法滥沫、構造器兰绣、下標)的默認訪問級別缀辩。如果你將類型指定為私有或者文件私有級別,那么該類型的所有成員的默認訪問級別也會變成私有或者文件私有級別健无。如果你將類型指定為公開或者內部訪問級別(或者不明確指定訪問級別,而使用默認的內部訪問級別)臼膏,那么該類型的所有成員的默認訪問級別將是內部訪問。
重要
上面提到夺溢,一個公開類型的所有成員的訪問級別默認為內部訪問級別丹禀,而不是公開級別密似。如果你想將某個成員指定為公開訪問級別贫导,那么你必須顯式指定。這樣做的好處是,在你定義公共接口的時候毅待,可以明確地選擇哪些接口是需要公開的,哪些是內部使用的,避免不小心將內部使用的接口公開盅蝗。
public class SomePublicClass { // 顯式公開類
public var somePublicProperty = 0 // 顯式公開類成員
var someInternalProperty = 0 // 隱式內部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
class SomeInternalClass { // 隱式內部類
var someInternalProperty = 0 // 隱式內部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
fileprivate class SomeFilePrivateClass { // 顯式文件私有類
func someFilePrivateMethod() {} // 隱式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
private class SomePrivateClass { // 顯式私有類
func somePrivateMethod() {} // 隱式私有類成員
}
元組類型
元組的訪問級別將由元組中訪問級別最嚴格的類型來決定狂秦。例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為內部訪問級別,另一個類型為私有訪問級別,那么這個元組的訪問級別為私有訪問級別。
注意
元組不同于類膀篮、結構體、枚舉毙死、函數那樣有單獨的定義再菊。元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。
函數類型
函數的訪問級別根據訪問級別最嚴格的參數類型或返回類型的訪問級別來決定聚请。但是,如果這種訪問級別不符合函數定義所在環(huán)境的默認訪問級別,那么就需要明確地指定該函數的訪問級別龄恋。
下面的例子定義了一個名為 someFunction() 的全局函數,并且沒有明確地指定其訪問級別殖蚕。也許你會認為該函數應該擁有默認的訪問級別 internal,但事實并非如此。事實上,如果按下面這種寫法珊擂,代碼將無法通過編譯:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數實現(xiàn)部分
}
我們可以看到扳剿,這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱自定義類型)佣蓉。其中一個類的訪問級別是 internal,另一個的訪問級別是 private擂涛,所以根據元組訪問級別的原則谷暮,該元組的訪問級別是 private(元組的訪問級別與元組中訪問級別最低的類型一致)俯在。
因為該函數返回類型的訪問級別是 private愕提,所以你必須使用 private 修飾符馒稍,明確指定該函數的訪問級別:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數實現(xiàn)部分
}
將該函數指定為 public 或 internal浅侨,或者使用默認的訪問級別 internal 都是錯誤的纽谒,因為如果把該函數當做 public 或 internal 級別來使用的話,可能會無法訪問 private 級別的返回值如输。
枚舉類型
枚舉成員的訪問級別和該枚舉類型相同鼓黔,你不能為枚舉成員單獨指定不同的訪問級別。
比如下面的例子不见,枚舉 CompassPoint 被明確指定為 public 級別澳化,那么它的成員 North、South稳吮、East缎谷、West 的訪問級別同樣也是 public:
public enum CompassPoint {
case North
case South
case East
case West
}
原始值和關聯(lián)值:枚舉定義中的任何原始值或關聯(lián)值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如灶似,你不能在一個 internal 訪問級別的枚舉中定義 private 級別的原始值類型列林。
嵌套類型
如果在 private 級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有 private 訪問級別酪惭。如果在 public 或者 internal 級別的類型中定義嵌套類型席纽,那么該嵌套類型自動擁有 internal 訪問級別。如果想讓嵌套類型擁有 public 訪問級別撞蚕,那么需要明確指定該嵌套類型的訪問級別润梯。
5.子類
子類的訪問級別不得高于父類的訪問級別。例如甥厦,父類的訪問級別是 internal纺铭,子類的訪問級別就不能是 public。
此外刀疙,你可以在符合當前訪問級別的條件下重寫任意類成員(方法舶赔、屬性、構造器谦秧、下標等)竟纳。
6.常量、變量疚鲤、屬性锥累、下標
常量、變量集歇、屬性不能擁有比它們的類型更高的訪問級別桶略。例如,你不能定義一個 public 級別的屬性,但是它的類型卻是 private 級別的际歼。同樣惶翻,下標也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量鹅心、變量吕粗、屬性、下標的類型是 private 級別的旭愧,那么它們必須明確指定訪問級別為 private:
private var privateInstance = SomePrivateClass()
- Getter 和 Setter
常量颅筋、變量、屬性榕茧、下標的 Getters 和 Setters 的訪問級別和它們所屬類型的訪問級別相同垃沦。
Setter 的訪問級別可以低于對應的 Getter 的訪問級別,這樣就可以控制變量用押、屬性或下標的讀寫權限肢簿。在 var 或 subscript 關鍵字之前,你可以通過 fileprivate(set)蜻拨,private(set) 或 internal(set) 為它們的寫入權限指定更低的訪問級別池充。
注意
這個規(guī)則同時適用于存儲型屬性和計算型屬性。即使你不明確指定存儲型屬性的 Getter 和 Setter缎讼,Swift 也會隱式地為其創(chuàng)建 Getter 和 Setter收夸,用于訪問該屬性的后備存儲。使用 fileprivate(set)血崭,private(set) 和 internal(set) 可以改變 Setter 的訪問級別卧惜,這對計算型屬性也同樣適用。
下面的例子中定義了一個名為 TrackedString 的結構體夹纫,它記錄了 value 屬性被修改的次數:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
TrackedString 結構體定義了一個用于存儲 String 值的屬性 value咽瓷,并將初始值設為 ""(一個空字符串)。該結構體還定義了另一個用于存儲 Int 值的屬性 numberOfEdits舰讹,它用于記錄屬性 value 被修改的次數茅姜。這個功能通過屬性 value 的 didSet 觀察器實現(xiàn),每當給 value 賦新值時就會調用 didSet 方法月匣,然后將 numberOfEdits 的值加一钻洒。
結構體 TrackedString 和它的屬性 value 均沒有顯式指定訪問級別,所以它們都擁有默認的訪問級別 internal锄开。但是該結構體的 numberOfEdits 屬性使用了 private(set) 修飾符素标,這意味著 numberOfEdits 屬性只能在定義該結構體的源文件中賦值。numberOfEdits 屬性的 Getter 依然是默認的訪問級別 internal院刁,但是 Setter 的訪問級別是 private糯钙,這表示該屬性只有在當前的源文件中是可讀寫的粪狼,而在當前源文件所屬的模塊中只是一個可讀的屬性退腥。
如果你實例化 TrackedString 結構體任岸,并多次對 value 屬性的值進行修改,你就會看到 numberOfEdits 的值會隨著修改次數而變化:
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// 打印 “The number of edits is 3”
雖然你可以在其他的源文件中實例化該結構體并且獲取到 numberOfEdits 屬性的值狡刘,但是你不能對其進行賦值享潜。這一限制保護了該記錄功能的實現(xiàn)細節(jié),同時還提供了方便的訪問方式嗅蔬。
你可以在必要時為 Getter 和 Setter 顯式指定訪問級別剑按。下面的例子將 TrackedString 結構體明確指定為了 public 訪問級別。結構體的成員(包括 numberOfEdits 屬性)擁有默認的訪問級別 internal澜术。你可以結合 public 和 private(set) 修飾符把結構體中的 numberOfEdits 屬性的 Getter 的訪問級別設置為 public艺蝴,而 Setter 的訪問級別設置為 private:
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
7.構造器
自定義構造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是必要構造器鸟废,它的訪問級別必須和所屬類型的訪問級別相同猜敢。
如同函數或方法的參數,構造器參數的訪問級別也不能低于構造器本身的訪問級別盒延。
默認構造器
如默認構造器所述缩擂,Swift 會為結構體和類提供一個默認的無參數的構造器,只要它們?yōu)樗写鎯π蛯傩栽O置了默認初始值添寺,并且未提供自定義的構造器胯盯。
默認構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public计露。如果一個類型被指定為 public 級別博脑,那么默認構造器的訪問級別將為 internal。如果你希望一個 public 級別的類型也能在其他模塊中使用這種無參數的默認構造器票罐,你只能自己提供一個 public 訪問級別的無參數構造器叉趣。
結構體默認的成員逐一構造器
如果結構體中任意存儲型屬性的訪問級別為 private,那么該結構體默認的成員逐一構造器的訪問級別就是 private胶坠。否則君账,這種構造器的訪問級別依然是 internal。
如同前面提到的默認構造器沈善,如果你希望一個 public 級別的結構體也能在其他模塊中使用其默認的成員逐一構造器乡数,你依然只能自己提供一個 public 訪問級別的成員逐一構造器。
8.協(xié)議
如果想為一個協(xié)議類型明確地指定訪問級別闻牡,在定義協(xié)議時指定即可净赴。這將限制該協(xié)議只能在適當的訪問級別范圍內被采納。
協(xié)議中的每一個要求都具有和該協(xié)議相同的訪問級別罩润。你不能將協(xié)議中的要求設置為其他訪問級別玖翅。這樣才能確保該協(xié)議的所有要求對于任意采納者都將可用。
注意
如果你定義了一個 public 訪問級別的協(xié)議,那么該協(xié)議的所有實現(xiàn)也會是 public 訪問級別金度。這一點不同于其他類型应媚,例如,當類型是 public 訪問級別時猜极,其成員的訪問級別卻只是 internal中姜。
協(xié)議繼承
如果定義了一個繼承自其他協(xié)議的新協(xié)議,那么新協(xié)議擁有的訪問級別最高也只能和被繼承協(xié)議的訪問級別相同跟伏。例如丢胚,你不能將繼承自 internal 協(xié)議的新協(xié)議定義為 public 協(xié)議。
協(xié)議一致性
一個類型可以采納比自身訪問級別低的協(xié)議受扳。例如携龟,你可以定義一個 public 級別的類型,它可以在其他模塊中使用勘高,同時它也可以采納一個 internal 級別的協(xié)議峡蟋,但是只能在該協(xié)議所在的模塊中作為符合該協(xié)議的類型使用。
采納了協(xié)議的類型的訪問級別取它本身和所采納協(xié)議兩者間最低的訪問級別相满。也就是說如果一個類型是 public 級別层亿,采納的協(xié)議是 internal 級別,那么采納了這個協(xié)議后立美,該類型作為符合協(xié)議的類型時匿又,其訪問級別也是 internal。
如果你采納了協(xié)議建蹄,那么實現(xiàn)了協(xié)議的所有要求后碌更,你必須確保這些實現(xiàn)的訪問級別不能低于協(xié)議的訪問級別。例如洞慎,一個 public 級別的類型痛单,采納了 internal 級別的協(xié)議,那么協(xié)議的實現(xiàn)至少也得是 internal 級別劲腿。
注意
Swift 和 Objective-C 一樣旭绒,協(xié)議的一致性是全局的,也就是說焦人,在同一程序中挥吵,一個類型不可能用兩種不同的方式實現(xiàn)同一個協(xié)議。
9.擴展
你可以在訪問級別允許的情況下對類花椭、結構體忽匈、枚舉進行擴展。擴展成員具有和原始類型成員一致的訪問級別矿辽。例如丹允,你擴展了一個 public 或者 internal 類型郭厌,擴展中的成員具有默認的 internal 訪問級別,和原始類型中的成員一致 雕蔽。如果你擴展了一個 private 類型折柠,擴展成員則擁有默認的 private 訪問級別。
或者萎羔,你可以明確指定擴展的訪問級別(例如液走,private extension)碳默,從而給該擴展中的所有成員指定一個新的默認訪問級別贾陷。這個新的默認訪問級別仍然可以被單獨指定的訪問級別所覆蓋。
通過擴展添加協(xié)議一致性
如果你通過擴展來采納協(xié)議嘱根,那么你就不能顯式指定該擴展的訪問級別了髓废。協(xié)議擁有相應的訪問級別,并會為該擴展中所有協(xié)議要求的實現(xiàn)提供默認的訪問級別该抒。
10.泛型
泛型類型或泛型函數的訪問級別取決于泛型類型或泛型函數本身的訪問級別慌洪,還需結合類型參數的類型約束的訪問級別,根據這些訪問級別中的最低訪問級別來確定凑保。
11.類型別名
你定義的任何類型別名都會被當作不同的類型冈爹,以便于進行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別欧引。例如频伤,private 級別的類型別名可以作為 private,file-private芝此,internal憋肖,public或者open類型的別名,但是 public 級別的類型別名只能作為 public 類型的別名婚苹,不能作為 internal岸更,file-private,或 private 類型的別名膊升。
注意
這條規(guī)則也適用于為滿足協(xié)議一致性而將類型別名用于關聯(lián)類型的情況怎炊。
12.訪問控制總結
訪問控制
// 訪問控制語法 修飾符open public internal fileprivate private
public class SomePublicClass { // 顯示公開類
public var somePublicProperty = 0 // 顯示公開類成員
var someInternalProperty = 0 // 隱式內部類成員
fileprivate func someFilePrivateMethod() {} // 顯示文件私有類成員
private func somePrivateMethod() {} // 顯示私有類成員
}
// 省略internal關鍵字
class SomeInternalClass { // 隱式內部類
var someInternalProperty = 0 // 隱式內部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
fileprivate class SomeFilePrivateClass { // 顯式文件私有類
func someFilePrivateMethod() {} // 隱式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
private class SomePrivateClass { // 顯示私有類
func somePrivateMethod() {} // 隱式私有類成員
}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
// 函數類型訪問級別由訪問級別最嚴格的參數類型或返回類型的訪問級別決定
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
return (SomeInternalClass(), SomePrivateClass())
}
// 從返回值元組的訪問級別推斷出元祖的訪問級別是private,那么函數的訪問級別應該也是private廓译,因此需要顯式聲明
// 子類的訪問級別不得高于父類的訪問級別评肆,可以通過重寫為繼承來的類成員提供更高的訪問級別
/*
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
運行起來是錯誤的
*/
// 如果常量、變量责循、屬性糟港、下標的類型是 private 級別的,那么它們必須明確指定訪問級別為 private:
private var privateInstance = SomePrivateClass()
// Setter 的訪問級別可以低于對應的 Getter 的訪問級別院仿,這樣就可以控制變量秸抚、屬性或下標的讀寫權限速和。在 var 或 subscript 關鍵字之前,你可以通過 fileprivate(set)剥汤,private(set) 或 internal(set) 為它們的寫入權限指定更低的訪問級別颠放。
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += "This edit will increment numberOfEdits."
stringToEdit.value += "So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// stringToEdit.numberOfEdits = 4
// 不可以設置value的值,只能獲取
高級運算符
除了在之前介紹過的基本運算符吭敢,Swift 中還有許多可以對數值進行復雜運算的高級運算符碰凶。這些高級運算符包含了在 C 和 Objective-C 中已經被大家所熟知的位運算符和移位運算符。
與 C 語言中的算術運算符不同鹿驼,Swift 中的算術運算符默認是不會溢出的欲低。所有溢出行為都會被捕獲并報告為錯誤。如果想讓系統(tǒng)允許溢出行為畜晰,可以選擇使用 Swift 中另一套默認支持溢出的運算符砾莱,比如溢出加法運算符(&+)。所有的這些溢出運算符都是以 & 開頭的凄鼻。
自定義結構體腊瑟、類和枚舉時,如果也為它們提供標準 Swift 運算符的實現(xiàn)块蚌,將會非常有用闰非。在 Swift 中自定義運算符非常簡單,運算符也會針對不同類型使用對應實現(xiàn)峭范。
我們不用被預定義的運算符所限制薄啥。在 Swift 中可以自由地定義中綴摇零、前綴谒养、后綴和賦值運算符沥潭,以及相應的優(yōu)先級與結合性。這些運算符在代碼中可以像預定義的運算符一樣使用其徙,我們甚至可以擴展已有的類型以支持自定義的運算符胚迫。
1.位運算符
位運算符可以操作數據結構中每個獨立的比特位。它們通常被用在底層開發(fā)中唾那,比如圖形編程和創(chuàng)建設備驅動访锻。位運算符在處理外部資源的原始數據時也十分有用,比如對自定義通信協(xié)議傳輸的數據進行編碼和解碼闹获。
Swift 支持 C 語言中的全部位運算符期犬,接下來會一一介紹。
按位取反運算符
按位取反運算符(~)可以對一個數值的全部比特位進行取反:
按位取反運算符是一個前綴運算符避诽,需要直接放在運算的數之前龟虎,并且它們之間不能添加任何空格:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b1111000
UInt8 類型的整數有 8 個比特位,可以存儲 0 ~ 255 之間的任意整數沙庐。這個例子初始化了一個 UInt8 類型的整數鲤妥,并賦值為二進制的 00001111佳吞,它的前 4 位都為 0,后 4 位都為 1棉安。這個值等價于十進制的 15底扳。
接著使用按位取反運算符創(chuàng)建了一個名為 invertedBits 的常量,這個常量的值與全部位取反后的 initialBits 相等贡耽。即所有的 0 都變成了 1衷模,同時所有的 1 都變成 0。invertedBits 的二進制值為 11110000蒲赂,等價于無符號十進制數的 240阱冶。
按位與運算符
按位與運算符(&)可以對兩個數的比特位進行合并。它返回一個新的數凳宙,只有當兩個數的對應位都為 1 的時候熙揍,新數的對應位才為 1:
在下面的示例當中,firstSixBits 和 lastSixBits 中間 4 個位的值都為 1氏涩。按位與運算符對它們進行了運算,得到二進制數值 00111100有梆,等價于無符號十進制數的 60:
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
按位或運算符
按位或運算符(|)可以對兩個數的比特位進行比較是尖。它返回一個新的數,只要兩個數的對應位中有任意一個為 1 時泥耀,新數的對應位就為 1:
在下面的示例中饺汹,someBits 和 moreBits 不同的位會被設置為 1。接位或運算符對它們進行了運算痰催,得到二進制數值 11111110兜辞,等價于無符號十進制數的 254:
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
按位異或運算符
按位異或運算符(^)可以對兩個數的比特位進行比較。它返回一個新的數夸溶,當兩個數的對應位不相同時逸吵,新數的對應位就為 1
在下面的示例當中,firstBits 和 otherBits 都有一個自己的位為 1 而對方的對應位為 0 的位缝裁。 按位異或運算符將新數的這兩個位都設置為 1扫皱,同時將其它位都設置為 0:
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
按位左移、右移運算符
按位左移運算符(<<)和按位右移運算符(>>)可以對一個數的所有位進行指定位數的左移和右移捷绑,但是需要遵守下面定義的規(guī)則韩脑。
對一個數進行按位左移或按位右移,相當于對這個數進行乘以 2 或除以 2 的運算粹污。將一個整數左移一位段多,等價于將這個數乘以 2,同樣地壮吩,將一個整數右移一位进苍,等價于將這個數除以 2蕾总。
- 無符號整數的移位運算
對無符號整數進行移位的規(guī)則如下:
1.已經存在的位按指定的位數進行左移和右移。
2.任何因移動而超出整型存儲范圍的位都會被丟棄琅捏。
3.用 0 來填充移位后產生的空白位生百。
這種方法稱為邏輯移位。
以下這張圖展示了 11111111 << 1(即把 11111111 向左移動 1 位)柄延,和 11111111 >> 1(即把 11111111 向右移動 1 位)的結果蚀浆。藍色的部分是被移位的,灰色的部分是被拋棄的搜吧,橙色的部分則是被填充進來的:
下面的代碼演示了 Swift 中的移位運算:
let shiftBits: UInt8 = 4 // 即二進制的 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001
可以使用移位運算對其他的數據類型進行編碼和解碼:
let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC市俊,即 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99滤奈,即 153
這個示例使用了一個命名為 pink 的 UInt32 型常量來存儲 CSS 中粉色的顏色值摆昧。該 CSS 的十六進制顏色值 #CC6699,在 Swift 中表示為 0xCC6699蜒程。然后利用按位與運算符(&)和按位右移運算符(>>)從這個顏色值中分解出紅(CC)绅你、綠(66)以及藍(99)三個部分。
紅色部分是通過對 0xCC6699 和 0xFF0000 進行按位與運算后得到的昭躺。0xFF0000 中的 0 部分“掩蓋”了 OxCC6699 中的第二忌锯、第三個字節(jié),使得數值中的 6699 被忽略领炫,只留下 0xCC0000偶垮。
然后,再將這個數按向右移動 16 位(>> 16)帝洪。十六進制中每兩個字符表示 8 個比特位似舵,所以移動 16 位后 0xCC0000 就變?yōu)?0x0000CC。這個數和0xCC是等同的葱峡,也就是十進制數值的 204砚哗。
同樣的,綠色部分通過對 0xCC6699 和 0x00FF00 進行按位與運算得到 0x006600族沃。然后將這個數向右移動 8 位频祝,得到 0x66,也就是十進制數值的 102脆淹。
最后常空,藍色部分通過對 0xCC6699 和 0x0000FF 進行按位與運算得到 0x000099。這里不需要再向右移位盖溺,所以結果為 0x99 漓糙,也就是十進制數值的 153。
- 有符號整數的移位運算
對比無符號整數烘嘱,有符號整數的移位運算相對復雜得多昆禽,這種復雜性源于有符號整數的二進制表現(xiàn)形式蝗蛙。(為了簡單起見,以下的示例都是基于 8 比特位的有符號整數的醉鳖,但是其中的原理對任何位數的有符號整數都是通用的捡硅。)
有符號整數使用第 1 個比特位(通常被稱為符號位)來表示這個數的正負。符號位為 0 代表正數盗棵,為 1 代表負數壮韭。
其余的比特位(通常被稱為數值位)存儲了實際的值。有符號正整數和無符號數的存儲方式是一樣的纹因,都是從 0 開始算起喷屋。這是值為 4 的 Int8 型整數的二進制位表現(xiàn)形式:
符號位為 0,說明這是一個正數瞭恰,另外 7 位則代表了十進制數值 4 的二進制表示屯曹。
負數的存儲方式略有不同。它存儲的值的絕對值等于 2 的 n 次方減去它的實際值(也就是數值位表示的值)惊畏,這里的 n 為數值位的比特位數恶耽。一個 8 比特位的數有 7 個比特位是數值位,所以是 2 的 7 次方陕截,即 128驳棱。
這是值為 -4 的 Int8 型整數的二進制位表現(xiàn)形式:
這次的符號位為 1,說明這是一個負數农曲,另外 7 個位則代表了數值 124(即 128 - 4)的二進制表示:
負數的表示通常被稱為二進制補碼表示。用這種方法來表示負數乍看起來有點奇怪驻债,但它有幾個優(yōu)點乳规。
首先,如果想對 -1 和 -4 進行加法運算合呐,我們只需要將這兩個數的全部 8 個比特位進行相加暮的,并且將計算結果中超出 8 位的數值丟棄:
其次,使用二進制補碼可以使負數的按位左移和右移運算得到跟正數同樣的效果淌实,即每向左移一位就將自身的數值乘以 2冻辩,每向右一位就將自身的數值除以 2。要達到此目的拆祈,對有符號整數的右移有一個額外的規(guī)則:當對整數進行按位右移運算時恨闪,遵循與無符號整數相同的規(guī)則,但是對于移位產生的空白位使用符號位進行填充放坏,而不是用 0咙咽。
這個行為可以確保有符號整數的符號位不會因為右移運算而改變,這通常被稱為算術移位淤年。
由于正數和負數的特殊存儲方式钧敞,在對它們進行右移的時候蜡豹,會使它們越來越接近 0。在移位的過程中保持符號位不變溉苛,意味著負整數在接近 0 的過程中會一直保持為負镜廉。
2.溢出運算符
在默認情況下,當向一個整數賦予超過它容量的值時愚战,Swift 默認會報錯娇唯,而不是生成一個無效的數。這個行為為我們在運算過大或著過小的數的時候提供了額外的安全性凤巨。
例如视乐,Int16 型整數能容納的有符號整數范圍是 -32768 到 32767,當為一個 Int16 型變量賦的值超過這個范圍時敢茁,系統(tǒng)就會報錯:
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767佑淀,這是 Int16 能容納的最大整數
potentialOverflow += 1
// 這里會報錯
為過大或者過小的數值提供錯誤處理,能讓我們在處理邊界值時更加靈活彰檬。
然而伸刃,也可以選擇讓系統(tǒng)在數值溢出的時候采取截斷處理,而非報錯逢倍∨趼可以使用 Swift 提供的三個溢出運算符來讓系統(tǒng)支持整數溢出運算。這些運算符都是以 & 開頭的:
- 溢出加法 &+
- 溢出減法 &-
- 溢出乘法 &*
數值溢出
數值有可能出現(xiàn)上溢或者下溢较雕。
這個示例演示了當我們對一個無符號整數使用溢出加法(&+)進行上溢運算時會發(fā)生什么:
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容納的最大整數 255
unsignedOverflow = unsignedOverflow &+ 1
// 此時 unsignedOverflow 等于 0
unsignedOverflow 被初始化為 UInt8 所能容納的最大整數(255碉哑,以二進制表示即 11111111)。然后使用了溢出加法運算符(&+)對其進行加 1 運算亮蒋。這使得它的二進制表示正好超出 UInt8 所能容納的位數扣典,也就導致了數值的溢出,如下圖所示慎玖。數值溢出后贮尖,留在 UInt8 邊界內的值是 00000000,也就是十進制數值的 0趁怔。
同樣地湿硝,當我們對一個無符號整數使用溢出減法(&-)進行下溢運算時也會產生類似的現(xiàn)象:
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容納的最小整數 0
unsignedOverflow = unsignedOverflow &- 1
// 此時 unsignedOverflow 等于 255
UInt8 型整數能容納的最小值是 0,以二進制表示即 00000000润努。當使用溢出減法運算符對其進行減 1 運算時关斜,數值會產生下溢并被截斷為 11111111, 也就是十進制數值的 255任连。
溢出也會發(fā)生在有符號整型數值上蚤吹。在對有符號整型數值進行溢出加法或溢出減法運算時,符號位也需要參與計算,正如按位左移裁着、右移運算符所描述的繁涂。
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容納的最小整數 -128
signedOverflow = signedOverflow &- 1
// 此時 signedOverflow 等于 127
Int8 型整數能容納的最小值是 -128,以二進制表示即 10000000二驰。當使用溢出減法運算符對其進行減 1 運算時扔罪,符號位被翻轉,得到二進制數值 01111111桶雀,也就是十進制數值的 127矿酵,這個值也是 Int8 型整數所能容納的最大值。
對于無符號與有符號整型數值來說矗积,當出現(xiàn)上溢時全肮,它們會從數值所能容納的最大數變成最小的數。同樣地棘捣,當發(fā)生下溢時辜腺,它們會從所能容納的最小數變成最大的數。
3.優(yōu)先級和結合性
運算符的優(yōu)先級使得一些運算符優(yōu)先于其他運算符乍恐,高優(yōu)先級的運算符會先被計算评疗。
結合性定義了相同優(yōu)先級的運算符是如何結合的,也就是說茵烈,是與左邊結合為一組百匆,還是與右邊結合為一組∥赝叮可以將這意思理解為“它們是與左邊的表達式結合的”或者“它們是與右邊的表達式結合的”加匈。
在復合表達式的運算順序中,運算符的優(yōu)先級和結合性是非常重要的仑荐。舉例來說矩动,運算符優(yōu)先級解釋了為什么下面這個表達式的運算結果會是 17。
2 + 3 % 4 * 5
// 結果是 17
如果完全從左到右進行運算释漆,則運算的過程是這樣的:
- 2 + 3 = 5
- 5 % 4 = 1
- 1 * 5 = 5
但是正確答案是 17 而不是 5。優(yōu)先級高的運算符要先于優(yōu)先級低的運算符進行計算篮迎。與 C 語言類似男图,在 Swift 中,乘法運算符(*)與取余運算符(%)的優(yōu)先級高于加法運算符(+)甜橱。因此逊笆,它們的計算順序要先于加法運算。
而乘法與取余的優(yōu)先級相同岂傲。這時為了得到正確的運算順序难裆,還需要考慮結合性。乘法與取余運算都是左結合的∧烁辏可以將這考慮成為這兩部分表達式都隱式地加上了括號:
2 + ((3 % 4) * 5)
(3 % 4) 等于 3褂痰,所以表達式相當于:
2 + (3 * 5)
3 * 5 等于 15,所以表達式相當于:
2 + 15
因此計算結果為 17症虑。
4.運算符函數
類和結構體可以為現(xiàn)有的運算符提供自定義的實現(xiàn)缩歪,這通常被稱為運算符重載。
下面的例子展示了如何為自定義的結構體實現(xiàn)加法運算符(+)谍憔。算術加法運算符是一個雙目運算符匪蝙,因為它可以對兩個值進行運算,同時它還是中綴運算符习贫,因為它出現(xiàn)在兩個值中間逛球。
例子中定義了一個名為 Vector2D 的結構體用來表示二維坐標向量 (x, y),緊接著定義了一個可以對兩個 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 結構體聲明內屋厘。而算術加法運算符是雙目運算符,所以這個運算符函數接收兩個類型為 Vector2D 的參數月而,同時有一個 Vector2D 類型的返回值汗洒。
在這個實現(xiàn)中,輸入參數分別被命名為 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)
這個例子實現(xiàn)兩個向量 (3.0,1.0) 和 (2.0肝集,4.0) 的相加瞻坝,并得到新的向量 (5.0,5.0)杏瞻。這個過程如下圖示:
前綴和后綴運算符
上個例子演示了一個雙目中綴運算符的自定義實現(xiàn)所刀。類與結構體也能提供標準單目運算符的實現(xiàn)。單目運算符只運算一個值捞挥。當運算符出現(xiàn)在值之前時浮创,它就是前綴的(例如 -a),而當它出現(xiàn)在值之后時砌函,它就是后綴的(例如 b!)斩披。
要實現(xiàn)前綴或者后綴運算符溜族,需要在聲明運算符函數的時候在 func 關鍵字之前指定 prefix 或者 postfix 修飾符:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
這段代碼為 Vector2D 類型實現(xiàn)了單目負號運算符。由于該運算符是前綴運算符垦沉,所以這個函數需要加上 prefix 修飾符煌抒。
對于簡單數值,單目負號運算符可以對它們的正負性進行改變乡话。對于 Vector2D 來說摧玫,該運算將其 x 和 y 屬性的正負性都進行了改變:
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一個值為 (-3.0, -4.0) 的 Vector2D 實例
let alsoPositive = -negative
// alsoPositive 是一個值為 (3.0, 4.0) 的 Vector2D 實例
復合賦值運算符
復合賦值運算符將賦值運算符(=)與其它運算符進行結合。例如绑青,將加法與賦值結合成加法賦值運算符(+=)诬像。在實現(xiàn)的時候,需要把運算符的左參數設置成 inout 類型闸婴,因為這個參數的值會在運算符函數內直接被修改坏挠。
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
因為加法運算在之前已經定義過了,所以在這里無需重新定義邪乍。在這里可以直接利用現(xiàn)有的加法運算符函數降狠,用它來對左值和右值進行相加,并再次賦值給左值:
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值現(xiàn)在為 (4.0, 6.0)
注意
不能對默認的賦值運算符(=)進行重載庇楞。只有組合賦值運算符可以被重載榜配。同樣地,也無法對三目條件運算符 (a ? b : c) 進行重載吕晌。
等價運算符
自定義的類和結構體沒有對等價運算符進行默認實現(xiàn)蛋褥,等價運算符通常被稱為“相等”運算符(==)與“不等”運算符(!=)。對于自定義類型睛驳,Swift 無法判斷其是否“相等”烙心,因為“相等”的含義取決于這些自定義類型在你的代碼中所扮演的角色。
為了使用等價運算符能對自定義的類型進行判等運算乏沸,需要為其提供自定義實現(xiàn)淫茵,實現(xiàn)的方法與其它中綴運算符一樣:
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)
}
}
上述代碼實現(xiàn)了“相等”運算符(==)來判斷兩個 Vector2D 實例是否相等。對于 Vector2D 類型來說蹬跃,“相等”意味著“兩個實例的 x 屬性和 y 屬性都相等”匙瘪,這也是代碼中用來進行判等的邏輯。示例里同時也實現(xiàn)了“不等”運算符(!=)蝶缀,它簡單地將“相等”運算符的結果進行取反后返回辆苔。
現(xiàn)在我們可以使用這兩個運算符來判斷兩個 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.”
5.自定義運算符
除了實現(xiàn)標準運算符,在 Swift 中還可以聲明和實現(xiàn)自定義運算符扼劈。
新的運算符要使用 operator 關鍵字在全局作用域內進行定義,同時還要指定 prefix菲驴、infix 或者 postfix 修飾符:
prefix operator +++
上面的代碼定義了一個新的名為 +++ 的前綴運算符荐吵。對于這個運算符,在 Swift 中并沒有意義,因此我們針對 Vector2D 的實例來定義它的意義先煎。對這個示例來講贼涩,+++ 被實現(xiàn)為“前綴雙自增”運算符。它使用了前面定義的復合加法運算符來讓矩陣對自身進行相加薯蝎,從而讓 Vector2D 實例的 x 屬性和 y 屬性的值翻倍遥倦。實現(xiàn) +++ 運算符的方式如下:
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 現(xiàn)在的值為 (2.0, 8.0)
// afterDoubling 現(xiàn)在的值也為 (2.0, 8.0)
自定義中綴運算符的優(yōu)先級
每個自定義中綴運算符都屬于某個優(yōu)先級組。這個優(yōu)先級組指定了這個運算符和其他中綴運算符的優(yōu)先級和結合性占锯。
而沒有明確放入優(yōu)先級組的自定義中綴運算符會放到一個默認的優(yōu)先級組內袒哥,其優(yōu)先級高于三元運算符。
以下例子定義了一個新的自定義中綴運算符 +-消略,此運算符屬于 AdditionPrecedence 優(yōu)先組:
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 是一個 Vector2D 實例堡称,并且它的值為 (4.0, -2.0)
這個運算符把兩個向量的 x 值相加,同時用第一個向量的 y 值減去第二個向量的 y 值艺演。因為它本質上是屬于“相加型”運算符却紧,所以將它放置 + 和 - 等默認的中綴“相加型”運算符相同的優(yōu)先級組中。
注意
當定義前綴與后綴運算符的時候胎撤,我們并沒有指定優(yōu)先級晓殊。然而,如果對同一個值同時使用前綴與后綴運算符伤提,則后綴運算符會先參與運算巫俺。
6.高級運算符總結
高級運算符
// 運算符函數 運算符重載 前綴、中綴飘弧、后綴運算符
// 定義一個名為Vector2D的結構體用來表示二維坐標向量(x, y)
struct Vector2D {
var x = 0.0, y = 0.0
}
// 自定義運算符
prefix operator +++
// 自定義中綴運算符 此運算符屬于優(yōu)先組
infix operator +-: AdditionPrecedence
extension Vector2D {
// 加法運算符
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
// 前綴運算符 在func關鍵字之前指定prefix或者Postfix修飾符
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
// 復合賦值運算符 需要把做參數設置成inout類型,因為會改變做參數的值
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
// 等價運算符
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)
}
// 自定義運算符
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
// 自定義中綴運算符并且放入優(yōu)先級組中
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
var vector = Vector2D(x: 3.0, y: 1.0)
var anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
print("combinedVector: (\(combinedVector.x), \(combinedVector.y))")
let negative = -vector
print("negative: (\(negative.x), \(negative.y))")
vector += vector
print("now vector: (\(vector.x), \(vector.y))")
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.")
}
if twoThree == anotherTwoThree {
print("兩者相同")
} else {
print("兩者不同")
}
if combinedVector == twoThree {
print("兩者相同")
} else {
print("兩者不同")
}
let afterDoubing = +++anotherVector
print("afterDoubing: (\(afterDoubing.x), \(afterDoubing.y))")
print("now anotherVector: (\(anotherVector.x), \(anotherVector.y))")
print("now vector: (\(vector.x), \(vector.y))")
let plusMinusVector = vector +- anotherVector
print("plusMinusVector: (\(plusMinusVector.x), \(plusMinusVector.y))")
// 定義優(yōu)先級例子
let one = Vector2D(x: 2.0, y: 3.0)
let two = Vector2D(x: -5.0, y: 4.0)
let three = Vector2D(x: 4.0, y: -3.0)
let result = one + two +- three
print("result: (\(result.x), \(result.y))") // result:(1.0, 10.0)