翻譯自:Classes and Structures
類和結(jié)構(gòu)體是組成程序代碼的通用的、靈活的結(jié)構(gòu)。通過(guò)使用與常亮、變量和函數(shù)完全相同的語(yǔ)法定義屬性和方法來(lái)為類和結(jié)構(gòu)添加功能。
與其他編程語(yǔ)言不同,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ì)象。然而,swift類和結(jié)構(gòu)體在功能上比其他語(yǔ)言更接近,而本章介紹了很多可以應(yīng)用到的類和結(jié)構(gòu)體的實(shí)例的功能。
比較類和結(jié)構(gòu)體
Swift中的類和結(jié)構(gòu)體有許多共同之處。兩者都可以:
- 定義屬性以存儲(chǔ)值
- 定義提供功能的方法
- 使用下標(biāo)語(yǔ)法定義下標(biāo)以提供對(duì)其值的訪問(wèn)
- 定義初始化程序以設(shè)置其初始狀態(tài)
- 擴(kuò)展到超出默認(rèn)實(shí)現(xiàn)范圍的功能
- 符合協(xié)議以提供某種標(biāo)準(zhǔn)功能
有關(guān)更多信息, 參閱 Properties, Methods, Subscripts, Initialization, Extensions, and Protocols.
類具有結(jié)構(gòu)體不具有的其它功能:
- 繼承使一個(gè)類能夠繼承另一個(gè)類的特性。
- 類型轉(zhuǎn)換使您能夠在運(yùn)行時(shí)檢查和解釋實(shí)例的類型。
- Deinitializers使類的一個(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ù)。
定義語(yǔ)法
類和結(jié)構(gòu)具有類似的定義語(yǔ)法。您可以使用class關(guān)鍵字定義類,struct關(guān)鍵字定義結(jié)構(gòu)體。兩者都將其整個(gè)定義放在一對(duì)大括號(hào)中:
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
注意
每當(dāng)你定義一個(gè)新的類或結(jié)構(gòu)時(shí),你都可以有效地定義一個(gè)全新的Swift類型。給出類型UpperCamelCase名稱(如SomeClass和SomeStructure這里)來(lái)匹配標(biāo)準(zhǔn)Swift類型的資本(如String,Int和Bool)。相反,總是給屬性和方法lowerCamelCase名稱(如frameRate和incrementCount)來(lái)區(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?
}
上面的例子定義了一種新的結(jié)構(gòu)體,稱為Resolution,用來(lái)描述基于像素的顯示分辨率。這個(gè)結(jié)構(gòu)體有兩個(gè)存儲(chǔ)屬性,稱為width和height。存儲(chǔ)的屬性是作為類或結(jié)構(gòu)的一部分捆綁并存儲(chǔ)的常量或變量。這兩個(gè)屬性被推斷為int類型,將它們?cè)O(shè)置為初始整數(shù)值為0。
上面的例子還定義了一個(gè)新類VideoMode,用于描述視頻顯示的特定視頻模式。這個(gè)類有四個(gè)變量存儲(chǔ)的屬性。第一個(gè),resolution是用一個(gè)新的Resolution結(jié)構(gòu)實(shí)例初始化的,它推斷出一個(gè)屬性類型Resolution。對(duì)于其他三個(gè)屬性,VideoMode將使用(意思是“非隔行視頻”)interlaced設(shè)置來(lái)初始化新實(shí)例false,播放幀速率為0.0,以及可選String值為name。該name屬性會(huì)自動(dòng)給出默認(rèn)值nil或“無(wú)name值”,因?yàn)樗强蛇x類型。
類和結(jié)構(gòu)實(shí)例
該Resolution結(jié)構(gòu)定義和VideoMode類定義只說(shuō)明什么Resolution或VideoMode看起來(lái)像。他們自己沒(méi)有描述特定的分辨率或視頻模式。要做到這一點(diǎn),你需要?jiǎng)?chuàng)建一個(gè)結(jié)構(gòu)或類的實(shí)例。
創(chuàng)建實(shí)例的語(yǔ)法對(duì)于結(jié)構(gòu)和類都非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
結(jié)構(gòu)和類都為新實(shí)例使用初始化語(yǔ)法。最簡(jiǎn)單的初始化語(yǔ)法形式使用類或結(jié)構(gòu)體的名稱,后跟空括號(hào),如Resolution()或VideoMode()。這將創(chuàng)建類或結(jié)構(gòu)的新實(shí)例,并將任何屬性初始化為默認(rèn)值。類和結(jié)構(gòu)初始化在初始化中有更詳細(xì)的描述。
訪問(wèn)屬性
您可以使用點(diǎn)語(yǔ)法訪問(wèn)實(shí)例的屬性。在點(diǎn)語(yǔ)法中,可以在實(shí)例名稱后面立即寫(xiě)入屬性名稱,并用句點(diǎn)(.)分隔,但不帶任何空格:
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
在這個(gè)例子中,someResolution.width指的是someResolution的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ǔ)法為變量屬性指定一個(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è)例子中,someVideoMode 的resolutionproperty 的width屬性是直接設(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)和枚舉是值類型
值類型是當(dāng)其被傳遞給函數(shù)時(shí)或者分配給變量或常量時(shí),它的值被拷貝。
事實(shí)上,Swift整數(shù),浮點(diǎn)數(shù),布爾值,字符串,數(shù)組和字典中的所有基本類型都是值類型,并在后臺(tái)實(shí)現(xiàn)為結(jié)構(gòu)結(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)镽esolution是一個(gè)結(jié)構(gòu),現(xiàn)有實(shí)例的一個(gè)副本被創(chuàng)建,并且這個(gè)新副本被分配給cinema。雖然hd和cinema現(xiàn)在有相同的寬度和高度,他們是幕后兩種完全不同的情況。
接下來(lái),將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通過(guò)1080從之前。它被設(shè)置為隔行掃描,并被命名為"1080i"。最后,它被設(shè)置為25.0每秒幀數(shù)的幀速率。
接下來(lái),tenEighty將其分配給一個(gè)新的常量,并調(diào)用alsoTenEighty幀速率alsoTenEighty:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因?yàn)轭愂且妙愋?,tenEighty并且alsoTenEighty實(shí)際上都引用同一個(gè) VideoMode實(shí)例。實(shí)際上,它們只是同一個(gè)實(shí)例的兩個(gè)不同名稱。
檢查frameRate屬性tenEighty顯示它正確報(bào)告30.0來(lái)自底層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縯enEighty和alsoTenEighty常量的值本身并沒(méi)有改變。tenEighty并且alsoTenEighty它們自己不“存儲(chǔ)” VideoMode實(shí)例 - 相反,它們都指向VideoMode幕后的實(shí)例。它是VideoMode底層的屬性frameRate被更改,而不是常量VideoMode引用的值。
Identity 操作
由于類是引用類型,因此多個(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)算符來(lái)檢查兩個(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)為是“相等的”或“等同的”,對(duì)于類型的設(shè)計(jì)者所定義的“相等”的某些適當(dāng)?shù)暮x。
當(dāng)您定義自己的自定義類和結(jié)構(gòu)時(shí),您有責(zé)任決定兩個(gè)“平等”實(shí)例的合格性。在等價(jià)運(yùn)算符中描述定義您自己的“等于”和“不等于”運(yùn)算符的實(shí)現(xiàn)過(guò)程。
指針
如果您有使用C,C ++或Objective-C的經(jīng)驗(yàn),您可能會(huì)知道這些語(yǔ)言使用指針來(lái)引用內(nèi)存中的地址。引用某個(gè)引用類型的實(shí)例的Swift常量或變量類似于C中的指針,但不是指向內(nèi)存中某個(gè)地址的直接指針,也不需要寫(xiě)一個(gè)asterisk(*)來(lái)指示您是創(chuàng)造一個(gè)參考。相反,這些引用是像Swift中的其他常量或變量一樣定義的。
選擇類和結(jié)構(gòu)
您可以使用類和結(jié)構(gòu)來(lái)定義自定義數(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)建結(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í)例,以便通過(guò)引用進(jìn)行管理和傳遞。實(shí)際上,這意味著大多數(shù)自定義數(shù)據(jù)結(jié)構(gòu)應(yīng)該是類而不是結(jié)構(gòu)。
字符串,數(shù)組和字典的賦值和復(fù)制行為
在swift中,許多基本的數(shù)據(jù)類型,如String,Array以及Dictionary被實(shí)現(xiàn)為結(jié)構(gòu)體。這意味著如果將字符串,數(shù)組和字典等數(shù)據(jù)分配給新的常量或變量,或者將它們傳遞給函數(shù)或方法時(shí),它們將被復(fù)制。
在OC的Foundation框架中:NSString,NSArray,和NSDictionary為類,而不是結(jié)構(gòu)來(lái)實(shí)現(xiàn)。Foundation中的字符串,數(shù)組和字典始終作為對(duì)現(xiàn)有實(shí)例的引用進(jìn)行分配和傳遞,而不是作為副本。
注意
上面的描述涉及字符串,數(shù)組和字典的“復(fù)制”。您在代碼中看到的行為將始終像發(fā)生副本一樣。但是,Swift僅在絕對(duì)必要時(shí),幕后執(zhí)行實(shí)際的副本。Swift管理所有值復(fù)制以確保最佳性能,并且您不應(yīng)該避免分配嘗試搶占此優(yōu)化。