結(jié)構(gòu)體和類是通用的、靈活的結(jié)體聋涨,它們成為程序代碼的構(gòu)建部分。我們可以使用定義常量负乡、變量和函數(shù)的相同語(yǔ)法來定義屬性和方法牍白,以在結(jié)構(gòu)體和類中添加功能。
與其他編程語(yǔ)言不同抖棘,Swift不需要為自定義結(jié)構(gòu)體和類創(chuàng)建單獨(dú)的接口和實(shí)現(xiàn)文件茂腥。在Swift中,我們?cè)趩蝹€(gè)文件中定義結(jié)構(gòu)體或類切省,該類或結(jié)構(gòu)體的外部接口將自動(dòng)提供給其他代碼使用最岗。
注意
類的實(shí)例傳統(tǒng)上被稱為對(duì)象。但是朝捆,Swift的結(jié)構(gòu)體和類般渡,在功能性上比在其他語(yǔ)言中更接近,本章的大部分內(nèi)容描述了應(yīng)用于類或結(jié)構(gòu)體類型實(shí)例的功能芙盘。因此驯用,使用術(shù)語(yǔ)實(shí)例更為通用。
比較結(jié)構(gòu)體和類
Swift中的結(jié)構(gòu)體和類有許多共同之處儒老。兩者都可以:
- 定義屬性來存儲(chǔ)值蝴乔;
- 定義提供功能的方法;
- 定義下標(biāo)贷盲,以使用下標(biāo)語(yǔ)法訪問其值淘这;
- 定義初始值設(shè)定項(xiàng)以設(shè)置初始狀態(tài);
- 擴(kuò)展以擴(kuò)展其功能巩剖,使其功能超出默認(rèn)實(shí)現(xiàn)铝穷;
- 符合協(xié)議,以提供某種標(biāo)準(zhǔn)功能
有關(guān)詳細(xì)信息佳魔,請(qǐng)參見屬性曙聂、方法、下標(biāo)鞠鲜、初始化宁脊、擴(kuò)展和協(xié)議。
類具有結(jié)構(gòu)體不具備的其他功能:
- 繼承贤姆,使一個(gè)類能夠繼承另一個(gè)類的特性榆苞。
- 類型轉(zhuǎn)換,使我們能夠在運(yùn)行時(shí)檢查和解釋類實(shí)例的類型霞捡。
- 釋放實(shí)例坐漏,使類的實(shí)例能夠釋放它分配的任何資源。
- 引用計(jì)數(shù),允許對(duì)類實(shí)例進(jìn)行多個(gè)引用赊琳。
有關(guān)詳細(xì)信息街夭,請(qǐng)參見繼承、類型轉(zhuǎn)換躏筏、釋放實(shí)例和自動(dòng)引用計(jì)數(shù)板丽。
類支持的額外功能,是增加其復(fù)雜性為代價(jià)的趁尼。作為一個(gè)一般的指南埃碱,更偏向使用結(jié)構(gòu)體,因?yàn)樗鼈兏菀淄评砣蹩ǎ⑶以谶m當(dāng)或必要時(shí)使用類乃正。實(shí)際上,這意味著我們定義的大多數(shù)自定義數(shù)據(jù)類型將是結(jié)構(gòu)體和枚舉婶博。有關(guān)更詳細(xì)的比較瓮具,請(qǐng)參見結(jié)構(gòu)體和類之間的選擇。
定義語(yǔ)法
結(jié)構(gòu)體和類具有類似的定義語(yǔ)法凡人。引入帶有struct
關(guān)鍵字的結(jié)構(gòu)體和帶有class
關(guān)鍵字的類名党。兩者都將其整個(gè)定義放在一對(duì)大括號(hào)中:
struct SomeStructure {
// structure definition goes here
}
class SomeClass {
// class definition goes here
}
注意
無論何時(shí)定義新的結(jié)構(gòu)體或類,都要定義一個(gè)新的Swift類型挠轴。為類型提供大寫名稱(如SomeStructure
和SomeClass
)以匹配標(biāo)準(zhǔn)Swift類型(如String
传睹、Int
和Bool
)的大小寫。為屬性和方法提供小寫的melcase
名稱(如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?
}
上面的示例定義了一種稱為Resolution
的新結(jié)構(gòu)體,以描述基于像素的顯示分辨率启上。此結(jié)構(gòu)體有兩個(gè)存儲(chǔ)屬性邢隧,稱為width
和height
。存儲(chǔ)屬性是作為結(jié)構(gòu)體或類的一部分捆綁和存儲(chǔ)的常量或變量冈在。通過將這兩個(gè)屬性設(shè)置為0的初始整數(shù)值倒慧,可以推斷它們屬于Int類型。
上面的示例還定義了一個(gè)名為VideoMode
的新類包券,用于描述視頻顯示的特定視頻模式纫谅。這個(gè)類有四個(gè)變量存儲(chǔ)屬性。第一個(gè)是resolution
溅固,它用一個(gè)新的Resolution
結(jié)構(gòu)體實(shí)例初始化付秕,該實(shí)例推斷出resolution
的屬性類型。對(duì)于其他三個(gè)屬性侍郭,新的VideoMode
實(shí)例將使用interlaced
設(shè)置false(表示“非插入視頻”)盹牧、frameRate
為0.0和名為name
的可選字符串值進(jìn)行初始化俩垃。name
屬性會(huì)自動(dòng)指定一個(gè)默認(rèn)值nil
或“no name value”励幼,因?yàn)樗强蛇x類型汰寓。
結(jié)構(gòu)體和類實(shí)例
Resolution
結(jié)構(gòu)體定義和VideoMode
類定義僅描述Resolution
或VideoMode
的外觀。它們本身并不描述特定的分辨率或視頻模式苹粟。為此有滑,需要?jiǎng)?chuàng)建結(jié)構(gòu)體或類的實(shí)例。
對(duì)于結(jié)構(gòu)體和類嵌削,創(chuàng)建實(shí)例的語(yǔ)法非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
結(jié)構(gòu)體和類都對(duì)新實(shí)例使用初始值設(shè)定項(xiàng)語(yǔ)法毛好。初始化器語(yǔ)法的最簡(jiǎn)單形式是使用類或結(jié)構(gòu)體的類型名,后跟空括號(hào)苛秕,如Resolution()
或VideoMode()
肌访。這將創(chuàng)建類或結(jié)構(gòu)體的新實(shí)例,并將所有屬性初始化為其默認(rèn)值艇劫。類和結(jié)構(gòu)體初始化在初始化中有更詳細(xì)的描述吼驶。
訪問屬性
可以使用點(diǎn)語(yǔ)法訪問實(shí)例的屬性。在點(diǎn)語(yǔ)法中店煞,屬性名緊跟在實(shí)例名之后蟹演,用句點(diǎn)(.)
分隔,不帶空格:
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
在這個(gè)例子中顷蟀,someResolution.width
引用width
屬性酒请,并返回其默認(rèn)初始值0。
我們可以深入到子屬性鸣个,例如VideoMode
的resolution
屬性中的width
屬性:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"
也可以使用點(diǎn)語(yǔ)法為變量屬性指定新值:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
結(jié)構(gòu)體類型的成員式初始化方法
所有結(jié)構(gòu)體都有一個(gè)自動(dòng)生成的成員式初始化方法羞反,我們可以使用它初始化新結(jié)構(gòu)體實(shí)例的成員屬性。新實(shí)例屬性的初始值可以通過名稱傳遞給成員式初始化方法:
let vga = Resolution(width: 640, height: 480)
與結(jié)構(gòu)體不同囤萤,類實(shí)例不接收默認(rèn)的成員式初始化方法昼窗。初始化方法在初始化中有更詳細(xì)的描述。
結(jié)構(gòu)體和枚舉是值類型
值類型是一種類型阁将,它的值在賦值給變量或常量或傳遞給函數(shù)時(shí)被復(fù)制膏秫。
在前面的章節(jié)中,我們已經(jīng)廣泛地使用了值類型做盅。事實(shí)上缤削,Swift的整數(shù)、浮點(diǎn)數(shù)吹榴、布爾亭敢、字符串、數(shù)組和字典中的所有基本類型都是值類型图筹,并在系統(tǒng)中實(shí)現(xiàn)為結(jié)構(gòu)體帅刀。
所有結(jié)構(gòu)體和枚舉都是Swift中的值類型让腹。這意味著我們創(chuàng)建的任何結(jié)構(gòu)體和枚舉實(shí)例以及它們作為屬性的任何值類型在代碼中傳遞時(shí)都會(huì)被復(fù)制。
注意
由標(biāo)準(zhǔn)庫(kù)定義的集合(如數(shù)組扣溺、字典和字符串)使用優(yōu)化來降低復(fù)制的性能成本骇窍。這些集合不是立即創(chuàng)建副本,而是共享存儲(chǔ)在原始實(shí)例和任何副本之間的元素的內(nèi)存锥余。如果修改了集合的一個(gè)副本腹纳,則將在修改之前復(fù)制元素。我們?cè)诖a中看到的行為總是好像一個(gè)拷貝立即發(fā)生驱犹。
考慮這個(gè)例子嘲恍,它使用上一個(gè)例子中的Resolution
結(jié)構(gòu)體:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
本例聲明了一個(gè)名為hd
的常量,并將其設(shè)置為以全高清視頻的寬度和高度(1920像素寬×1080像素高)來初始化的Resolution
實(shí)例雄驹。
然后聲明一個(gè)名為cinema
的變量佃牛,并將其設(shè)置為hd
的當(dāng)前值。因?yàn)?code>Resolution是一個(gè)結(jié)構(gòu)體医舆,所以將生成現(xiàn)有實(shí)例的副本俘侠,并將此新副本指定給cinema
。盡管高清和影院現(xiàn)在有相同的寬度和高度彬向,但它們?cè)谀缓髤s是兩個(gè)完全不同的例子兼贡。
接下來,Resolution
的width
屬性被修改為用于數(shù)字電影院投影的略寬的2K標(biāo)準(zhǔn)的寬度(2048像素寬和1080像素高):
cinema.width = 2048
檢查cinema
的width
屬性表明它確實(shí)已更改為2048:
print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
但是娃胆,原始hd
實(shí)例的width
屬性仍然具有舊值1920:
print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"
當(dāng)給cinema
以當(dāng)前的hd
值時(shí)遍希,存儲(chǔ)在hd
中的值被復(fù)制到新的cinema
實(shí)例中。最終結(jié)果是兩個(gè)完全獨(dú)立的實(shí)例里烦,其中包含相同的數(shù)值凿蒜。但是,由于它們是獨(dú)立的實(shí)例胁黑,將cinema
寬度設(shè)置為2048不會(huì)影響hd
中存儲(chǔ)的寬度废封,如下圖所示:
同樣的行為也適用于枚舉:
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)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"
當(dāng)membereddirection
被指定currentDirection
的值時(shí),它實(shí)際上被設(shè)置為該值的一個(gè)副本丧蘸。此后更改currentDirection
的值不會(huì)影響存儲(chǔ)在rememberedDirection
中的原始值的副本漂洋。
類是引用類型
與值類型不同,當(dāng)引用類型被賦給變量或常量力喷,或者被傳遞給函數(shù)時(shí)刽漂,它們不會(huì)被復(fù)制。使用對(duì)同一現(xiàn)有實(shí)例的引用弟孟,而不是副本贝咙。
下面是一個(gè)示例,使用上面定義的VideoMode類:
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
本例聲明了一個(gè)名為tenEighty
的新常量拂募,并將其設(shè)置為引用VideoMode
類的新實(shí)例庭猩。視頻模式從之前被分配了1920×1080的HD分辨率的副本窟她。它被設(shè)置為隔行掃描,其名稱被設(shè)置為“1080i”蔼水,其幀速率被設(shè)置為每秒25.0幀震糖。
接下來,tenEighty
被分配給一個(gè)新的常量徙缴,稱為alsotenance
试伙,并且alsotenance
的幀速率被修改:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因?yàn)轭愂且妙愋停?code>tenEighty和alsoTenEighty
實(shí)際上都引用同一個(gè)VideoMode實(shí)例于样。實(shí)際上,它們只是同一個(gè)實(shí)例的兩個(gè)不同名稱潘靖,如下圖所示:
檢查tenEighty
的frameRate
屬性表明穿剖,它可以從VideoMode實(shí)例正確報(bào)告30.0的新幀速率:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
這個(gè)例子還展示了引用類型如何更難推理。如果tenEighty
和alsoTenEighty
在程序代碼中相距很遠(yuǎn)卦溢,則很難找到更改視頻模式的所有方法糊余。無論在哪里使用tenEighty
,都必須考慮使用alsoTenEighty
的代碼单寂,反之亦然贬芥。相比之下,值類型更容易推理宣决,因?yàn)榕c相同值交互的所有代碼在源文件中都很接近蘸劈。
請(qǐng)注意,tenEighty
和alsoTenEighty
被聲明為常量尊沸,而不是變量威沫。但是,我們?nèi)匀豢梢愿淖?code>tenEighty.frameRate以及alsoTenEighty.frameRate
洼专。因?yàn)?code>tenEighty和alsoTenEighty
常量本身的值實(shí)際上并沒有改變棒掠。tenEighty
和alsoTenEighty
本身并不“存儲(chǔ)”VideoMode實(shí)例,而是在幕后引用VideoMode實(shí)例屁商。改變的是底層視頻模式的frameRate
屬性烟很,而不是該視頻模式的常量引用值。
標(biāo)識(shí)運(yùn)算符
因?yàn)轭愂且妙愋屠猓远鄠€(gè)常量和變量有可能在幕后引用同一個(gè)類的單個(gè)實(shí)例雾袱。(對(duì)于結(jié)構(gòu)體和枚舉,情況并非如此帽哑,因?yàn)楫?dāng)它們被賦給常量谜酒、變量或傳遞給函數(shù)時(shí),它們總是被復(fù)制妻枕。)
有時(shí)僻族,找出兩個(gè)常量或變量是否引用了一個(gè)類的完全相同的實(shí)例會(huì)很有用粘驰。為了實(shí)現(xiàn)這一點(diǎn),Swift提供了兩個(gè)身份操作符:
- 等同于(
===
) - 不等同于(
!==
)
使用這些運(yùn)算符檢查兩個(gè)常量或變量是否引用同一個(gè)實(shí)例:
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
請(qǐng)注意述么,等同于(由三個(gè)等號(hào)表示蝌数,或===
)并不是相等(由兩個(gè)等號(hào)表示,或==
)度秘。等同于表示類 類型的兩個(gè)常量或變量引用完全相同的類實(shí)例顶伞。根據(jù)類型設(shè)計(jì)者的定義,對(duì)于相等的某些適當(dāng)含義剑梳,相等表示兩個(gè)實(shí)例在值上被認(rèn)為是相等的或相等的唆貌。
當(dāng)我們自定義結(jié)構(gòu)體和類時(shí),我們的責(zé)任是確定兩個(gè)實(shí)例是否相等垢乙。定義自己的==
和!=
運(yùn)算符的實(shí)現(xiàn)的過程在等價(jià)運(yùn)算符中描述锨咙。
指針
如果我們有C、C++或Objto-C的經(jīng)驗(yàn)追逮,我們可能知道這些語(yǔ)言使用指針來引用內(nèi)存中的地址酪刀。引用某個(gè)類型實(shí)例的Swift常量或變量類似于C中的指針,但不是指向內(nèi)存中地址的直接指針钮孵,也不需要寫星號(hào)(*)來表示正在創(chuàng)建引用骂倘。相反,這些引用的定義與Swift中的任何其他常量或變量一樣巴席。標(biāo)準(zhǔn)庫(kù)提供了指針和緩沖區(qū)類型历涝,如果需要直接與指針交互,可以使用這些類型請(qǐng)參見手動(dòng)內(nèi)存管理情妖。