> 1.0 翻譯:[JaySurplus](https://github.com/JaySurplus)?校對:[sg552](https://github.com/sg552)
>
> 2.0 翻譯+校對:[SkyJean](https://github.com/SkyJean)
本頁包含內容:
[TOC=2]
類和結構體是人們構建代碼所用的一種通用且靈活的構造體。我們可以使用完全相同的語法規則來為類和結構體定義屬性(常量、變量)和添加方法,從而擴展類和結構體的功能。
與其他編程語言所不同的是,Swift 并不要求你為自定義類和結構去創建獨立的接口和實現文件。你所要做的是在一個單一文件中定義一個類或者結構體,系統將會自動生成面向其它代碼的外部接口。
> 注意: 通常一個`類`的實例被稱為`對象`。然而在Swift 中,類和結構體的關系要比在其他語言中更加的密切,本章中所討論的大部分功能都可以用在類和結構體上。因此,我們會主要使用`實例`而不是`對象`。
### 類和結構體對比
Swift 中類和結構體有很多共同點。共同處在于:
* 定義屬性用于存儲值
* 定義方法用于提供功能
* 定義附屬腳本用于訪問值
* 定義構造器用于生成初始化值
* 通過擴展以增加默認實現的功能
* 實現協議以提供某種標準功能
更多信息請參見?[屬性](http://wiki.jikexueyuan.com/project/swift/chapter2/10_Properties.html),[方法](http://wiki.jikexueyuan.com/project/swift/chapter2/11_Methods.html),[下標腳本](http://wiki.jikexueyuan.com/project/swift/chapter2/12_Subscripts.html),[初始過程](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html),[擴展](http://wiki.jikexueyuan.com/project/swift/chapter2/20_Extensions.html),和[協議](http://wiki.jikexueyuan.com/project/swift/chapter2/21_Protocols.html)。
與結構體相比,類還有如下的附加功能:
* 繼承允許一個類繼承另一個類的特征
* 類型轉換允許在運行時檢查和解釋一個類實例的類型
* 解構器允許一個類實例釋放任何其所被分配的資源
* 引用計數允許對一個類的多次引用
更多信息請參見[繼承](http://wiki.jikexueyuan.com/project/swift/chapter2/13_Inheritance.html),[類型轉換](http://wiki.jikexueyuan.com/project/swift/chapter2/20_Type_Casting.html),[析構過程](http://wiki.jikexueyuan.com/project/swift/chapter2/15_Deinitialization),和[自動引用計數](http://wiki.jikexueyuan.com/project/swift/chapter2/16_Automatic_Reference_Counting)。
> 注意: 結構體總是通過被復制的方式在代碼中傳遞,因此請不要使用引用計數。
### 定義
類和結構體有著類似的定義方式。我們通過關鍵字`class`和`struct`來分別表示類和結構體,并在一對大括號中定義它們的具體內容:
~~~
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
~~~
> 注意: 在你每次定義一個新類或者結構體的時候,實際上你是有效地定義了一個新的 Swift 類型。因此請使用`UpperCamelCase`?這種方式來命名(如?`SomeClass`?和`SomeStructure`等),以便符合標準Swift 類型的大寫命名風格(如`String`,`Int`和`Bool`)。相反的,請使用`lowerCamelCase`這種方式為屬性和方法命名(如`framerate`和`incrementCount`),以便和類區分。
以下是定義結構體和定義類的示例:
~~~
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
~~~
在上面的示例中我們定義了一個名為`Resolution`的結構體,用來描述一個顯示器的像素分辨率。這個結構體包含了兩個名為`width`和`height`的存儲屬性。存儲屬性是捆綁和存儲在類或結構體中的常量或變量。當這兩個屬性被初始化為整數`0`的時候,它們會被推斷為`Int`類型。
在上面的示例中我們還定義了一個名為`VideoMode`的類,用來描述一個視頻顯示器的特定模式。這個類包含了四個儲存屬性變量。第一個是`分辨率`,它被初始化為一個新的`Resolution`結構體的實例,具有`Resolution`的屬性類型。新`VideoMode`實例同時還會初始化其它三個屬性,它們分別是,初始值為`false`(意為“非隔行掃描視頻”)的`interlaced`,回放幀率初始值為`0.0`的`frameRate`和值為可選`String`的`name`。`name`屬性會被自動賦予一個默認值`nil`,意為“沒有`name`值”,因為它是一個可選類型。
### 類和結構體實例
`Resolution`結構體和`VideoMode`類的定義僅描述了什么是`Resolution`和`VideoMode`。它們并沒有描述一個特定的分辨率(resolution)或者視頻模式(video mode)。為了描述一個特定的分辨率或者視頻模式,我們需要生成一個它們的實例。
生成結構體和類實例的語法非常相似:
~~~
let someResolution = Resolution()
let someVideoMode = VideoMode()
~~~
結構體和類都使用構造器語法來生成新的實例。構造器語法的最簡單形式是在結構體或者類的類型名稱后跟隨一對空括號,如`Resolution()`或`VideoMode()`。通過這種方式所創建的類或者結構體實例,其屬性均會被初始化為默認值。[構造過程](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html)章節會對類和結構體的初始化進行更詳細的討論。
### 屬性訪問
通過使用_點語法_(_dot syntax_),你可以訪問實例中所含有的屬性。其語法規則是,實例名后面緊跟屬性名,兩者通過點號(.)連接:
~~~
print("The width of someResolution is \(someResolution.width)")
// 輸出 "The width of someResolution is 0"
~~~
在上面的例子中,`someResolution.width`引用`someResolution`的`width`屬性,返回`width`的初始值`0`。
你也可以訪問子屬性,如`VideoMode`中`Resolution`屬性的`width`屬性:
~~~
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 輸出 "The width of someVideoMode is 0"
~~~
你也可以使用點語法為屬性變量賦值:
~~~
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 輸出 "The width of someVideoMode is now 1280"
~~~
> 注意: 與 Objective-C 語言不同的是,Swift 允許直接設置結構體屬性的子屬性。上面的最后一個例子,就是直接設置了`someVideoMode`中`resolution`屬性的`width`這個子屬性,以上操作并不需要重新設置`resolution`屬性。
### 結構體類型的成員逐一構造器(Memberwise Initializers for Structure Types)
所有結構體都有一個自動生成的_成員逐一構造器_,用于初始化新結構體實例中成員的屬性。新實例中各個屬性的初始值可以通過屬性的名稱傳遞到成員逐一構造器之中:
~~~
let vga = Resolution(width:640, height: 480)
~~~
與結構體不同,類實例沒有默認的成員逐一構造器。[構造過程](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html)章節會對構造器進行更詳細的討論。
## 結構體和枚舉是值類型
_值類型_被賦予給一個變量、常量或者本身被傳遞給一個函數的時候,實際上操作的是其的_拷貝_。
在之前的章節中,我們已經大量使用了值類型。實際上,在 Swift 中,所有的基本類型:整數(Integer)、浮點數(floating-point)、布爾值(Boolean)、字符串(string)、數組(array)和字典(dictionary),都是值類型,并且都是以結構體的形式在后臺所實現。
在 Swift 中,所有的結構體和枚舉類型都是值類型。這意味著它們的實例,以及實例中所包含的任何值類型屬性,在代碼中傳遞的時候都會被復制。
請看下面這個示例,其使用了前一個示例中`Resolution`結構體:
~~~
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
~~~
在以上示例中,聲明了一個名為`hd`的常量,其值為一個初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的`Resolution`實例。
然后示例中又聲明了一個名為`cinema`的變量,其值為之前聲明的`hd`。因為`Resolution`是一個結構體,所以`cinema`的值其實是`hd`的一個拷貝副本,而不是`hd`本身。盡管`hd`和`cinema`有著相同的寬(width)和高(height)屬性,但是在后臺中,它們是兩個完全不同的實例。
下面,為了符合數碼影院放映的需求(2048 像素寬,1080 像素高),`cinema`的`width`屬性需要作如下修改:
~~~
cinema.width = 2048
~~~
這里,將會顯示`cinema`的`width`屬性確已改為了`2048`:
~~~
print("cinema is now \(cinema.width) pixels wide")
// 輸出 "cinema is now 2048 pixels wide"
~~~
然而,初始的`hd`實例中`width`屬性還是`1920`:
~~~
print("hd is still \(hd.width ) pixels wide")
// 輸出 "hd is still 1920 pixels wide"
~~~
在將`hd`賦予給`cinema`的時候,實際上是將`hd`中所存儲的`值(values)`進行拷貝,然后將拷貝的數據存儲到新的`cinema`實例中。結果就是兩個完全獨立的實例碰巧包含有相同的數值。由于兩者相互獨立,因此將`cinema`的`width`修改為`2048`并不會影響`hd`中的`width`的值。
枚舉也遵循相同的行為準則:
~~~
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
print("The remembered direction is still .West")
}
// 輸出 "The remembered direction is still .West"
~~~
上例中`rememberedDirection`被賦予了`currentDirection`的值(value),實際上它被賦予的是值(value)的一個拷貝。賦值過程結束后再修改`currentDirection`的值并不影響`rememberedDirection`所儲存的原始值(value)的拷貝。
## 類是引用類型
與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數時,操作的是引用,其并不是拷貝。因此,引用的是已存在的實例本身而不是其拷貝。
請看下面這個示例,其使用了之前定義的`VideoMode`類:
~~~
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
~~~
以上示例中,聲明了一個名為`tenEighty`的常量,其引用了一個`VideoMode`類的新實例。在之前的示例中,這個視頻模式(video mode)被賦予了HD分辨率(1920*1080)的一個拷貝(`hd`)。同時設置為交錯(interlaced),命名為`“1080i”`。最后,其幀率是`25.0`幀每秒。
然后,`tenEighty`?被賦予名為`alsoTenEighty`的新常量,同時對`alsoTenEighty`的幀率進行修改:
~~~
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
~~~
因為類是引用類型,所以`tenEight`和`alsoTenEight`實際上引用的是相同的`VideoMode`實例。換句話說,它們是同一個實例的兩種叫法。
下面,通過查看`tenEighty`的`frameRate`屬性,我們會發現它正確的顯示了基本`VideoMode`實例的新幀率,其值為`30.0`:
~~~
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 輸出 "The frameRate property of theEighty is now 30.0"
~~~
需要注意的是`tenEighty`和`alsoTenEighty`被聲明為_常量((constants)_而不是變量。然而你依然可以改變`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因為這兩個常量本身不會改變。它們并不`存儲`這個`VideoMode`實例,在后臺僅僅是對`VideoMode`實例的引用。所以,改變的是被引用的基礎`VideoMode`的`frameRate`參數,而不改變常量的值。
### 恒等運算符
因為類是引用類型,有可能有多個常量和變量在后臺同時引用某一個類實例。(對于結構體和枚舉來說,這并不成立。因為它們作為值類型,在被賦予到常量、變量或者傳遞到函數時,其值總是會被拷貝。)
如果能夠判定兩個常量或者變量是否引用同一個類實例將會很有幫助。為了達到這個目的,Swift 內建了兩個恒等運算符:
* 等價于 ( === )
* 不等價于 ( !== )
以下是運用這兩個運算符檢測兩個常量或者變量是否引用同一個實例:
~~~
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//輸出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
~~~
請注意`“等價于"`(用三個等號表示,===) 與`“等于"`(用兩個等號表示,==)的不同:
* “等價于”表示兩個類類型(class type)的常量或者變量引用同一個類實例。
* “等于”表示兩個實例的值“相等”或“相同”,判定時要遵照類設計者定義定義的評判標準,因此相比于“相等”,這是一種更加合適的叫法。
當你在定義你的自定義類和結構體的時候,你有義務來決定判定兩個實例“相等”的標準。在章節[等價操作符](http://wiki.jikexueyuan.com/project/swift/chapter2/24_Advanced_Operators.html#equivalence_operators)中將會詳細介紹實現自定義“等于”和“不等于”運算符的流程。
### 指針
如果你有 C,C++ 或者 Objective-C 語言的經驗,那么你也許會知道這些語言使用_指針_來引用內存中的地址。一個 Swift 常量或者變量引用一個引用類型的實例與 C 語言中的指針類似,不同的是并不直接指向內存中的某個地址,而且也不要求你使用星號(*)來表明你在創建一個引用。Swift 中這些引用與其它的常量或變量的定義方式相同。
## 類和結構體的選擇
在你的代碼中,你可以使用類和結構體來定義你的自定義數據類型。
然而,結構體實例總是通過值傳遞,類實例總是通過引用傳遞。這意味兩者適用不同的任務。當你在考慮一個工程項目的數據構造和功能的時候,你需要決定每個數據構造是定義成類還是結構體。
按照通用的準則,當符合一條或多條以下條件時,請考慮構建結構體:
* 結構體的主要目的是用來封裝少量相關簡單數據值。
* 有理由預計一個結構體實例在賦值或傳遞時,封裝的數據將會被拷貝而不是被引用。
* 任何在結構體中儲存的值類型屬性,也將會被拷貝,而不是被引用。
* 結構體不需要去繼承另一個已存在類型的屬性或者行為。
舉例來說,以下情境中適合使用結構體:
* 幾何形狀的大小,封裝一個`width`屬性和`height`屬性,兩者均為`Double`類型。
* 一定范圍內的路徑,封裝一個`start`屬性和`length`屬性,兩者均為`Int`類型。
* 三維坐標系內一點,封裝`x`,`y`和`z`屬性,三者均為`Double`類型。
在所有其它案例中,定義一個類,生成一個它的實例,并通過引用來管理和傳遞。實際中,這意味著絕大部分的自定義數據構造都應該是類,而非結構體。
## 字符串(String)、數組(Array)、和字典(Dictionary)類型的賦值與復制行為
Swift 中`字符串(String)`,`數組(Array)`和`字典(Dictionary)`類型均以結構體的形式實現。這意味著String,Array,Dictionary類型數據被賦值給新的常量或變量,或者被傳入函數或方法中時,它們的值會發生拷貝行為(值傳遞方式)。
Objective-C中`字符串(NSString)`,`數組(NSArray)`和`字典(NSDictionary)`類型均以類的形式實現,這與Swfit中以值傳遞方式是不同的。NSString,NSArray,NSDictionary在發生賦值或者傳入函數(或方法)時,不會發生值拷貝,而是傳遞已存在實例的引用。
> 注意: 以上是對于字符串、數組、字典和其它值的`拷貝`的描述。 在你的代碼中,拷貝好像是確實是在有拷貝行為的地方產生過。然而,在 Swift 的后臺中,只有確有必要,`實際(actual)`拷貝才會被執行。Swift 管理所有的值拷貝以確保性能最優化的性能,所以你也沒有必要去避免賦值以保證最優性能。(實際賦值由系統管理優化)
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題