鍍金池/ 教程/ iOS/ 類和結(jié)構(gòu)體
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴展
泛型參數(shù)(Generic Parameters and Arguments)
訪問控制和 protected
語句(Statements)
模式(Patterns)
WWDC 里面的那個“大炮打氣球”
關(guān)于語言參考(About the Language Reference)
語法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(A Swift Tour)
泛型
枚舉(Enumerations)
高級運算符
繼承
析構(gòu)過程
關(guān)于 Swift(About Swift)
訪問控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問題
錯誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動引用計數(shù)
Swift 里的值類型與引用類型
表達式(Expressions)
Swift 文檔修訂歷史
造個類型不是夢-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標
方法(Methods)
可選鏈式調(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

類和結(jié)構(gòu)體

1.0 翻譯:JaySurplus 校對:sg552

2.0 翻譯+校對:SkyJean

2.1 校對:shanks,2015-10-29

2.2 校對:SketchK 2016-05-13

3.0.1, shanks, 2016-11-12

4.0 校對:kemchenj 2017-09-21

4.1 翻譯+校對:mylittleswift

本頁包含內(nèi)容:

結(jié)構(gòu)體是人們構(gòu)建代碼所用的一種通用且靈活的構(gòu)造體。我們可以使用完全相同的語法規(guī)則來為類和結(jié)構(gòu)體定義屬性(常量、變量)和添加方法,從而擴展類和結(jié)構(gòu)體的功能。

與其他編程語言所不同的是,Swift 并不要求你為自定義類和結(jié)構(gòu)去創(chuàng)建獨立的接口和實現(xiàn)文件。你所要做的是在一個單一文件中定義一個類或者結(jié)構(gòu)體,系統(tǒng)將會自動生成面向其它代碼的外部接口。

注意

通常一個的實例被稱為對象。然而在 Swift 中,類和結(jié)構(gòu)體的關(guān)系要比在其他語言中更加的密切,本章中所討論的大部分功能都可以用在類和結(jié)構(gòu)體上。因此,我們會主要使用實例。

類和結(jié)構(gòu)體對比

Swift 中類和結(jié)構(gòu)體有很多共同點。共同處在于:

  • 定義屬性用于存儲值
  • 定義方法用于提供功能
  • 定義下標操作通過下標語法可以訪問它們的值
  • 定義構(gòu)造器用于生成初始化值
  • 通過擴展以增加默認實現(xiàn)的功能
  • 遵循協(xié)議以提供某種標準功能

更多信息請參見屬性,方法,下標,構(gòu)造過程,擴展,和協(xié)議。

與結(jié)構(gòu)體相比,類還有如下的附加功能:

  • 繼承允許一個類繼承另一個類的特征
  • 類型轉(zhuǎn)換允許在運行時檢查和解釋一個類實例的類型
  • 析構(gòu)器允許一個類實例釋放任何其所被分配的資源
  • 引用計數(shù)允許對一個類的多次引用

更多信息請參見繼承,類型轉(zhuǎn)換析構(gòu)過程,和自動引用計數(shù)。

注意

結(jié)構(gòu)體總是通過被復制的方式在代碼中傳遞,不使用引用計數(shù)。

定義語法

類和結(jié)構(gòu)體有著類似的定義方式。我們通過關(guān)鍵字 classstruct 來分別表示類和結(jié)構(gòu)體,并在一對大括號中定義它們的具體內(nèi)容:

class SomeClass {
    // 在這里定義類
}
struct SomeStructure {
    // 在這里定義結(jié)構(gòu)體
}

注意

在你每次定義一個新類或者結(jié)構(gòu)體的時候,實際上你是定義了一個新的 Swift 類型。因此請使用 UpperCamelCase 這種方式來命名(如 SomeClassSomeStructure 等),以便符合標準 Swift 類型的大寫命名風格(如 StringIntBool)。相反的,請使用 lowerCamelCase 這種方式為屬性和方法命名(如 framerateincrementCount),以便和類型名區(qū)分。

以下是定義結(jié)構(gòu)體和定義類的示例:

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

在上面的示例中我們定義了一個名為 Resolution 的結(jié)構(gòu)體,用來描述一個顯示器的像素分辨率。這個結(jié)構(gòu)體包含了兩個名為 widthheight 的存儲屬性。存儲屬性是被捆綁和存儲在類或結(jié)構(gòu)體中的常量或變量。當這兩個屬性被初始化為整數(shù) 0 的時候,它們會被推斷為 Int 類型。

在上面的示例中我們還定義了一個名為 VideoMode 的類,用來描述一個視頻顯示器的特定模式。這個類包含了四個變量存儲屬性。第一個是 分辨率,它被初始化為一個新的 Resolution 結(jié)構(gòu)體的實例,屬性類型被推斷為 Resolution。新 VideoMode 實例同時還會初始化其它三個屬性,它們分別是,初始值為 falseinterlaced,初始值為 0.0frameRate,以及值為可選 Stringnamename 屬性會被自動賦予一個默認值 nil,意為“沒有 name 值”,因為它是一個可選類型。

類和結(jié)構(gòu)體實例

Resolution 結(jié)構(gòu)體和 VideoMode 類的定義僅描述了什么是 ResolutionVideoMode。它們并沒有描述一個特定的分辨率(resolution)或者視頻模式(video mode)。為了描述一個特定的分辨率或者視頻模式,我們需要生成一個它們的實例。

生成結(jié)構(gòu)體和類實例的語法非常相似:

let someResolution = Resolution()
let someVideoMode = VideoMode()

結(jié)構(gòu)體和類都使用構(gòu)造器語法來生成新的實例。構(gòu)造器語法的最簡單形式是在結(jié)構(gòu)體或者類的類型名稱后跟隨一對空括號,如 Resolution()VideoMode()。通過這種方式所創(chuàng)建的類或者結(jié)構(gòu)體實例,其屬性均會被初始化為默認值。構(gòu)造過程章節(jié)會對類和結(jié)構(gòu)體的初始化進行更詳細的討論。

屬性訪問

通過使用點語法,你可以訪問實例的屬性。其語法規(guī)則是,實例名后面緊跟屬性名,兩者通過點號(.)連接:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

在上面的例子中,someResolution.width 引用 someResolutionwidth 屬性,返回 width 的初始值 0。

你也可以訪問子屬性,如 VideoModeResolution 屬性的 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 允許直接設(shè)置結(jié)構(gòu)體屬性的子屬性。上面的最后一個例子,就是直接設(shè)置了 someVideoModeresolution 屬性的 width 這個子屬性,以上操作并不需要重新為整個 resolution 屬性設(shè)置新值。

結(jié)構(gòu)體類型的成員逐一構(gòu)造器

所有結(jié)構(gòu)體都有一個自動生成的成員逐一構(gòu)造器,用于初始化新結(jié)構(gòu)體實例中成員的屬性。新實例中各個屬性的初始值可以通過屬性的名稱傳遞到成員逐一構(gòu)造器之中:

let vga = Resolution(width: 640, height: 480)

與結(jié)構(gòu)體不同,類實例沒有默認的成員逐一構(gòu)造器。構(gòu)造過程章節(jié)會對構(gòu)造器進行更詳細的討論。

結(jié)構(gòu)體和枚舉是值類型

值類型被賦予給一個變量、常量或者被傳遞給一個函數(shù)的時候,其值會被拷貝。

在之前的章節(jié)中,我們已經(jīng)大量使用了值類型。實際上,在 Swift 中,所有的基本類型:整數(shù)(Integers)、浮點數(shù)(floating-point numbers)、布爾值(Booleans)、字符串(strings)、數(shù)組(arrays)和字典(dictionaries),都是值類型,并且在底層都是以結(jié)構(gòu)體的形式所實現(xiàn)。

在 Swift 中,所有的結(jié)構(gòu)體和枚舉類型都是值類型。這意味著它們的實例,以及實例中所包含的任何值類型屬性,在代碼中傳遞的時候都會被復制。

請看下面這個示例,其使用了前一個示例中的 Resolution 結(jié)構(gòu)體:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

在以上示例中,聲明了一個名為 hd 的常量,其值為一個初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的 Resolution 實例。

然后示例中又聲明了一個名為 cinema 的變量,并將 hd 賦值給它。因為 Resolution 是一個結(jié)構(gòu)體,所以 cinema 的值其實是 hd 的一個拷貝副本,而不是 hd 本身。盡管 hdcinema 有著相同的寬(width)和高(height),但是在幕后它們是兩個完全不同的實例。

下面,為了符合數(shù)碼影院放映的需求(2048 像素寬,1080 像素高),cinemawidth 屬性需要作如下修改:

cinema.width = 2048

這里,將會顯示 cinemawidth 屬性確已改為了 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 中所存儲的值進行拷貝,然后將拷貝的數(shù)據(jù)存儲到新的 cinema 實例中。結(jié)果就是兩個完全獨立的實例碰巧包含有相同的數(shù)值。由于兩者相互獨立,因此將 cinemawidth 修改為 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 的值,實際上它被賦予的是值的一個拷貝。賦值過程結(jié)束后再修改 currentDirection 的值并不影響 rememberedDirection 所儲存的原始值的拷貝。

類是引用類型

與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數(shù)時,其值不會被拷貝。因此,引用的是已存在的實例本身而不是其拷貝。

請看下面這個示例,其使用了之前定義的 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 實例)。同時設(shè)置為 interlaced,命名為 “1080i”。最后,其幀率是 25.0 幀每秒。

