# 結構體和類
*結構體*和*類*作為一種通用而又靈活的結構,成為了人們構建代碼的基礎。你可以使用定義常量、變量和函數的語法,為你的結構體和類定義屬性、添加方法。
與其他編程語言所不同的是,Swift 并不要求你為自定義的結構體和類的接口與實現代碼分別創建文件。你只需在單一的文件中定義一個結構體或者類,系統將會自動生成面向其它代碼的外部接口。
> 注意
>
> 通常一個*類*的實例被稱為*對象*。然而相比其他語言,Swift 中結構體和類的功能更加相近,本章中所討論的大部分功能都可以用在結構體或者類上。因此,這里會使用*實例*這個更通用的術語。
## 結構體和類對比 {#comparing-structures-and-classes}
Swift 中結構體和類有很多共同點。兩者都可以:
* 定義屬性用于存儲值
* 定義方法用于提供功能
* 定義下標操作用于通過下標語法訪問它們的值
* 定義構造器用于設置初始值
* 通過擴展以增加默認實現之外的功能
* 遵循協議以提供某種標準功能
更多信息請參見 [屬性](./10_Properties.md)、[方法](./11_Methods.md)、[下標](./12_Subscripts.md)、[構造過程](./14_Initialization.md)、[擴展](./20_Extensions.md) 和 [協議](./21_Protocols.md)。
與結構體相比,類還有如下的附加功能:
* 繼承允許一個類繼承另一個類的特征
* 類型轉換允許在運行時檢查和解釋一個類實例的類型
* 析構器允許一個類實例釋放任何其所被分配的資源
* 引用計數允許對一個類的多次引用
更多信息請參見 [繼承](./13_Inheritance.md)、[類型轉換](./18_Type_Casting.md)、[析構過程](./15_Deinitialization.md) 和 [自動引用計數](./24_Automatic_Reference_Counting.md)。
類支持的附加功能是以增加復雜性為代價的。作為一般準則,優先使用結構體,因為它們更容易理解,僅在適當或必要時才使用類。實際上,這意味著你的大多數自定義數據類型都會是結構體和枚舉。更多詳細的比較參見 [在結構和類之間進行選擇](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)。
### 類型定義的語法 {#definition-syntax}
結構體和類有著相似的定義方式。你通過 `struct` 關鍵字引入結構體,通過 `class` 關鍵字引入類,并將它們的具體定義放在一對大括號中:
```swift
struct SomeStructure {
// 在這里定義結構體
}
class SomeClass {
// 在這里定義類
}
```
> 注意
>
> 每當你定義一個新的結構體或者類時,你都是定義了一個新的 Swift 類型。請使用 `UpperCamelCase` 這種方式來命名類型(如這里的 `SomeClass` 和 `SomeStructure`),以便符合標準 Swift 類型的大寫命名風格(如 `String`,`Int` 和 `Bool`)。請使用 `lowerCamelCase` 這種方式來命名屬性和方法(如 `frameRate` 和 `incrementCount`),以便和類型名區分。
以下是定義結構體和定義類的示例:
```swift
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` 結構體的實例,屬性類型被推斷為 `Resolution`。新 `VideoMode` 實例同時還會初始化其它三個屬性,它們分別是初始值為 `false` 的 `interlaced`(意為“非隔行視頻”),初始值為 `0.0` 的 `frameRate`,以及值為可選 `String` 的 `name`。因為 `name` 是一個可選類型,它會被自動賦予一個默認值 `nil`,意為“沒有 `name` 值”。
### 結構體和類的實例 {#class-and-structure-instances}
`Resolution` 結構體和 `VideoMode` 類的定義僅描述了什么是 `Resolution` 和 `VideoMode`。它們并沒有描述一個特定的分辨率(resolution)或者視頻模式(video mode)。為此,你需要創建結構體或者類的一個實例。
創建結構體和類實例的語法非常相似:
```swift
let someResolution = Resolution()
let someVideoMode = VideoMode()
```
結構體和類都使用構造器語法來創建新的實例。構造器語法的最簡單形式是在結構體或者類的類型名稱后跟隨一對空括號,如 `Resolution()` 或 `VideoMode()`。通過這種方式所創建的類或者結構體實例,其屬性均會被初始化為默認值。[構造過程](./14_Initialization.md) 章節會對類和結構體的初始化進行更詳細的討論。
### 屬性訪問 {#accessing-properties}
你可以通過使用*點語法*訪問實例的屬性。其語法規則是,實例名后面緊跟屬性名,兩者以點號(`.`)分隔,不帶空格:
```swift
print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"
```
在上面的例子中,`someResolution.width` 引用 `someResolution` 的 `width` 屬性,返回 `width` 的初始值 `0`。
你也可以訪問子屬性,如 `VideoMode` 中 `resolution` 屬性的 `width` 屬性:
```swift
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"
```
你也可以使用點語法為可變屬性賦值:
```swift
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280"
```
### 結構體類型的成員逐一構造器 {#memberwise-initializers-for-structure-types}
所有結構體都有一個自動生成的*成員逐一構造器*,用于初始化新結構體實例中成員的屬性。新實例中各個屬性的初始值可以通過屬性的名稱傳遞到成員逐一構造器之中:
```swift
let vga = Resolution(width: 640, height: 480)
```
與結構體不同,類實例沒有默認的成員逐一構造器。[構造過程](./14_Initialization.md) 章節會對構造器進行更詳細的討論。
## 結構體和枚舉是值類型 {#structures-and-enumerations-are-value-types}
*值類型*是這樣一種類型,當它被賦值給一個變量、常量或者被傳遞給一個函數的時候,其值會被*拷貝*。
在之前的章節中,你已經大量使用了值類型。實際上,Swift 中所有的基本類型:整數(integer)、浮點數(floating-point number)、布爾值(boolean)、字符串(string)、數組(array)和字典(dictionary),都是值類型,其底層也是使用結構體實現的。
Swift 中所有的結構體和枚舉類型都是值類型。這意味著它們的實例,以及實例中所包含的任何值類型的屬性,在代碼中傳遞的時候都會被復制。
> 注意
>
> 標準庫定義的集合,例如數組,字典和字符串,都對復制進行了優化以降低性能成本。新集合不會立即復制,而是跟原集合共享同一份內存,共享同樣的元素。在集合的某個副本要被修改前,才會復制它的元素。而你在代碼中看起來就像是立即發生了復制。
請看下面這個示例,其使用了上一個示例中的 `Resolution` 結構體:
```swift
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
```
在以上示例中,聲明了一個名為 `hd` 的常量,其值為一個初始化為全高清視頻分辨率(`1920` 像素寬,`1080` 像素高)的 `Resolution` 實例。
然后示例中又聲明了一個名為 `cinema` 的變量,并將 `hd` 賦值給它。因為 `Resolution` 是一個結構體,所以會先創建一個現有實例的副本,然后將副本賦值給 `cinema` 。盡管 `hd` 和 `cinema` 有著相同的寬(width)和高(height),但是在幕后它們是兩個完全不同的實例。
下面,為了符合數碼影院放映的需求(`2048` 像素寬,`1080` 像素高),`cinema` 的 `width` 屬性被修改為稍微寬一點的 2K 標準:
```swift
cinema.width = 2048
```
查看 `cinema` 的 `width` 屬性,它的值確實改為了 `2048`:
```swift
print("cinema is now \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"
```
然而,初始的 `hd` 實例中 `width` 屬性還是 `1920`:
```swift
print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"
```
將 `hd` 賦值給 `cinema` 時,`hd` 中所存儲的*值*會拷貝到新的 `cinema` 實例中。結果就是兩個完全獨立的實例包含了相同的數值。由于兩者相互獨立,因此將 `cinema` 的 `width` 修改為 `2048` 并不會影響 `hd` 中的 `width` 的值,如下圖所示:

