本章將會介紹
模塊和源文件
訪問級別
訪問控制語法
自定義類型
子類
常量、變量、屬性、下標(biāo)
構(gòu)造器
協(xié)議
擴(kuò)展
泛型
類型別名
位運算符
溢出運算符
優(yōu)先級和結(jié)合性
運算符函數(shù)
自定義運算符
訪問控制
訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別。這個特性可以讓我們隱藏代碼的一些實現(xiàn)細(xì)節(jié),并且可以為其他人可以訪問和使用的代碼提供接口。
你可以明確地給單個類型(類、結(jié)構(gòu)體、枚舉)設(shè)置訪問級別,也可以給這些類型的屬性、方法、構(gòu)造器、下標(biāo)等設(shè)置訪問級別。協(xié)議也可以被限定在一定的范圍內(nèi)使用,包括協(xié)議里的全局常量、變量和函數(shù)。
Swift 不僅提供了多種不同的訪問級別,還為某些典型場景提供了默認(rèn)的訪問級別,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實,如果只是開發(fā)一個單一目標(biāo)的應(yīng)用程序,我們完全可以不用顯式聲明代碼的訪問級別。
注意
為了簡單起見,對于代碼中可以設(shè)置訪問級別的特性(屬性、基本類型、函數(shù)等),在下面的章節(jié)中我們會稱之為“實體”。
1.模塊和源文件
Swift 中的訪問控制模型基于模塊和源文件這兩個概念。
模塊指的是獨立的代碼單元,框架或應(yīng)用程序會作為一個獨立的模塊來構(gòu)建和發(fā)布。在 Swift 中,一個模塊可以使用 import 關(guān)鍵字導(dǎo)入另外一個模塊。
在 Swift 中,Xcode 的每個目標(biāo)(例如框架或應(yīng)用程序)都被當(dāng)作獨立的模塊處理。如果你是為了實現(xiàn)某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨立的框架,這個框架就是 Swift 中的一個模塊。當(dāng)它被導(dǎo)入到某個應(yīng)用程序或者其他框架時,框架內(nèi)容都將屬于這個獨立的模塊。
源文件就是 Swift 中的源代碼文件,它通常屬于一個模塊,即一個應(yīng)用程序或者框架。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個源文件也可以包含多個類型、函數(shù)之類的定義。
2.訪問級別
Swift 為代碼中的實體提供了五種不同的訪問級別。這些訪問級別不僅與源文件中定義的實體相關(guān),同時也與源文件所屬的模塊相關(guān)。
- 開放訪問和公開訪問可以訪問同一模塊源文件中的任何實體,在模塊外也可以通過導(dǎo)入該模塊來訪問源文件里的所有實體。通常情況下,框架中的某個接口可以被任何人使用時,你可以將其設(shè)置為開放或者公開訪問。
- 內(nèi)部訪問可以訪問同一模塊源文件中的任何實體,但是不能從模塊外訪問該模塊源文件中的實體。通常情況下,某個接口只在應(yīng)用程序或框架內(nèi)部使用時,你可以將其設(shè)置為內(nèi)部訪問。
- 文件私有訪問限制實體只能被所定義的文件內(nèi)部訪問。當(dāng)需要把這些細(xì)節(jié)被整個文件使用的時候,使用文件私有訪問隱藏了一些特定功能的實現(xiàn)細(xì)節(jié)。
- 私有訪問限制實體只能在所定義的作用域內(nèi)使用。需要把這些細(xì)節(jié)被整個作用域使用的時候,使用文件私有訪問隱藏了一些特定功能的實現(xiàn)細(xì)節(jié)。
開放訪問為最高(限制最少)訪問級別,私有訪問為最低(限制最多)訪問級別。
開放訪問只作用于類類型和類的成員,它和公開訪問的區(qū)別如下:
- 公開訪問或者其他更嚴(yán)訪問級別的類,只能在它們定義的模塊內(nèi)部被繼承。
- 公開訪問或者其他更嚴(yán)訪問級別的類成員,只能在它們定義的模塊內(nèi)部的子類中重寫。
- 開放訪問的類,可以在它們定義的模塊中被繼承,也可以在引用它們的模塊中被繼承。
- 開放訪問的類成員,可以在它們定義的模塊中子類中重寫,也可以在引用它們的模塊中的子類重寫。
- 把一個類標(biāo)記為開放,顯式地表明,你認(rèn)為其他模塊中的代碼使用此類作為父類,然后你已經(jīng)設(shè)計好了你的類的代碼了。
訪問級別基本原則
Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更低(更嚴(yán)格)的實體。
例如:
- 一個公開訪問級別的變量,其類型的訪問級別不能是內(nèi)部,文件私有或是私有類型的。因為無法保證變量的類型在使用變量的地方也具有訪問權(quán)限。
- 函數(shù)的訪問級別不能高于它的參數(shù)類型和返回類型的訪問級別。因為這樣就會出現(xiàn)函數(shù)可以在任何地方被訪問,但是它的參數(shù)類型和返回類型卻不可以的情況。
默認(rèn)訪問級別
如果你不為代碼中的實體顯式指定訪問級別,那么它們默認(rèn)為 internal 級別(有一些例外情況,稍后會進(jìn)行說明)。因此,在大多數(shù)情況下,我們不需要顯式指定實體的訪問級別。
單目標(biāo)應(yīng)用程序的訪問級別
當(dāng)你編寫一個單目標(biāo)應(yīng)用程序時,應(yīng)用的所有功能都是為該應(yīng)用服務(wù),而不需要提供給其他應(yīng)用或者模塊使用,所以我們不需要明確設(shè)置訪問級別,使用默認(rèn)的訪問級別 internal 即可。但是,你也可以使用文件私有訪問或私有訪問級別,用于隱藏一些功能的實現(xiàn)細(xì)節(jié)。
框架的訪問級別
當(dāng)你開發(fā)框架時,就需要把一些對外的接口定義為開放訪問或公開訪問級別,以便使用者導(dǎo)入該框架后可以正常使用其功能。這些被你定義為對外的接口,就是這個框架的 API。
注意
框架依然會使用默認(rèn)的內(nèi)部訪問級別,也可以指定為文件私有訪問或者私有訪問級別。當(dāng)你想把某個實體作為框架的 API 的時候,需顯式為其指定開放訪問或公開訪問級別。
單元測試目標(biāo)的訪問級別
當(dāng)你的應(yīng)用程序包含單元測試目標(biāo)時,為了測試,測試模塊需要訪問應(yīng)用程序模塊中的代碼。默認(rèn)情況下只有開放訪問或公開訪問級別級別的實體才可以被其他模塊訪問。然而,如果在導(dǎo)入應(yīng)用程序模塊的語句前使用 @testable 特性,然后在允許測試的編譯設(shè)置(Build Options -> Enable Testability)下編譯這個應(yīng)用程序模塊,單元測試目標(biāo)就可以訪問應(yīng)用程序模塊中所有內(nèi)部級別的實體。
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() {}
除非專門指定,否則實體默認(rèn)的訪問級別為內(nèi)部訪問級別。這意味著在不使用修飾符顯式聲明訪問級別的情況下,SomeInternalClass 和 someInternalConstant 仍然擁有隱式的內(nèi)部訪問級別:
class SomeInternalClass {} // 隱式內(nèi)部訪問級別
var someInternalConstant = 0 // 隱式內(nèi)部訪問級別
4.自定義類型
如果想為一個自定義類型指定訪問級別,在定義類型時進(jìn)行指定即可。新類型只能在它的訪問級別限制范圍內(nèi)使用。例如,你定義了一個文件私有級別的類,那這個類就只能在定義它的源文件中使用,可以作為屬性類型、函數(shù)參數(shù)類型或者返回類型,等等。
一個類型的訪問級別也會影響到類型成員(屬性、方法、構(gòu)造器、下標(biāo))的默認(rèn)訪問級別。如果你將類型指定為私有或者文件私有級別,那么該類型的所有成員的默認(rèn)訪問級別也會變成私有或者文件私有級別。如果你將類型指定為公開或者內(nèi)部訪問級別(或者不明確指定訪問級別,而使用默認(rèn)的內(nèi)部訪問級別),那么該類型的所有成員的默認(rèn)訪問級別將是內(nèi)部訪問。
重要
上面提到,一個公開類型的所有成員的訪問級別默認(rèn)為內(nèi)部訪問級別,而不是公開級別。如果你想將某個成員指定為公開訪問級別,那么你必須顯式指定。這樣做的好處是,在你定義公共接口的時候,可以明確地選擇哪些接口是需要公開的,哪些是內(nèi)部使用的,避免不小心將內(nèi)部使用的接口公開。
public class SomePublicClass { // 顯式公開類
public var somePublicProperty = 0 // 顯式公開類成員
var someInternalProperty = 0 // 隱式內(nèi)部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
class SomeInternalClass { // 隱式內(nèi)部類
var someInternalProperty = 0 // 隱式內(nèi)部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
fileprivate class SomeFilePrivateClass { // 顯式文件私有類
func someFilePrivateMethod() {} // 隱式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
private class SomePrivateClass { // 顯式私有類
func somePrivateMethod() {} // 隱式私有類成員
}
元組類型
元組的訪問級別將由元組中訪問級別最嚴(yán)格的類型來決定。例如,如果你構(gòu)建了一個包含兩種不同類型的元組,其中一個類型為內(nèi)部訪問級別,另一個類型為私有訪問級別,那么這個元組的訪問級別為私有訪問級別。
注意
元組不同于類、結(jié)構(gòu)體、枚舉、函數(shù)那樣有單獨的定義。元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。
函數(shù)類型
函數(shù)的訪問級別根據(jù)訪問級別最嚴(yán)格的參數(shù)類型或返回類型的訪問級別來決定。但是,如果這種訪問級別不符合函數(shù)定義所在環(huán)境的默認(rèn)訪問級別,那么就需要明確地指定該函數(shù)的訪問級別。
下面的例子定義了一個名為 someFunction() 的全局函數(shù),并且沒有明確地指定其訪問級別。也許你會認(rèn)為該函數(shù)應(yīng)該擁有默認(rèn)的訪問級別 internal,但事實并非如此。事實上,如果按下面這種寫法,代碼將無法通過編譯:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數(shù)實現(xiàn)部分
}
我們可以看到,這個函數(shù)的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱自定義類型)。其中一個類的訪問級別是 internal,另一個的訪問級別是 private,所以根據(jù)元組訪問級別的原則,該元組的訪問級別是 private(元組的訪問級別與元組中訪問級別最低的類型一致)。
因為該函數(shù)返回類型的訪問級別是 private,所以你必須使用 private 修飾符,明確指定該函數(shù)的訪問級別:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數(shù)實現(xiàn)部分
}
將該函數(shù)指定為 public 或 internal,或者使用默認(rèn)的訪問級別 internal 都是錯誤的,因為如果把該函數(shù)當(dāng)做 public 或 internal 級別來使用的話,可能會無法訪問 private 級別的返回值。
枚舉類型
枚舉成員的訪問級別和該枚舉類型相同,你不能為枚舉成員單獨指定不同的訪問級別。
比如下面的例子,枚舉 CompassPoint 被明確指定為 public 級別,那么它的成員 North、South、East、West 的訪問級別同樣也是 public:
public enum CompassPoint {
case North
case South
case East
case West
}
原始值和關(guān)聯(lián)值:枚舉定義中的任何原始值或關(guān)聯(lián)值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如,你不能在一個 internal 訪問級別的枚舉中定義 private 級別的原始值類型。
嵌套類型
如果在 private 級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有 private 訪問級別。如果在 public 或者 internal 級別的類型中定義嵌套類型,那么該嵌套類型自動擁有 internal 訪問級別。如果想讓嵌套類型擁有 public 訪問級別,那么需要明確指定該嵌套類型的訪問級別。
5.子類
子類的訪問級別不得高于父類的訪問級別。例如,父類的訪問級別是 internal,子類的訪問級別就不能是 public。
此外,你可以在符合當(dāng)前訪問級別的條件下重寫任意類成員(方法、屬性、構(gòu)造器、下標(biāo)等)。
6.常量、變量、屬性、下標(biāo)
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。例如,你不能定義一個 public 級別的屬性,但是它的類型卻是 private 級別的。同樣,下標(biāo)也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標(biāo)的類型是 private 級別的,那么它們必須明確指定訪問級別為 private:
private var privateInstance = SomePrivateClass()
- Getter 和 Setter
常量、變量、屬性、下標(biāo)的 Getters 和 Setters 的訪問級別和它們所屬類型的訪問級別相同。
Setter 的訪問級別可以低于對應(yīng)的 Getter 的訪問級別,這樣就可以控制變量、屬性或下標(biāo)的讀寫權(quán)限。在 var 或 subscript 關(guān)鍵字之前,你可以通過 fileprivate(set),private(set) 或 internal(set) 為它們的寫入權(quán)限指定更低的訪問級別。
注意
這個規(guī)則同時適用于存儲型屬性和計算型屬性。即使你不明確指定存儲型屬性的 Getter 和 Setter,Swift 也會隱式地為其創(chuàng)建 Getter 和 Setter,用于訪問該屬性的后備存儲。使用 fileprivate(set),private(set) 和 internal(set) 可以改變 Setter 的訪問級別,這對計算型屬性也同樣適用。
下面的例子中定義了一個名為 TrackedString 的結(jié)構(gòu)體,它記錄了 value 屬性被修改的次數(shù):
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
TrackedString 結(jié)構(gòu)體定義了一個用于存儲 String 值的屬性 value,并將初始值設(shè)為 ""(一個空字符串)。該結(jié)構(gòu)體還定義了另一個用于存儲 Int 值的屬性 numberOfEdits,它用于記錄屬性 value 被修改的次數(shù)。這個功能通過屬性 value 的 didSet 觀察器實現(xiàn),每當(dāng)給 value 賦新值時就會調(diào)用 didSet 方法,然后將 numberOfEdits 的值加一。
結(jié)構(gòu)體 TrackedString 和它的屬性 value 均沒有顯式指定訪問級別,所以它們都擁有默認(rèn)的訪問級別 internal。但是該結(jié)構(gòu)體的 numberOfEdits 屬性使用了 private(set) 修飾符,這意味著 numberOfEdits 屬性只能在定義該結(jié)構(gòu)體的源文件中賦值。numberOfEdits 屬性的 Getter 依然是默認(rèn)的訪問級別 internal,但是 Setter 的訪問級別是 private,這表示該屬性只有在當(dāng)前的源文件中是可讀寫的,而在當(dāng)前源文件所屬的模塊中只是一個可讀的屬性。
如果你實例化 TrackedString 結(jié)構(gòu)體,并多次對 value 屬性的值進(jìn)行修改,你就會看到 numberOfEdits 的值會隨著修改次數(shù)而變化:
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”
雖然你可以在其他的源文件中實例化該結(jié)構(gòu)體并且獲取到 numberOfEdits 屬性的值,但是你不能對其進(jìn)行賦值。這一限制保護(hù)了該記錄功能的實現(xiàn)細(xì)節(jié),同時還提供了方便的訪問方式。
你可以在必要時為 Getter 和 Setter 顯式指定訪問級別。下面的例子將 TrackedString 結(jié)構(gòu)體明確指定為了 public 訪問級別。結(jié)構(gòu)體的成員(包括 numberOfEdits 屬性)擁有默認(rèn)的訪問級別 internal。你可以結(jié)合 public 和 private(set) 修飾符把結(jié)構(gòu)體中的 numberOfEdits 屬性的 Getter 的訪問級別設(shè)置為 public,而 Setter 的訪問級別設(shè)置為 private:
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
7.構(gòu)造器
自定義構(gòu)造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是必要構(gòu)造器,它的訪問級別必須和所屬類型的訪問級別相同。
如同函數(shù)或方法的參數(shù),構(gòu)造器參數(shù)的訪問級別也不能低于構(gòu)造器本身的訪問級別。
默認(rèn)構(gòu)造器
如默認(rèn)構(gòu)造器所述,Swift 會為結(jié)構(gòu)體和類提供一個默認(rèn)的無參數(shù)的構(gòu)造器,只要它們?yōu)樗写鎯π蛯傩栽O(shè)置了默認(rèn)初始值,并且未提供自定義的構(gòu)造器。
默認(rèn)構(gòu)造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public。如果一個類型被指定為 public 級別,那么默認(rèn)構(gòu)造器的訪問級別將為 internal。如果你希望一個 public 級別的類型也能在其他模塊中使用這種無參數(shù)的默認(rèn)構(gòu)造器,你只能自己提供一個 public 訪問級別的無參數(shù)構(gòu)造器。
結(jié)構(gòu)體默認(rèn)的成員逐一構(gòu)造器
如果結(jié)構(gòu)體中任意存儲型屬性的訪問級別為 private,那么該結(jié)構(gòu)體默認(rèn)的成員逐一構(gòu)造器的訪問級別就是 private。否則,這種構(gòu)造器的訪問級別依然是 internal。
如同前面提到的默認(rèn)構(gòu)造器,如果你希望一個 public 級別的結(jié)構(gòu)體也能在其他模塊中使用其默認(rèn)的成員逐一構(gòu)造器,你依然只能自己提供一個 public 訪問級別的成員逐一構(gòu)造器。
8.協(xié)議
如果想為一個協(xié)議類型明確地指定訪問級別,在定義協(xié)議時指定即可。這將限制該協(xié)議只能在適當(dāng)?shù)脑L問級別范圍內(nèi)被采納。
協(xié)議中的每一個要求都具有和該協(xié)議相同的訪問級別。你不能將協(xié)議中的要求設(shè)置為其他訪問級別。這樣才能確保該協(xié)議的所有要求對于任意采納者都將可用。
注意
如果你定義了一個 public 訪問級別的協(xié)議,那么該協(xié)議的所有實現(xiàn)也會是 public 訪問級別。這一點不同于其他類型,例如,當(dāng)類型是 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.擴(kuò)展
你可以在訪問級別允許的情況下對類、結(jié)構(gòu)體、枚舉進(jìn)行擴(kuò)展。擴(kuò)展成員具有和原始類型成員一致的訪問級別。例如,你擴(kuò)展了一個 public 或者 internal 類型,擴(kuò)展中的成員具有默認(rèn)的 internal 訪問級別,和原始類型中的成員一致 。如果你擴(kuò)展了一個 private 類型,擴(kuò)展成員則擁有默認(rèn)的 private 訪問級別。
或者,你可以明確指定擴(kuò)展的訪問級別(例如,private extension),從而給該擴(kuò)展中的所有成員指定一個新的默認(rèn)訪問級別。這個新的默認(rèn)訪問級別仍然可以被單獨指定的訪問級別所覆蓋。
通過擴(kuò)展添加協(xié)議一致性
如果你通過擴(kuò)展來采納協(xié)議,那么你就不能顯式指定該擴(kuò)展的訪問級別了。協(xié)議擁有相應(yīng)的訪問級別,并會為該擴(kuò)展中所有協(xié)議要求的實現(xiàn)提供默認(rèn)的訪問級別。
10.泛型
泛型類型或泛型函數(shù)的訪問級別取決于泛型類型或泛型函數(shù)本身的訪問級別,還需結(jié)合類型參數(shù)的類型約束的訪問級別,根據(jù)這些訪問級別中的最低訪問級別來確定。
11.類型別名
你定義的任何類型別名都會被當(dāng)作不同的類型,以便于進(jìn)行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別。例如,private 級別的類型別名可以作為 private,file-private,internal,public或者open類型的別名,但是 public 級別的類型別名只能作為 public 類型的別名,不能作為 internal,file-private,或 private 類型的別名。
注意
這條規(guī)則也適用于為滿足協(xié)議一致性而將類型別名用于關(guān)聯(lián)類型的情況。
12.訪問控制總結(jié)
訪問控制
// 訪問控制語法 修飾符open public internal fileprivate private
public class SomePublicClass { // 顯示公開類
public var somePublicProperty = 0 // 顯示公開類成員
var someInternalProperty = 0 // 隱式內(nèi)部類成員
fileprivate func someFilePrivateMethod() {} // 顯示文件私有類成員
private func somePrivateMethod() {} // 顯示私有類成員
}
// 省略internal關(guān)鍵字
class SomeInternalClass { // 隱式內(nèi)部類
var someInternalProperty = 0 // 隱式內(nèi)部類成員
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() {}
// 函數(shù)類型訪問級別由訪問級別最嚴(yán)格的參數(shù)類型或返回類型的訪問級別決定
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
return (SomeInternalClass(), SomePrivateClass())
}
// 從返回值元組的訪問級別推斷出元祖的訪問級別是private,那么函數(shù)的訪問級別應(yīng)該也是private,因此需要顯式聲明
// 子類的訪問級別不得高于父類的訪問級別,可以通過重寫為繼承來的類成員提供更高的訪問級別
/*
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
運行起來是錯誤的
*/
// 如果常量、變量、屬性、下標(biāo)的類型是 private 級別的,那么它們必須明確指定訪問級別為 private:
private var privateInstance = SomePrivateClass()
// Setter 的訪問級別可以低于對應(yīng)的 Getter 的訪問級別,這樣就可以控制變量、屬性或下標(biāo)的讀寫權(quán)限。在 var 或 subscript 關(guān)鍵字之前,你可以通過 fileprivate(set),private(set) 或 internal(set) 為它們的寫入權(quán)限指定更低的訪問級別。
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
// 不可以設(shè)置value的值,只能獲取
高級運算符
除了在之前介紹過的基本運算符,Swift 中還有許多可以對數(shù)值進(jìn)行復(fù)雜運算的高級運算符。這些高級運算符包含了在 C 和 Objective-C 中已經(jīng)被大家所熟知的位運算符和移位運算符。
與 C 語言中的算術(shù)運算符不同,Swift 中的算術(shù)運算符默認(rèn)是不會溢出的。所有溢出行為都會被捕獲并報告為錯誤。如果想讓系統(tǒng)允許溢出行為,可以選擇使用 Swift 中另一套默認(rèn)支持溢出的運算符,比如溢出加法運算符(&+)。所有的這些溢出運算符都是以 & 開頭的。
自定義結(jié)構(gòu)體、類和枚舉時,如果也為它們提供標(biāo)準(zhǔn) Swift 運算符的實現(xiàn),將會非常有用。在 Swift 中自定義運算符非常簡單,運算符也會針對不同類型使用對應(yīng)實現(xiàn)。
我們不用被預(yù)定義的運算符所限制。在 Swift 中可以自由地定義中綴、前綴、后綴和賦值運算符,以及相應(yīng)的優(yōu)先級與結(jié)合性。這些運算符在代碼中可以像預(yù)定義的運算符一樣使用,我們甚至可以擴(kuò)展已有的類型以支持自定義的運算符。
1.位運算符
位運算符可以操作數(shù)據(jù)結(jié)構(gòu)中每個獨立的比特位。它們通常被用在底層開發(fā)中,比如圖形編程和創(chuàng)建設(shè)備驅(qū)動。位運算符在處理外部資源的原始數(shù)據(jù)時也十分有用,比如對自定義通信協(xié)議傳輸?shù)臄?shù)據(jù)進(jìn)行編碼和解碼。
Swift 支持 C 語言中的全部位運算符,接下來會一一介紹。
按位取反運算符
按位取反運算符(~)可以對一個數(shù)值的全部比特位進(jìn)行取反:
按位取反運算符是一個前綴運算符,需要直接放在運算的數(shù)之前,并且它們之間不能添加任何空格:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b1111000
UInt8 類型的整數(shù)有 8 個比特位,可以存儲 0 ~ 255 之間的任意整數(shù)。這個例子初始化了一個 UInt8 類型的整數(shù),并賦值為二進(jìn)制的 00001111,它的前 4 位都為 0,后 4 位都為 1。這個值等價于十進(jìn)制的 15。
接著使用按位取反運算符創(chuàng)建了一個名為 invertedBits 的常量,這個常量的值與全部位取反后的 initialBits 相等。即所有的 0 都變成了 1,同時所有的 1 都變成 0。invertedBits 的二進(jìn)制值為 11110000,等價于無符號十進(jìn)制數(shù)的 240。
按位與運算符
按位與運算符(&)可以對兩個數(shù)的比特位進(jìn)行合并。它返回一個新的數(shù),只有當(dāng)兩個數(shù)的對應(yīng)位都為 1 的時候,新數(shù)的對應(yīng)位才為 1:
在下面的示例當(dāng)中,firstSixBits 和 lastSixBits 中間 4 個位的值都為 1。按位與運算符對它們進(jìn)行了運算,得到二進(jìn)制數(shù)值 00111100,等價于無符號十進(jìn)制數(shù)的 60:
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
按位或運算符
按位或運算符(|)可以對兩個數(shù)的比特位進(jìn)行比較。它返回一個新的數(shù),只要兩個數(shù)的對應(yīng)位中有任意一個為 1 時,新數(shù)的對應(yīng)位就為 1:
在下面的示例中,someBits 和 moreBits 不同的位會被設(shè)置為 1。接位或運算符對它們進(jìn)行了運算,得到二進(jìn)制數(shù)值 11111110,等價于無符號十進(jìn)制數(shù)的 254:
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
按位異或運算符
按位異或運算符(^)可以對兩個數(shù)的比特位進(jìn)行比較。它返回一個新的數(shù),當(dāng)兩個數(shù)的對應(yīng)位不相同時,新數(shù)的對應(yīng)位就為 1
在下面的示例當(dāng)中,firstBits 和 otherBits 都有一個自己的位為 1 而對方的對應(yīng)位為 0 的位。 按位異或運算符將新數(shù)的這兩個位都設(shè)置為 1,同時將其它位都設(shè)置為 0:
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
按位左移、右移運算符
按位左移運算符(<<)和按位右移運算符(>>)可以對一個數(shù)的所有位進(jìn)行指定位數(shù)的左移和右移,但是需要遵守下面定義的規(guī)則。
對一個數(shù)進(jìn)行按位左移或按位右移,相當(dāng)于對這個數(shù)進(jìn)行乘以 2 或除以 2 的運算。將一個整數(shù)左移一位,等價于將這個數(shù)乘以 2,同樣地,將一個整數(shù)右移一位,等價于將這個數(shù)除以 2。
- 無符號整數(shù)的移位運算
對無符號整數(shù)進(jìn)行移位的規(guī)則如下:
1.已經(jīng)存在的位按指定的位數(shù)進(jìn)行左移和右移。
2.任何因移動而超出整型存儲范圍的位都會被丟棄。
3.用 0 來填充移位后產(chǎn)生的空白位。
這種方法稱為邏輯移位。
以下這張圖展示了 11111111 << 1(即把 11111111 向左移動 1 位),和 11111111 >> 1(即把 11111111 向右移動 1 位)的結(jié)果。藍(lán)色的部分是被移位的,灰色的部分是被拋棄的,橙色的部分則是被填充進(jìn)來的:
下面的代碼演示了 Swift 中的移位運算:
let shiftBits: UInt8 = 4 // 即二進(jìn)制的 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001
可以使用移位運算對其他的數(shù)據(jù)類型進(jìn)行編碼和解碼:
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 的十六進(jìn)制顏色值 #CC6699,在 Swift 中表示為 0xCC6699。然后利用按位與運算符(&)和按位右移運算符(>>)從這個顏色值中分解出紅(CC)、綠(66)以及藍(lán)(99)三個部分。
紅色部分是通過對 0xCC6699 和 0xFF0000 進(jìn)行按位與運算后得到的。0xFF0000 中的 0 部分“掩蓋”了 OxCC6699 中的第二、第三個字節(jié),使得數(shù)值中的 6699 被忽略,只留下 0xCC0000。
然后,再將這個數(shù)按向右移動 16 位(>> 16)。十六進(jìn)制中每兩個字符表示 8 個比特位,所以移動 16 位后 0xCC0000 就變?yōu)?0x0000CC。這個數(shù)和0xCC是等同的,也就是十進(jìn)制數(shù)值的 204。
同樣的,綠色部分通過對 0xCC6699 和 0x00FF00 進(jìn)行按位與運算得到 0x006600。然后將這個數(shù)向右移動 8 位,得到 0x66,也就是十進(jìn)制數(shù)值的 102。
最后,藍(lán)色部分通過對 0xCC6699 和 0x0000FF 進(jìn)行按位與運算得到 0x000099。這里不需要再向右移位,所以結(jié)果為 0x99 ,也就是十進(jìn)制數(shù)值的 153。
- 有符號整數(shù)的移位運算
對比無符號整數(shù),有符號整數(shù)的移位運算相對復(fù)雜得多,這種復(fù)雜性源于有符號整數(shù)的二進(jìn)制表現(xiàn)形式。(為了簡單起見,以下的示例都是基于 8 比特位的有符號整數(shù)的,但是其中的原理對任何位數(shù)的有符號整數(shù)都是通用的。)
有符號整數(shù)使用第 1 個比特位(通常被稱為符號位)來表示這個數(shù)的正負(fù)。符號位為 0 代表正數(shù),為 1 代表負(fù)數(shù)。
其余的比特位(通常被稱為數(shù)值位)存儲了實際的值。有符號正整數(shù)和無符號數(shù)的存儲方式是一樣的,都是從 0 開始算起。這是值為 4 的 Int8 型整數(shù)的二進(jìn)制位表現(xiàn)形式:
符號位為 0,說明這是一個正數(shù),另外 7 位則代表了十進(jìn)制數(shù)值 4 的二進(jìn)制表示。
負(fù)數(shù)的存儲方式略有不同。它存儲的值的絕對值等于 2 的 n 次方減去它的實際值(也就是數(shù)值位表示的值),這里的 n 為數(shù)值位的比特位數(shù)。一個 8 比特位的數(shù)有 7 個比特位是數(shù)值位,所以是 2 的 7 次方,即 128。
這是值為 -4 的 Int8 型整數(shù)的二進(jìn)制位表現(xiàn)形式:
這次的符號位為 1,說明這是一個負(fù)數(shù),另外 7 個位則代表了數(shù)值 124(即 128 - 4)的二進(jìn)制表示:
負(fù)數(shù)的表示通常被稱為二進(jìn)制補碼表示。用這種方法來表示負(fù)數(shù)乍看起來有點奇怪,但它有幾個優(yōu)點。
首先,如果想對 -1 和 -4 進(jìn)行加法運算,我們只需要將這兩個數(shù)的全部 8 個比特位進(jìn)行相加,并且將計算結(jié)果中超出 8 位的數(shù)值丟棄:
其次,使用二進(jìn)制補碼可以使負(fù)數(shù)的按位左移和右移運算得到跟正數(shù)同樣的效果,即每向左移一位就將自身的數(shù)值乘以 2,每向右一位就將自身的數(shù)值除以 2。要達(dá)到此目的,對有符號整數(shù)的右移有一個額外的規(guī)則:當(dāng)對整數(shù)進(jìn)行按位右移運算時,遵循與無符號整數(shù)相同的規(guī)則,但是對于移位產(chǎn)生的空白位使用符號位進(jìn)行填充,而不是用 0。
這個行為可以確保有符號整數(shù)的符號位不會因為右移運算而改變,這通常被稱為算術(shù)移位。
由于正數(shù)和負(fù)數(shù)的特殊存儲方式,在對它們進(jìn)行右移的時候,會使它們越來越接近 0。在移位的過程中保持符號位不變,意味著負(fù)整數(shù)在接近 0 的過程中會一直保持為負(fù)。
2.溢出運算符
在默認(rèn)情況下,當(dāng)向一個整數(shù)賦予超過它容量的值時,Swift 默認(rèn)會報錯,而不是生成一個無效的數(shù)。這個行為為我們在運算過大或著過小的數(shù)的時候提供了額外的安全性。
例如,Int16 型整數(shù)能容納的有符號整數(shù)范圍是 -32768 到 32767,當(dāng)為一個 Int16 型變量賦的值超過這個范圍時,系統(tǒng)就會報錯:
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767,這是 Int16 能容納的最大整數(shù)
potentialOverflow += 1
// 這里會報錯
為過大或者過小的數(shù)值提供錯誤處理,能讓我們在處理邊界值時更加靈活。
然而,也可以選擇讓系統(tǒng)在數(shù)值溢出的時候采取截斷處理,而非報錯??梢允褂?Swift 提供的三個溢出運算符來讓系統(tǒng)支持整數(shù)溢出運算。這些運算符都是以 & 開頭的:
- 溢出加法 &+
- 溢出減法 &-
- 溢出乘法 &*
數(shù)值溢出
數(shù)值有可能出現(xiàn)上溢或者下溢。
這個示例演示了當(dāng)我們對一個無符號整數(shù)使用溢出加法(&+)進(jìn)行上溢運算時會發(fā)生什么:
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容納的最大整數(shù) 255
unsignedOverflow = unsignedOverflow &+ 1
// 此時 unsignedOverflow 等于 0
unsignedOverflow 被初始化為 UInt8 所能容納的最大整數(shù)(255,以二進(jìn)制表示即 11111111)。然后使用了溢出加法運算符(&+)對其進(jìn)行加 1 運算。這使得它的二進(jìn)制表示正好超出 UInt8 所能容納的位數(shù),也就導(dǎo)致了數(shù)值的溢出,如下圖所示。數(shù)值溢出后,留在 UInt8 邊界內(nèi)的值是 00000000,也就是十進(jìn)制數(shù)值的 0。
同樣地,當(dāng)我們對一個無符號整數(shù)使用溢出減法(&-)進(jìn)行下溢運算時也會產(chǎn)生類似的現(xiàn)象:
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容納的最小整數(shù) 0
unsignedOverflow = unsignedOverflow &- 1
// 此時 unsignedOverflow 等于 255
UInt8 型整數(shù)能容納的最小值是 0,以二進(jìn)制表示即 00000000。當(dāng)使用溢出減法運算符對其進(jìn)行減 1 運算時,數(shù)值會產(chǎn)生下溢并被截斷為 11111111, 也就是十進(jìn)制數(shù)值的 255。
溢出也會發(fā)生在有符號整型數(shù)值上。在對有符號整型數(shù)值進(jìn)行溢出加法或溢出減法運算時,符號位也需要參與計算,正如按位左移、右移運算符所描述的。
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容納的最小整數(shù) -128
signedOverflow = signedOverflow &- 1
// 此時 signedOverflow 等于 127
Int8 型整數(shù)能容納的最小值是 -128,以二進(jìn)制表示即 10000000。當(dāng)使用溢出減法運算符對其進(jìn)行減 1 運算時,符號位被翻轉(zhuǎn),得到二進(jìn)制數(shù)值 01111111,也就是十進(jìn)制數(shù)值的 127,這個值也是 Int8 型整數(shù)所能容納的最大值。
對于無符號與有符號整型數(shù)值來說,當(dāng)出現(xiàn)上溢時,它們會從數(shù)值所能容納的最大數(shù)變成最小的數(shù)。同樣地,當(dāng)發(fā)生下溢時,它們會從所能容納的最小數(shù)變成最大的數(shù)。
3.優(yōu)先級和結(jié)合性
運算符的優(yōu)先級使得一些運算符優(yōu)先于其他運算符,高優(yōu)先級的運算符會先被計算。
結(jié)合性定義了相同優(yōu)先級的運算符是如何結(jié)合的,也就是說,是與左邊結(jié)合為一組,還是與右邊結(jié)合為一組??梢詫⑦@意思理解為“它們是與左邊的表達(dá)式結(jié)合的”或者“它們是與右邊的表達(dá)式結(jié)合的”。
在復(fù)合表達(dá)式的運算順序中,運算符的優(yōu)先級和結(jié)合性是非常重要的。舉例來說,運算符優(yōu)先級解釋了為什么下面這個表達(dá)式的運算結(jié)果會是 17。
2 + 3 % 4 * 5
// 結(jié)果是 17
如果完全從左到右進(jìn)行運算,則運算的過程是這樣的:
- 2 + 3 = 5
- 5 % 4 = 1
- 1 * 5 = 5
但是正確答案是 17 而不是 5。優(yōu)先級高的運算符要先于優(yōu)先級低的運算符進(jìn)行計算。與 C 語言類似,在 Swift 中,乘法運算符(*)與取余運算符(%)的優(yōu)先級高于加法運算符(+)。因此,它們的計算順序要先于加法運算。
而乘法與取余的優(yōu)先級相同。這時為了得到正確的運算順序,還需要考慮結(jié)合性。乘法與取余運算都是左結(jié)合的。可以將這考慮成為這兩部分表達(dá)式都隱式地加上了括號:
2 + ((3 % 4) * 5)
(3 % 4) 等于 3,所以表達(dá)式相當(dāng)于:
2 + (3 * 5)
3 * 5 等于 15,所以表達(dá)式相當(dāng)于:
2 + 15
因此計算結(jié)果為 17。
4.運算符函數(shù)
類和結(jié)構(gòu)體可以為現(xiàn)有的運算符提供自定義的實現(xiàn),這通常被稱為運算符重載。
下面的例子展示了如何為自定義的結(jié)構(gòu)體實現(xiàn)加法運算符(+)。算術(shù)加法運算符是一個雙目運算符,因為它可以對兩個值進(jìn)行運算,同時它還是中綴運算符,因為它出現(xiàn)在兩個值中間。
例子中定義了一個名為 Vector2D 的結(jié)構(gòu)體用來表示二維坐標(biāo)向量 (x, y),緊接著定義了一個可以對兩個 Vector2D 結(jié)構(gòu)體進(jìn)行相加的運算符函數(shù):
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)
}
}
該運算符函數(shù)被定義為 Vector2D 上的一個類方法,并且函數(shù)的名字與它要進(jìn)行重載的 + 名字一致。因為加法運算并不是一個向量必需的功能,所以這個類方法被定義在 Vector2D 的一個擴(kuò)展中,而不是 Vector2D 結(jié)構(gòu)體聲明內(nèi)。而算術(shù)加法運算符是雙目運算符,所以這個運算符函數(shù)接收兩個類型為 Vector2D 的參數(shù),同時有一個 Vector2D 類型的返回值。
在這個實現(xiàn)中,輸入?yún)?shù)分別被命名為 left 和 right,代表在 + 運算符左邊和右邊的兩個 Vector2D 實例。函數(shù)返回了一個新的 Vector2D 實例,這個實例的 x 和 y 分別等于作為參數(shù)的兩個實例的 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)。類與結(jié)構(gòu)體也能提供標(biāo)準(zhǔn)單目運算符的實現(xiàn)。單目運算符只運算一個值。當(dāng)運算符出現(xiàn)在值之前時,它就是前綴的(例如 -a),而當(dāng)它出現(xiàn)在值之后時,它就是后綴的(例如 b!)。
要實現(xiàn)前綴或者后綴運算符,需要在聲明運算符函數(shù)的時候在 func 關(guān)鍵字之前指定 prefix 或者 postfix 修飾符:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
這段代碼為 Vector2D 類型實現(xiàn)了單目負(fù)號運算符。由于該運算符是前綴運算符,所以這個函數(shù)需要加上 prefix 修飾符。
對于簡單數(shù)值,單目負(fù)號運算符可以對它們的正負(fù)性進(jìn)行改變。對于 Vector2D 來說,該運算將其 x 和 y 屬性的正負(fù)性都進(jìn)行了改變:
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 實例
復(fù)合賦值運算符
復(fù)合賦值運算符將賦值運算符(=)與其它運算符進(jìn)行結(jié)合。例如,將加法與賦值結(jié)合成加法賦值運算符(+=)。在實現(xiàn)的時候,需要把運算符的左參數(shù)設(shè)置成 inout 類型,因為這個參數(shù)的值會在運算符函數(shù)內(nèi)直接被修改。
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
因為加法運算在之前已經(jīng)定義過了,所以在這里無需重新定義。在這里可以直接利用現(xiàn)有的加法運算符函數(shù),用它來對左值和右值進(jì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)
注意
不能對默認(rèn)的賦值運算符(=)進(jìn)行重載。只有組合賦值運算符可以被重載。同樣地,也無法對三目條件運算符 (a ? b : c) 進(jìn)行重載。
等價運算符
自定義的類和結(jié)構(gòu)體沒有對等價運算符進(jìn)行默認(rèn)實現(xiàn),等價運算符通常被稱為“相等”運算符(==)與“不等”運算符(!=)。對于自定義類型,Swift 無法判斷其是否“相等”,因為“相等”的含義取決于這些自定義類型在你的代碼中所扮演的角色。
為了使用等價運算符能對自定義的類型進(jìn)行判等運算,需要為其提供自定義實現(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 屬性都相等”,這也是代碼中用來進(jìn)行判等的邏輯。示例里同時也實現(xiàn)了“不等”運算符(!=),它簡單地將“相等”運算符的結(jié)果進(jì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)標(biāo)準(zhǔn)運算符,在 Swift 中還可以聲明和實現(xiàn)自定義運算符。
新的運算符要使用 operator 關(guān)鍵字在全局作用域內(nèi)進(jìn)行定義,同時還要指定 prefix、infix 或者 postfix 修飾符:
prefix operator +++
上面的代碼定義了一個新的名為 +++ 的前綴運算符。對于這個運算符,在 Swift 中并沒有意義,因此我們針對 Vector2D 的實例來定義它的意義。對這個示例來講,+++ 被實現(xiàn)為“前綴雙自增”運算符。它使用了前面定義的復(fù)合加法運算符來讓矩陣對自身進(jì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)先級和結(jié)合性。
而沒有明確放入優(yōu)先級組的自定義中綴運算符會放到一個默認(rèn)的優(yōu)先級組內(nèi),其優(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 值。因為它本質(zhì)上是屬于“相加型”運算符,所以將它放置 + 和 - 等默認(rèn)的中綴“相加型”運算符相同的優(yōu)先級組中。
注意
當(dāng)定義前綴與后綴運算符的時候,我們并沒有指定優(yōu)先級。然而,如果對同一個值同時使用前綴與后綴運算符,則后綴運算符會先參與運算。
6.高級運算符總結(jié)
高級運算符
// 運算符函數(shù) 運算符重載 前綴、中綴、后綴運算符
// 定義一個名為Vector2D的結(jié)構(gòu)體用來表示二維坐標(biāo)向量(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關(guān)鍵字之前指定prefix或者Postfix修飾符
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
// 復(fù)合賦值運算符 需要把做參數(shù)設(shè)置成inout類型,因為會改變做參數(shù)的值
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)