類和結(jié)構(gòu)體
結(jié)構(gòu)體和類作為一種通用而又靈活的結(jié)構(gòu),成為了人們構(gòu)建代碼的基礎(chǔ)。你可以使用定義常量盾饮、變量和函數(shù)的語法,為你的結(jié)構(gòu)體和類定義屬性懒熙、添加方法丘损。
與其他編程語言所不同的是,Swift 并不要求你為自定義的結(jié)構(gòu)體和類的接口與實(shí)現(xiàn)代碼分別創(chuàng)建文件工扎。你只需在單一的文件中定義一個(gè)結(jié)構(gòu)體或者類徘钥,系統(tǒng)將會(huì)自動(dòng)生成面向其它代碼的外部接口。
注意
通常一個(gè)類的實(shí)例被稱為對象肢娘。然而相比其他語言呈础,Swift 中結(jié)構(gòu)體和類的功能更加相近,本章中所討論的大部分功能都可以用在結(jié)構(gòu)體或者類上橱健。因此,這里會(huì)使用實(shí)例這個(gè)更通用的術(shù)語拘荡。
結(jié)構(gòu)體和類對比
Swift 中結(jié)構(gòu)體和類有很多共同點(diǎn)臼节。兩者都可以:
定義屬性用于存儲(chǔ)值
定義方法用于提供功能
定義下標(biāo)操作用于通過下標(biāo)語法訪問它們的值
定義構(gòu)造器用于設(shè)置初始值
通過擴(kuò)展以增加默認(rèn)實(shí)現(xiàn)之外的功能
遵循協(xié)議以提供某種標(biāo)準(zhǔn)功能
與結(jié)構(gòu)體相比,類還有如下的附加功能:
繼承允許一個(gè)類繼承另一個(gè)類的特征
類型轉(zhuǎn)換允許在運(yùn)行時(shí)檢查和解釋一個(gè)類實(shí)例的類型
析構(gòu)器允許一個(gè)類實(shí)例釋放任何其所被分配的資源
引用計(jì)數(shù)允許對一個(gè)類的多次引用
類支持的附加功能是以增加復(fù)雜性為代價(jià)的。作為一般準(zhǔn)則犀盟,優(yōu)先使用結(jié)構(gòu)體纽哥,因?yàn)樗鼈兏菀桌斫猓瑑H在適當(dāng)或必要時(shí)才使用類吼句。實(shí)際上锅必,這意味著你的大多數(shù)自定義數(shù)據(jù)類型都會(huì)是結(jié)構(gòu)體和枚舉。更多詳細(xì)的比較參見 在結(jié)構(gòu)和類之間進(jìn)行選擇命辖。
注意
類和 actors 共享很多特性况毅。更多信息請參見 并發(fā)。
類型定義的語法
結(jié)構(gòu)體和類有著相似的定義方式尔艇。你通過 struct
關(guān)鍵字引入結(jié)構(gòu)體尔许,通過 class
關(guān)鍵字引入類,并將它們的具體定義放在一對大括號中:
struct SomeStructure {
// 在這里定義結(jié)構(gòu)體
}
class SomeClass {
// 在這里定義類
}
注意
每當(dāng)你定義一個(gè)新的結(jié)構(gòu)體或者類時(shí)终娃,你都是定義了一個(gè)新的 Swift 類型味廊。請使用
UpperCamelCase
這種方式來命名類型(如這里的SomeClass
和SomeStructure
),以便符合標(biāo)準(zhǔn) Swift 類型的大寫命名風(fēng)格(如String
棠耕,Int
和Bool
)余佛。請使用lowerCamelCase
這種方式來命名屬性和方法(如frameRate
和incrementCount
),以便和類型名區(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è)結(jié)構(gòu)體包含了名為 width
和 height
的兩個(gè)存儲(chǔ)屬性蕊退。存儲(chǔ)屬性是與結(jié)構(gòu)體或者類綁定的郊楣,并存儲(chǔ)在其中的常量或變量。當(dāng)這兩個(gè)屬性被初始化為整數(shù) 0
的時(shí)候瓤荔,它們會(huì)被推斷為 Int
類型净蚤。
在上面的示例還定義了一個(gè)名為 VideoMode
的類,用來描述視頻顯示器的某個(gè)特定視頻模式输硝。這個(gè)類包含了四個(gè)可變的存儲(chǔ)屬性今瀑。第一個(gè), resolution
,被初始化為一個(gè)新的 Resolution
結(jié)構(gòu)體的實(shí)例橘荠,屬性類型被推斷為 Resolution
屿附。新 VideoMode
實(shí)例同時(shí)還會(huì)初始化其它三個(gè)屬性,它們分別是初始值為 false
的 interlaced
(意為“非隔行視頻”)砾医,初始值為 0.0
的 frameRate
拿撩,以及值為可選 String
的 name
衣厘。因?yàn)?name
是一個(gè)可選類型如蚜,它會(huì)被自動(dòng)賦予一個(gè)默認(rèn)值 nil
,意為“沒有 name
值”影暴。
結(jié)構(gòu)體和類的實(shí)例
Resolution
結(jié)構(gòu)體和 VideoMode
類的定義僅描述了什么是 Resolution
和 VideoMode
错邦。它們并沒有描述一個(gè)特定的分辨率(resolution)或者視頻模式(video mode)。為此型宙,你需要?jiǎng)?chuàng)建結(jié)構(gòu)體或者類的一個(gè)實(shí)例撬呢。
創(chuàng)建結(jié)構(gòu)體和類實(shí)例的語法非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
結(jié)構(gòu)體和類都使用構(gòu)造器語法來創(chuàng)建新的實(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
引用 someResolution
的 width
屬性安疗,返回 width
的初始值 0
。
你也可以訪問子屬性够委,如 VideoMode
中 resolution
屬性的 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"
結(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)體和枚舉是值類型
值類型是這樣一種類型脐雪,當(dāng)它被賦值給一個(gè)變量厌小、常量或者被傳遞給一個(gè)函數(shù)的時(shí)候,其值會(huì)被拷貝战秋。
在之前的章節(jié)中璧亚,你已經(jīng)大量使用了值類型。實(shí)際上,Swift 中所有的基本類型:整數(shù)(integer)癣蟋、浮點(diǎn)數(shù)(floating-point number)透硝、布爾值(boolean)、字符串(string)疯搅、數(shù)組(array)和字典(dictionary)濒生,都是值類型,其底層也是使用結(jié)構(gòu)體實(shí)現(xiàn)的幔欧。
Swift 中所有的結(jié)構(gòu)體和枚舉類型都是值類型罪治。這意味著它們的實(shí)例,以及實(shí)例中所包含的任何值類型的屬性礁蔗,在代碼中傳遞的時(shí)候都會(huì)被復(fù)制觉义。
注意
標(biāo)準(zhǔn)庫定義的集合,例如數(shù)組浴井,字典和字符串晒骇,都對復(fù)制進(jìn)行了優(yōu)化以降低性能成本。新集合不會(huì)立即復(fù)制磺浙,而是跟原集合共享同一份內(nèi)存洪囤,共享同樣的元素。在集合的某個(gè)副本要被修改前撕氧,才會(huì)復(fù)制它的元素瘤缩。而你在代碼中看起來就像是立即發(fā)生了復(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)體富腊,所以會(huì)先創(chuàng)建一個(gè)現(xiàn)有實(shí)例的副本,然后將副本賦值給 cinema
域帐。盡管 hd
和 cinema
有著相同的寬(width)和高(height)赘被,但是在幕后它們是兩個(gè)完全不同的實(shí)例。
下面肖揣,為了符合數(shù)碼影院放映的需求(2048
像素寬民假,1080
像素高),cinema
的 width
屬性被修改為稍微寬一點(diǎn)的 2K 標(biāo)準(zhǔn):
cinema.width = 2048
查看 cinema
的 width
屬性龙优,它的值確實(shí)改為了 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í),hd
中所存儲(chǔ)的值會(huì)拷貝到新的 cinema
實(shí)例中。結(jié)果就是兩個(gè)完全獨(dú)立的實(shí)例包含了相同的數(shù)值野舶。由于兩者相互獨(dú)立易迹,因此將 cinema
的 width
修改為 2048
并不會(huì)影響 hd
中的 width
的值,如下圖所示:
枚舉也遵循相同的行為準(zhǔn)則:
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"
當(dāng) 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í)例。它的視頻模式(video mode)被賦值為之前創(chuàng)建的 HD 分辨率(1920
*1080
)的一個(gè)拷貝衅胀。然后將它設(shè)置為隔行視頻岔乔,名字設(shè)為 “1080i”
,并將幀率設(shè)置為 25.0
幀每秒滚躯。
接下來雏门,將 tenEighty
賦值給一個(gè)名為 alsoTenEighty
的新常量,并修改 alsoTenEighty
的幀率:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因?yàn)轭愂且妙愋偷停?tenEight
和 alsoTenEight
實(shí)際上引用的是同一個(gè) VideoMode
實(shí)例茁影。換句話說,它們是同一個(gè)實(shí)例的兩種叫法丧凤,如下圖所示:
通過查看 tenEighty
的 frameRate
屬性募闲,可以看到它正確地顯示了底層的 VideoMode
實(shí)例的新幀率 30.0
:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"
這個(gè)例子也顯示了為何引用類型更加難以理解。如果 tenEighty
和 alsoTenEighty
在你代碼中的位置相距很遠(yuǎn)愿待,那么就很難找到所有修改視頻模式的地方浩螺。無論在哪使用 tenEighty
,你都要考慮使用 alsoTenEighty
的代碼仍侥,反之亦然要出。相反,值類型就更容易理解了农渊,因?yàn)槟愕脑创a中與同一個(gè)值交互的代碼都很近患蹂。
需要注意的是 tenEighty
和 alsoTenEighty
被聲明為常量而不是變量。然而你依然可以改變 tenEighty.frameRate
和 alsoTenEighty.frameRate
,這是因?yàn)?tenEighty
和 alsoTenEighty
這兩個(gè)常量的值并未改變况脆。它們并不“存儲(chǔ)”這個(gè) VideoMode
實(shí)例饭宾,而僅僅是對 VideoMode
實(shí)例的引用。所以格了,改變的是底層 VideoMode
實(shí)例的 frameRate
屬性看铆,而不是指向 VideoMode
的常量引用的值。
恒等運(yùn)算符
因?yàn)轭愂且妙愋褪⒛远鄠€(gè)常量和變量可能在幕后同時(shí)引用同一個(gè)類實(shí)例弹惦。(對于結(jié)構(gòu)體和枚舉來說,這并不成立悄但。因?yàn)樗鼈冏鳛橹殿愋吞囊诒毁x予到常量、變量或者傳遞到函數(shù)時(shí)檐嚣,其值總是會(huì)被拷貝助泽。)
判定兩個(gè)常量或者變量是否引用同一個(gè)類實(shí)例有時(shí)很有用。為了達(dá)到這個(gè)目的嚎京,Swift 提供了兩個(gè)恒等運(yùn)算符:
相同(
===
)不相同(
!==
)
使用這兩個(gè)運(yùn)算符檢測兩個(gè)常量或者變量是否引用了同一個(gè)實(shí)例:
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
請注意嗡贺,“相同”(用三個(gè)等號表示,===
)與“等于”(用兩個(gè)等號表示鞍帝,==
)的不同诫睬。“相同”表示兩個(gè)類類型(class type)的常量或者變量引用同一個(gè)類實(shí)例帕涌∩惴玻“等于”表示兩個(gè)實(shí)例的值“相等”或“等價(jià)”,判定時(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)存中的地址捎琐。Swift 中引用了某個(gè)引用類型實(shí)例的常量或變量会涎,與 C 語言中的指針類似,不過它并不直接指向某個(gè)內(nèi)存地址瑞凑,也不要求你使用星號(*
)來表明你在創(chuàng)建一個(gè)引用末秃。相反,Swift 中引用的定義方式與其它的常量或變量的一樣籽御。如果需要直接與指針交互练慕,你可以使用標(biāo)準(zhǔn)庫提供的指針和緩沖區(qū)類型 —— 參見 手動(dòng)管理內(nèi)存惰匙。
繼續(xù)閱讀 Swift - 屬性