類和結(jié)構(gòu)
類和結(jié)構(gòu)是通用的冈闭,靈活的構(gòu)造佃却,它們成為程序代碼的構(gòu)建塊。您可以使用與常量讳窟,變量和函數(shù)完全相同的語法來定義屬性和方法让歼,從而為您的類和結(jié)構(gòu)添加功能。
與其他編程語言不同丽啡,Swift不要求您為自定義類和結(jié)構(gòu)創(chuàng)建單獨(dú)的接口和實(shí)現(xiàn)文件谋右。在Swift中,您可以在單個(gè)文件中定義一個(gè)類或結(jié)構(gòu)补箍,并且該類或結(jié)構(gòu)的外部接口會(huì)自動(dòng)提供給其他代碼使用改执。
注意
傳統(tǒng)上將類的實(shí)例稱為對(duì)象。然而坑雅,斯威夫特類和結(jié)構(gòu)在功能上比其他語言更接近天梧,而很多本章介紹了可以應(yīng)用到的實(shí)例功能,無論是類或結(jié)構(gòu)類型霞丧。因此呢岗,使用更一般的術(shù)語實(shí)例。
比較類和結(jié)構(gòu)
Swift中的類和結(jié)構(gòu)有許多共同之處蛹尝。兩者都可以:
定義屬性以存儲(chǔ)值
定義提供功能的方法
使用下標(biāo)語法定義下標(biāo)以提供對(duì)其值的訪問
定義初始化程序以設(shè)置其初始狀態(tài)
擴(kuò)展到超出默認(rèn)實(shí)現(xiàn)范圍的功能
符合協(xié)議以提供某種標(biāo)準(zhǔn)功能
有關(guān)更多信息后豫,請(qǐng)參閱屬性,方法突那,下標(biāo)挫酿,初始化,擴(kuò)展和協(xié)議愕难。
類具有結(jié)構(gòu)不具有的其他功能:
繼承使一個(gè)類能夠繼承另一個(gè)類的特性早龟。
類型轉(zhuǎn)換使您能夠在運(yùn)行時(shí)檢查和解釋類實(shí)例的類型惫霸。
去初始化器使類的一個(gè)實(shí)例釋放它分配的任何資源。
引用計(jì)數(shù)允許對(duì)一個(gè)類實(shí)例的多個(gè)引用葱弟。
有關(guān)更多信息壹店,請(qǐng)參閱繼承,類型轉(zhuǎn)換芝加,取消初始化和自動(dòng)引用計(jì)數(shù)硅卢。
注意
結(jié)構(gòu)在代碼中傳遞時(shí)總是被復(fù)制,并且不使用引用計(jì)數(shù)藏杖。
定義語法
類和結(jié)構(gòu)具有類似的定義語法将塑。您可以使用class
關(guān)鍵字引入具有關(guān)鍵字和結(jié)構(gòu)的類struct
。兩者都將其整個(gè)定義放在一對(duì)大括號(hào)內(nèi):
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
注意
每當(dāng)你定義一個(gè)新的類或結(jié)構(gòu)時(shí)蝌麸,你都可以有效地定義一個(gè)全新的Swift類型点寥。給出類型UpperCamelCase
名稱(如SomeClass
和SomeStructure
這里)來匹配標(biāo)準(zhǔn)斯威夫特類型的資本(如String
,Int
和Bool
)来吩。相反敢辩,總是給屬性和方法lowerCamelCase
名稱(如frameRate
和incrementCount
)來區(qū)分它們和類型名稱。
下面是一個(gè)結(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è)稱為的新結(jié)構(gòu)Resolution
來描述基于像素的顯示分辨率误褪。這個(gè)結(jié)構(gòu)有兩個(gè)存儲(chǔ)的屬性,稱為width
和height
碾褂。存儲(chǔ)屬性是常量或變量兽间,它們被捆綁并存儲(chǔ)為類或結(jié)構(gòu)的一部分。通過將這兩個(gè)屬性Int
設(shè)置為初始整數(shù)值正塌,可以將這兩個(gè)屬性推斷為類型0
嘀略。
上面的例子還定義了一個(gè)新類VideoMode
,用于描述視頻顯示的特定視頻模式乓诽。這個(gè)類有四個(gè)變量存儲(chǔ)的屬性帜羊。第一個(gè),resolution
是用一個(gè)新的Resolution
結(jié)構(gòu)實(shí)例初始化的鸠天,它推斷出一個(gè)屬性類型Resolution
讼育。對(duì)于其他三個(gè)屬性,VideoMode
將使用(意思是“非隔行視頻”)interlaced
設(shè)置來初始化新實(shí)例false
稠集,播放幀速率為0.0
奶段,以及可選String
值為name
。該name
屬性會(huì)自動(dòng)給出默認(rèn)值nil
或“無name
值”剥纷,因?yàn)樗强蛇x類型痹籍。
類和結(jié)構(gòu)實(shí)例
該Resolution
結(jié)構(gòu)定義和VideoMode
類定義只說明什么Resolution
或VideoMode
看起來像。他們自己沒有描述特定的分辨率或視頻模式晦鞋。要做到這一點(diǎn)蹲缠,你需要?jiǎng)?chuàng)建一個(gè)結(jié)構(gòu)或類的實(shí)例棺克。
創(chuàng)建實(shí)例的語法對(duì)于結(jié)構(gòu)和類都非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
結(jié)構(gòu)和類都為新實(shí)例使用初始化語法。最簡(jiǎn)單的初始化語法形式使用類或名稱的結(jié)構(gòu)线定,后跟空括號(hào)娜谊,如Resolution()
或VideoMode()
。這將創(chuàng)建類或結(jié)構(gòu)的新實(shí)例渔肩,并將任何屬性初始化為默認(rèn)值因俐。類和結(jié)構(gòu)初始化在初始化中有更詳細(xì)的描述。
訪問屬性
您可以使用點(diǎn)語法訪問實(shí)例的屬性周偎。在點(diǎn)語法中抹剩,可以在實(shí)例名稱后面立即寫入屬性名稱,并用句點(diǎn)(.
)分隔蓉坎,但不帶任何空格:
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
在這個(gè)例子中澳眷,someResolution.width
指的是width
屬性someResolution
,并返回它的默認(rèn)初始值0
蛉艾。
您可以深入查看子屬性钳踊,例如a width
屬性中的resolution
屬性VideoMode
:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"
您也可以使用點(diǎn)語法為變量屬性指定一個(gè)新值:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
注意
與Objective-C不同的是,Swift使您能夠直接設(shè)置結(jié)構(gòu)屬性的子屬性勿侯。在上面的最后一個(gè)例子中拓瞪,width
屬性resolution
property someVideoMode
是直接設(shè)置的,不需要將整個(gè)resolution
屬性設(shè)置為新值助琐。
結(jié)構(gòu)類型的成員初始化程序
所有結(jié)構(gòu)都有一個(gè)自動(dòng)生成的成員初始化程序祭埂,您可以使用它初始化新結(jié)構(gòu)實(shí)例的成員屬性。新實(shí)例屬性的初始值可以按名稱傳遞給成員初始值設(shè)定項(xiàng):
let vga = Resolution(width: 640, height: 480)
與結(jié)構(gòu)不同兵钮,類實(shí)例不會(huì)接收默認(rèn)的成員初始值設(shè)定項(xiàng)蛆橡。初始化中更詳細(xì)地描述在初始化。
結(jié)構(gòu)和枚舉是值類型
甲值類型是一個(gè)類型掘譬,其值被拷貝時(shí)泰演,它被分配給一個(gè)變量或常數(shù),或當(dāng)它被傳遞給函數(shù)葱轩。
在前面的章節(jié)中睦焕,您實(shí)際上已經(jīng)廣泛使用了值類型。事實(shí)上靴拱,Swift整數(shù)复亏,浮點(diǎn)數(shù),布爾值缭嫡,字符串缔御,數(shù)組和字典中的所有基本類型都是值類型,并在后臺(tái)實(shí)現(xiàn)為結(jié)構(gòu)妇蛀。
所有結(jié)構(gòu)和枚舉都是Swift中的值類型耕突。這意味著您創(chuàng)建的任何結(jié)構(gòu)和枚舉實(shí)例以及它們具有的任何值類型都會(huì)在您的代碼中傳遞時(shí)始終進(jìn)行復(fù)制笤成。
考慮這個(gè)例子,它使用Resolution
了前面例子中的結(jié)構(gòu):
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
此示例聲明了一個(gè)常量hd
眷茁,并將其設(shè)置為使用Resolution
全高清視頻的寬度和高度(1920
像素寬1080
高像素)初始化的實(shí)例炕泳。
然后它聲明一個(gè)變量cinema
,并將其設(shè)置為當(dāng)前值hd
上祈。因?yàn)?code>Resolution是一個(gè)結(jié)構(gòu)培遵,現(xiàn)有實(shí)例的一個(gè)副本被創(chuàng)建,并且這個(gè)新副本被分配給cinema
登刺。雖然hd
和cinema
現(xiàn)在有相同的寬度和高度籽腕,他們是幕后兩種完全不同的情況。
接下來纸俭,將width
屬性cinema
修改為用于數(shù)字電影投影的寬度稍寬的2K標(biāo)準(zhǔn)(2048
像素寬和1080
像素高):
cinema.width = 2048
檢查width
屬性cinema
顯示它確實(shí)已更改為2048
:
print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
但是皇耗,width
原始hd
實(shí)例的屬性仍具有以下舊值1920
:
print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"
當(dāng)cinema
給出當(dāng)前值時(shí)hd
,存儲(chǔ)在其中的值hd
被復(fù)制到新cinema
實(shí)例中揍很。最終結(jié)果是兩個(gè)完全分離的實(shí)例郎楼,它們恰好包含相同的數(shù)值。由于它們是單獨(dú)的實(shí)例窒悔,因此設(shè)置寬度cinema
以2048
不影響存儲(chǔ)的寬度hd
呜袁。
相同的行為適用于枚舉:
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")
}
// Prints "The remembered direction is still .west"
當(dāng)rememberedDirection
賦值的時(shí)候currentDirection
,它實(shí)際上被設(shè)置為該值的一個(gè)副本简珠。currentDirection
此后更改此值不會(huì)影響存儲(chǔ)在其中的原始值的副本rememberedDirection
阶界。
類是引用類型
與值類型不同,引用類型在分配給變量或常量時(shí)北救,或者傳遞給函數(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í)例宅倒。視頻模式被分配的HD分辨率的副本攘宙,1920
通過1080
從之前。它被設(shè)置為隔行掃描拐迁,并被命名為"1080i"
蹭劈。最后,它被設(shè)置為25.0
每秒幀數(shù)的幀速率线召。
接下來铺韧,tenEighty
將其分配給一個(gè)新的常量,并調(diào)用alsoTenEighty
幀速率alsoTenEighty
:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因?yàn)轭愂且妙愋停?code>tenEighty并且alsoTenEighty
實(shí)際上都引用同一個(gè) VideoMode
實(shí)例缓淹。實(shí)際上哈打,它們只是同一個(gè)實(shí)例的兩個(gè)不同名稱塔逃。
檢查frameRate
屬性tenEighty
顯示它正確報(bào)告30.0
來自底層VideoMode
實(shí)例的新幀速率:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
請(qǐng)注意,tenEighty
并alsoTenEighty
聲明為常量料仗,而不是變量湾盗。但是,你仍然可以改變tenEighty.frameRate
立轧,alsoTenEighty.frameRate
因?yàn)槌A?code>tenEighty和alsoTenEighty
常量的值本身并沒有改變格粪。tenEighty
并且alsoTenEighty
它們自己不“存儲(chǔ)” VideoMode
實(shí)例 - 相反,它們都指向VideoMode
幕后的實(shí)例氛改。它是frameRate
底層的屬性VideoMode
被更改帐萎,而不是常量引用的值VideoMode
。
身份運(yùn)營(yíng)商
由于類是引用類型平窘,因此多個(gè)常量和變量可能會(huì)在幕后引用同一個(gè)類的單個(gè)實(shí)例吓肋。(結(jié)構(gòu)和枚舉也是如此,因?yàn)樗鼈冊(cè)诜峙浣o常量或變量或傳遞給函數(shù)時(shí)總是被復(fù)制瑰艘。)
找出兩個(gè)常量或變量是否指向一個(gè)類的完全相同的實(shí)例有時(shí)會(huì)很有用是鬼。為了實(shí)現(xiàn)這一點(diǎn),Swift提供了兩個(gè)身份運(yùn)算符:
與(
===
) 相同與(
!==
) 不相同
使用這些運(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í)例。
“等于”意味著兩個(gè)實(shí)例在值中被認(rèn)為是“相等的”或“等價(jià)的”芒率,對(duì)于類型的設(shè)計(jì)者定義的“相等”的某些適當(dāng)?shù)暮x囤耳。
當(dāng)您定義自己的自定義類和結(jié)構(gòu)時(shí),您有責(zé)任決定兩個(gè)“平等”實(shí)例的合格性偶芍。在等價(jià)運(yùn)算符中描述定義您自己的“等于”和“不等于”運(yùn)算符的實(shí)現(xiàn)的過程充择。
指針
如果您有使用C,C ++或Objective-C的經(jīng)驗(yàn)匪蟀,您可能會(huì)知道這些語言使用指針來引用內(nèi)存中的地址椎麦。引用某個(gè)引用類型的實(shí)例的Swift常量或變量類似于C中的指針,但不是指向內(nèi)存中某個(gè)地址的直接指針材彪,也不需要寫一個(gè)asterisk(*
)來指示您是創(chuàng)造一個(gè)參考观挎。相反,這些引用是像Swift中的其他常量或變量一樣定義的段化。
選擇類和結(jié)構(gòu)
您可以使用類和結(jié)構(gòu)來定義自定義數(shù)據(jù)類型嘁捷,以用作程序代碼的構(gòu)建塊。
但是显熏,結(jié)構(gòu)實(shí)例總是按值傳遞雄嚣,而類實(shí)例總是按引用傳遞。這意味著它們適合于不同類型的任務(wù)喘蟆。在考慮項(xiàng)目所需的數(shù)據(jù)結(jié)構(gòu)和功能時(shí)缓升,請(qǐng)確定每個(gè)數(shù)據(jù)結(jié)構(gòu)是應(yīng)該定義為類還是結(jié)構(gòu)夷磕。
作為一般指導(dǎo)原則,考慮在適用以下一個(gè)或多個(gè)條件時(shí)創(chuàng)建一個(gè)結(jié)構(gòu):
該結(jié)構(gòu)的主要目的是封裝一些相對(duì)簡(jiǎn)單的數(shù)據(jù)值仔沿。
當(dāng)分配或傳遞該結(jié)構(gòu)的實(shí)例時(shí)坐桩,期望封裝值將被復(fù)制而不是被引用是合理的。
結(jié)構(gòu)存儲(chǔ)的任何屬性都是它們自己的值類型封锉,也可能被復(fù)制而不是引用绵跷。
該結(jié)構(gòu)不需要繼承其他現(xiàn)有類型的屬性或行為。
良好的結(jié)構(gòu)候選人的例子包括:
幾何形狀的大小成福,也許封裝一個(gè)
width
屬性和一個(gè)height
屬性碾局,都是類型Double
。一種引用一系列范圍內(nèi)的范圍的方法奴艾,也許封裝一個(gè)
start
屬性和一個(gè)length
屬性净当,兩者都是類型Int
。3D坐標(biāo)系中的一個(gè)點(diǎn)蕴潦,可能是封裝
x
像啼,y
以及z
每個(gè)類型的屬性Double
。
在所有其他情況下潭苞,定義一個(gè)類忽冻,并創(chuàng)建該類的實(shí)例,以便通過引用進(jìn)行管理和傳遞此疹。實(shí)際上僧诚,這意味著大多數(shù)自定義數(shù)據(jù)結(jié)構(gòu)應(yīng)該是類而不是結(jié)構(gòu)。
字符串蝗碎,數(shù)組和字典的賦值和復(fù)制行為
在夫特湖笨,許多基本的數(shù)據(jù)類型,如String
蹦骑,Array
以及Dictionary
被實(shí)現(xiàn)為結(jié)構(gòu)慈省。這意味著如果將字符串,數(shù)組和字典等數(shù)據(jù)分配給新的常量或變量脊串,或者將它們傳遞給函數(shù)或方法時(shí)辫呻,它們將被復(fù)制清钥。
此行為是不同的基金:NSString
琼锋,NSArray
,和NSDictionary
為類祟昭,而不是結(jié)構(gòu)來實(shí)現(xiàn)缕坎。基金會(huì)中的字符串篡悟,數(shù)組和字典始終作為對(duì)現(xiàn)有實(shí)例的引用進(jìn)行分配和傳遞谜叹,而不是作為副本匾寝。
注意
上面的描述涉及字符串,數(shù)組和字典的“復(fù)制”荷腊。您在代碼中看到的行為將始終像發(fā)生副本一樣艳悔。但是,當(dāng)絕對(duì)必要時(shí)女仰,Swift僅在幕后執(zhí)行實(shí)際的副本猜年。Swift管理所有值復(fù)制以確保最佳性能,并且您不應(yīng)該避免分配嘗試搶占此優(yōu)化疾忍。