枚舉也遵循相同的行為準則:
```swift
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// 打印 "The current direction is north"
// 打印 "The remembered direction is west"
```
當 `rememberedDirection` 被賦予了 `currentDirection` 的值,實際上它被賦予的是值的一個拷貝。賦值過程結束后再修改 `currentDirection` 的值并不影響 `rememberedDirection` 所儲存的原始值的拷貝。
## 類是引用類型 {#classes-are-reference-types}
與值類型不同,*引用類型*在被賦予到一個變量、常量或者被傳遞到一個函數時,其值不會被拷貝。因此,使用的是已存在實例的引用,而不是其拷貝。
請看下面這個示例,其使用了之前定義的 `VideoMode` 類:
```swift
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
```
以上示例中,聲明了一個名為 `tenEighty` 的常量,并讓其引用一個 `VideoMode` 類的新實例。它的視頻模式(video mode)被賦值為之前創建的 HD 分辨率(`1920`\*`1080`)的一個拷貝。然后將它設置為隔行視頻,名字設為 `“1080i”`,并將幀率設置為 `25.0` 幀每秒。
接下來,將 `tenEighty` 賦值給一個名為 `alsoTenEighty` 的新常量,并修改 `alsoTenEighty` 的幀率:
```swift
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
```
因為類是引用類型,所以 `tenEight` 和 `alsoTenEight` 實際上引用的是同一個 `VideoMode` 實例。換句話說,它們是同一個實例的兩種叫法,如下圖所示:

