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

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

注意

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

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

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

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

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

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

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

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

注意

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

定義語法

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

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

注意

在你每次定義一個(gè)新類或者結(jié)構(gòu)體的時(shí)候,實(shí)際上你是定義了一個(gè)新的 Swift 類型。因此請使用 UpperCamelCase 這種方式來命名(如 SomeClassSomeStructure 等),以便符合標(biāo)準(zhǔn) Swift 類型的大寫命名風(fēng)格(如 String,IntBool)。相反的,請使用 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?
}

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

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

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

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

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

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

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

屬性訪問

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

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"

你也可以使用點(diǎn)語法為變量屬性賦值:

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)體屬性的子屬性。上面的最后一個(gè)例子,就是直接設(shè)置了 someVideoModeresolution 屬性的 width 這個(gè)子屬性,以上操作并不需要重新為整個(gè) resolution 屬性設(shè)置新值。

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

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

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

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

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

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

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

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

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

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

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

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

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

cinema.width = 2048

這里,將會(huì)顯示 cinemawidth 屬性確已改為了 2048

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"

然而,初始的 hd 實(shí)例中 width 屬性還是 1920

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"

在將 hd 賦予給 cinema 的時(shí)候,實(shí)際上是將 hd 中所存儲(chǔ)的值進(jìn)行拷貝,然后將拷貝的數(shù)據(jù)存儲(chǔ)到新的 cinema 實(shí)例中。結(jié)果就是兩個(gè)完全獨(dú)立的實(shí)例碰巧包含有相同的數(shù)值。由于兩者相互獨(dú)立,因此將 cinemawidth 修改為 2048 并不會(huì)影響 hd 中的 width 的值。

枚舉也遵循相同的行為準(zhǔn)則:

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 的值,實(shí)際上它被賦予的是值的一個(gè)拷貝。賦值過程結(jié)束后再修改 currentDirection 的值并不影響 rememberedDirection 所儲(chǔ)存的原始值的拷貝。

類是引用類型

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

請看下面這個(gè)示例,其使用了之前定義的 VideoMode 類:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

以上示例中,聲明了一個(gè)名為 tenEighty 的常量,其引用了一個(gè) VideoMode 類的新實(shí)例。在之前的示例中,這個(gè)視頻模式(video mode)被賦予了 HD 分辨率(1920*1080)的一個(gè)拷貝(即 hd 實(shí)例)。同時(shí)設(shè)置為 interlaced,命名為 “1080i”。最后,其幀率是 25.0 幀每秒。

然后,tenEighty 被賦予名為 alsoTenEighty 的新常量,同時(shí)對 alsoTenEighty 的幀率進(jìn)行修改:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因?yàn)轭愂且妙愋?,所?tenEightalsoTenEight 實(shí)際上引用的是相同的 VideoMode 實(shí)例。換句話說,它們是同一個(gè)實(shí)例的兩種叫法。

下面,通過查看 tenEightyframeRate 屬性,我們會(huì)發(fā)現(xiàn)它正確的顯示了所引用的 VideoMode 實(shí)例的新幀率,其值為 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,因?yàn)?tenEightyalsoTenEighty 這兩個(gè)常量的值并未改變。它們并不“存儲(chǔ)”這個(gè) VideoMode 實(shí)例,而僅僅是對 VideoMode 實(shí)例的引用。所以,改變的是被引用的 VideoModeframeRate 屬性,而不是引用 VideoMode 的常量的值。

恒等運(yùn)算符

因?yàn)轭愂且妙愋停锌赡苡卸鄠€(gè)常量和變量在幕后同時(shí)引用同一個(gè)類實(shí)例。(對于結(jié)構(gòu)體和枚舉來說,這并不成立。因?yàn)樗鼈冏鳛橹殿愋?,在被賦予到常量、變量或者傳遞到函數(shù)時(shí),其值總是會(huì)被拷貝。)

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

  • 等價(jià)于(===
  • 不等價(jià)于(!==

運(yùn)用這兩個(gè)運(yùn)算符檢測兩個(gè)常量或者變量是否引用同一個(gè)實(shí)例:

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

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

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

當(dāng)你在定義你的自定義類和結(jié)構(gòu)體的時(shí)候,你有義務(wù)來決定判定兩個(gè)實(shí)例“相等”的標(biāo)準(zhǔn)。在章節(jié)等價(jià)操作符中將會(huì)詳細(xì)介紹實(shí)現(xiàn)自定義“等于”和“不等于”運(yùn)算符的流程。

指針

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

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

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

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

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

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

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

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

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

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

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

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

注意

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