然后,tenEighty 被賦予名為 alsoTenEighty 的新常量,同時對 alsoTenEighty 的幀率進行修改:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因為類是引用類型,所以 tenEightalsoTenEight 實際上引用的是相同的 VideoMode 實例。換句話說,它們是同一個實例的兩種叫法。

下面,通過查看 tenEightyframeRate 屬性,我們會發(fā)現(xiàn)它正確的顯示了所引用的 VideoMode 實例的新幀率,其值為 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"

需要注意的是 tenEightyalsoTenEighty 被聲明為常量而不是變量。然而你依然可以改變 tenEighty.frameRatealsoTenEighty.frameRate,因為 tenEightyalsoTenEighty 這兩個常量的值并未改變。它們并不“存儲”這個 VideoMode 實例,而僅僅是對 VideoMode 實例的引用。所以,改變的是被引用的 VideoModeframeRate 屬性,而不是引用 VideoMode 的常量的值。

恒等運算符

因為類是引用類型,有可能有多個常量和變量在幕后同時引用同一個類實例。(對于結(jié)構(gòu)體和枚舉來說,這并不成立。因為它們作為值類型,在被賦予到常量、變量或者傳遞到函數(shù)時,其值總是會被拷貝。)

如果能夠判定兩個常量或者變量是否引用同一個類實例將會很有幫助。為了達到這個目的,Swift 內(nèi)建了兩個恒等運算符:

  • 等價于(===
  • 不等價于(!==

運用這兩個運算符檢測兩個常量或者變量是否引用同一個實例:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance."

請注意,“等價于”(用三個等號表示,===)與“等于”(用兩個等號表示,==)的不同:

  • “等價于”表示兩個類類型(class type)的常量或者變量引用同一個類實例。
  • “等于”表示兩個實例的值“相等”或“相同”,判定時要遵照設(shè)計者定義的評判標準,因此相對于“相等”來說,這是一種更加合適的叫法。

當你在定義你的自定義類和結(jié)構(gòu)體的時候,你有義務來決定判定兩個實例“相等”的標準。在章節(jié)等價操作符中將會詳細介紹實現(xiàn)自定義“等于”和“不等于”運算符的流程。

指針

如果你有 C,C++ 或者 Objective-C 語言的經(jīng)驗,那么你也許會知道這些語言使用指針來引用內(nèi)存中的地址。一個引用某個引用類型實例的 Swift 常量或者變量,與 C 語言中的指針類似,但是并不直接指向某個內(nèi)存地址,也不要求你使用星號(*)來表明你在創(chuàng)建一個引用。Swift 中的這些引用與其它的常量或變量的定義方式相同。

類和結(jié)構(gòu)體的選擇

在你的代碼中,你可以使用類和結(jié)構(gòu)體來定義你的自定義數(shù)據(jù)類型。

然而,結(jié)構(gòu)體實例總是通過值傳遞,類實例總是通過引用傳遞。這意味兩者適用不同的任務。當你在考慮一個工程項目的數(shù)據(jù)結(jié)構(gòu)和功能的時候,你需要決定每個數(shù)據(jù)結(jié)構(gòu)是定義成類還是結(jié)構(gòu)體。

按照通用的準則,當符合一條或多條以下條件時,請考慮構(gòu)建結(jié)構(gòu)體:

  • 該數(shù)據(jù)結(jié)構(gòu)的主要目的是用來封裝少量相關(guān)簡單數(shù)據(jù)值。
  • 有理由預計該數(shù)據(jù)結(jié)構(gòu)的實例在被賦值或傳遞時,封裝的數(shù)據(jù)將會被拷貝而不是被引用。
  • 該數(shù)據(jù)結(jié)構(gòu)中儲存的值類型屬性,也應該被拷貝,而不是被引用。
  • 該數(shù)據(jù)結(jié)構(gòu)不需要去繼承另一個既有類型的屬性或者行為。

舉例來說,以下情境中適合使用結(jié)構(gòu)體:

  • 幾何形狀的大小,封裝一個 width 屬性和 height 屬性,兩者均為 Double 類型。
  • 一定范圍內(nèi)的路徑,封裝一個 start 屬性和 length 屬性,兩者均為 Int 類型。
  • 三維坐標系內(nèi)一點,封裝 x,yz 屬性,三者均為 Double 類型。

在所有其它案例中,定義一個類,生成一個它的實例,并通過引用來管理和傳遞。實際中,這意味著絕大部分的自定義數(shù)據(jù)構(gòu)造都應該是類,而非結(jié)構(gòu)體。

字符串、數(shù)組、和字典類型的賦值與復制行為

Swift 中,許多基本類型,諸如 StringArrayDictionary 類型均以結(jié)構(gòu)體的形式實現(xiàn)。這意味著被賦值給新的常量或變量,或者被傳入函數(shù)或方法中時,它們的值會被拷貝。

Objective-C 中 NSString,NSArrayNSDictionary 類型均以類的形式實現(xiàn),而并非結(jié)構(gòu)體。它們在被賦值或者被傳入函數(shù)或方法時,不會發(fā)生值拷貝,而是傳遞現(xiàn)有實例的引用。

注意

以上是對字符串、數(shù)組、字典的“拷貝”行為的描述。在你的代碼中,拷貝行為看起來似乎總會發(fā)生。然而,Swift 在幕后只在絕對必要時才執(zhí)行實際的拷貝。Swift 管理所有的值拷貝以確保性能最優(yōu)化,所以你沒必要去回避賦值來保證性能最優(yōu)化。