通過查看 `tenEighty` 的 `frameRate` 屬性,可以看到它正確地顯示了底層的 `VideoMode` 實例的新幀率 `30.0`:
```swift
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"
```
這個例子也顯示了為何引用類型更加難以理解。如果 `tenEighty` 和 `alsoTenEighty` 在你代碼中的位置相距很遠,那么就很難找到所有修改視頻模式的地方。無論在哪使用 `tenEighty`,你都要考慮使用 `alsoTenEighty` 的代碼,反之亦然。相反,值類型就更容易理解了,因為你的源碼中與同一個值交互的代碼都很近。
需要注意的是 `tenEighty` 和 `alsoTenEighty` 被聲明為常量而不是變量。然而你依然可以改變 `tenEighty.frameRate` 和 `alsoTenEighty.frameRate`,這是因為 `tenEighty` 和 `alsoTenEighty` 這兩個常量的值并未改變。它們并不“存儲”這個 `VideoMode` 實例,而僅僅是對 `VideoMode` 實例的引用。所以,改變的是底層 `VideoMode` 實例的 `frameRate` 屬性,而不是指向 `VideoMode` 的常量引用的值。
### 恒等運算符 {#identity-operators}
因為類是引用類型,所以多個常量和變量可能在幕后同時引用同一個類實例。(對于結構體和枚舉來說,這并不成立。因為它們作為值類型,在被賦予到常量、變量或者傳遞到函數時,其值總是會被拷貝。)
判定兩個常量或者變量是否引用同一個類實例有時很有用。為了達到這個目的,Swift 提供了兩個恒等運算符:
* 相同(`===`)
* 不相同(`!==`)
使用這兩個運算符檢測兩個常量或者變量是否引用了同一個實例:
```swift
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
```
請注意,“相同”(用三個等號表示,`===`)與“等于”(用兩個等號表示,`==`)的不同。“相同”表示兩個類類型(class type)的常量或者變量引用同一個類實例。“等于”表示兩個實例的值“相等”或“等價”,判定時要遵照設計者定義的評判標準。
當在定義你的自定義結構體和類的時候,你有義務來決定判定兩個實例“相等”的標準。在章節 [等價操作符](./27_Advanced_Operators.md#equivalence-operators) 中將會詳細介紹實現自定義 == 和 != 運算符的流程。
### 指針 {#pointers}
如果你有 C,C++ 或者 Objective-C 語言的經驗,那么你也許會知道這些語言使用*指針*來引用內存中的地址。Swift 中引用了某個引用類型實例的常量或變量,與 C 語言中的指針類似,不過它并不直接指向某個內存地址,也不要求你使用星號(`*`)來表明你在創建一個引用。相反,Swift 中引用的定義方式與其它的常量或變量的一樣。如果需要直接與指針交互,你可以使用標準庫提供的指針和緩沖區類型 —— 參見 [手動管理內存](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management)。
- 1.關于 Swift
- 2.Swift 初見
- 2-1基礎部分
- 2-2基本運算符
- 2-3字符串和字符
- 2-4集合類型
- 2-5控制流
- 2-6函數
- 2-7閉包
- 2-8枚舉
- 2-9類和結構體
- 2-10屬性
- 2-11方法
- 2-12下標
- 2-13繼承
- 2-14構造過程
- 2-15析構過程
- 2-16可選鏈
- 2-17錯誤處理
- 2-18類型轉換
- 2-19嵌套類型
- 2-20擴展
- 2-21協議
- 2-22泛型
- 2-23不透明類型
- 2-24自動引用計數
- 2-25內存安全
- 2-26訪問控制
- 2-27高級運算符
- 3-1關于語言參考
- 3-2詞法結構
- 3-3類型
- 3-4表達式
- 3-5語句
- 3-6聲明
- 3-7特性
- 3-8模式
- 3-9泛型參數
- 4語法總結